{"name":"letterstory MCP server","protocolVersion":"2025-06-18","transport":"streamable-http","endpoint":"/api/mcp","auth":{"header":"x-integrations-key"},"oauth":{"authorizationUrl":"/api/oauth/authorize","tokenUrl":"/api/oauth/token","revocationUrl":"/api/oauth/revoke","header":"authorization","scheme":"Bearer"},"tools":[{"name":"search_posts","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":100},"limit":{"description":"Maximum results to return. Defaults to 8; 20 at most.","type":"integer","minimum":1,"maximum":20}},"required":["query"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"href":{"type":"string"}},"required":["id","title","href"]}}},"required":["items"]}},{"name":"search_collections","description":"Search collections 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. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":100},"limit":{"description":"Maximum results to return. Defaults to 8; 20 at most.","type":"integer","minimum":1,"maximum":20}},"required":["query"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"href":{"type":"string"}},"required":["id","title","href"]}}},"required":["items"]}},{"name":"search_kernels","description":"Search kernels 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. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":100},"limit":{"description":"Maximum results to return. Defaults to 8; 20 at most.","type":"integer","minimum":1,"maximum":20}},"required":["query"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"href":{"type":"string"}},"required":["id","title","href"]}}},"required":["items"]}},{"name":"search_templates","description":"Search templates 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. Requires capability template:read.","capability":"template:read","inputSchema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":100},"limit":{"description":"Maximum results to return. Defaults to 8; 20 at most.","type":"integer","minimum":1,"maximum":20}},"required":["query"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"href":{"type":"string"}},"required":["id","title","href"]}}},"required":["items"]}},{"name":"ingest_article","description":"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 Letter 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. THE WAY TO IMPORT A POST YOU ALREADY WROTE: nothing is written or rewritten and no cadence applies; a complete body reads as finished, so schedule_article_publish (a day) or publish_article (now) can take it straight away. run_collection writes a new post instead. Returns the article id and title. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500,"description":"Article title"},"body":{"type":"string","minLength":1,"maxLength":1000000,"description":"HTML or markdown body"},"collection_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"UUID of the collection this article belongs to"},"article_id":{"description":"Existing article UUID (from a prior ingest_article call) to update in place instead of creating a new record. Must belong to the caller's org. Omit to create a new draft.","type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"context":{"type":"string","maxLength":2000,"description":"Your notes for this post: an angle to steer research, and/or your own data (benchmarks, metrics, customer results). Figures stated here are your first-party data: the post states them credited in words the way you attribute them, never linked to a page and never softened."},"custom_fields":{"description":"Values for this collection's declared custom fields (set_collection_config), keyed by field key -- e.g. { faq: [...] } if the source markdown already has one. A key matching a declared field is written as-is and the custom-fields pass never generates it, regardless of that field's write_policy; the pass only fills in keys you didn't provide, as a backup. A key with no matching declared field, or a value that doesn't match that field's shape, is dropped.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["title","body","collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","description":"Lifecycle status of the new draft (e.g. 'draft')."},"created_at":{"type":"string"}},"required":["article_id","title","status","created_at"]}},{"name":"run_custom_fields_pass","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"collection_id":{"type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"force":{"description":"Bypass the create_only guard and regenerate every enabled field, not just missing ones.","type":"boolean"},"limit":{"description":"Collection sweep only: how many articles to process in this call (default and max 50).","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"detail":{"type":"string"},"warning":{"type":"string"},"changed":{"type":"boolean"}},"required":["article_id","detail","changed"]}},"has_more":{"type":"boolean","description":"Collection sweep only: true when more articles remain — call again to continue."}},"required":["items","has_more"]}},{"name":"list_articles","description":"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, published_after/published_before for posts published in a period, and offset with limit to page (a letterbase's All posts). `total` is how many match in all, past the page: read it for a count (how many posts went out last month), never count a page. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"limit":{"default":20,"description":"Maximum number of items to return","type":"integer","minimum":1,"maximum":100},"collection_id":{"description":"Optional collection UUID (from list_collections) to filter by","type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"phase":{"description":"Filter to one lifecycle phase. 'finished' = a complete draft, never published, awaiting review — the same fact notify-standing-reviewers and the Control Room's pending-review bar watch.","type":"string","enum":["queued","writing","editing","in_review","finished","published"]},"unpublished_only":{"description":"When true, exclude articles that are already published (lifecycle_phase = 'published') — any of the other five phases still shows up. Use this before a bulk import to skip posts already shipped, without pinning to one exact phase the way `phase` does.","type":"boolean"},"phases":{"description":"Any of these lifecycle phases (e.g. ['queued','writing','editing','in_review'] for posts being written).","minItems":1,"maxItems":6,"type":"array","items":{"type":"string","enum":["queued","writing","editing","in_review","finished","published"]}},"search":{"description":"Only titles containing these words (case-insensitive).","type":"string","minLength":1,"maxLength":200},"mine":{"description":"Only posts the caller owns (metadata.owners holds the key's user). Ignored for a key with no user.","type":"boolean"},"offset":{"description":"Skip this many (paging, with limit). Default 0.","type":"integer","minimum":0,"maximum":10000},"published_after":{"description":"Only posts published at or after this ISO date or time (e.g. 2026-10-01). With published_before, `total` answers how many posts went out in a period.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"published_before":{"description":"Only posts published before this ISO date or time (e.g. 2026-11-01: October is before November 1).","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"lifecycle_phase":{"type":"string"},"collection_id":{"type":["string","null"]},"updated_at":{"type":"string"},"published_at":{"type":["string","null"]}},"required":["article_id","title","status","lifecycle_phase","collection_id","updated_at","published_at"]}},"total":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"How many articles match the filters in all, past this page; null when it could not be counted."}},"required":["items","total"]}},{"name":"list_collections","description":"List the caller's collections — each a publication with a standing configuration that produces and publishes posts on a cadence. `public_id` is the collection's address for the HTTP door (POST /api/collection/ingest/{public_id}); `hosted` marks a phantom (a site Letter hosts as an independent publication); `sponsor_disclosure_surfaces` says where it discloses its sponsor (empty = nowhere). Use collection_id to filter list_articles, as the target for ingest_article, or to run_collection. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string","description":"The collection's address in POST /api/collection/ingest/{public_id}. Not a secret."},"name":{"type":"string"},"description":{"type":["string","null"]},"profile":{"type":"string","description":"Display label: phantom | client_blog | owned_site. Never a code path."},"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"hosted":{"type":"boolean","description":"Whether a Letter-hosted site (a phantom) backs this collection."},"is_main":{"type":"boolean","description":"The org's MAIN blog — the collection get_onboarding_plan onboards, Measure defaults to and own-site probes bind to. Exactly one client collection; move it with set_main_collection."},"cadence_enabled":{"type":"boolean"},"posts_per_week":{"type":["number","null"]},"publishes":{"type":"object","properties":{"mode":{"type":"string","enum":["database","pull_request","handoff"],"description":"database: publishing puts the post live (Letter or a CMS); pull_request: it opens a PR in the customer's repo and is live once merged; handoff: a person publishes."},"destination":{"type":"string","description":"Where it lands, in words: Letter, Sanity, GitHub, …"}},"required":["mode","destination"],"description":"What publishing a post from this collection does (docs/architecture/content-databases.md)."},"sponsor_disclosure_surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where the site discloses its sponsor (get_collection_config's disclosure section). Empty = no disclosure; always empty on a client-brand collection."},"last_run_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","profile","brand_subject","publish_control","hosted","is_main","cadence_enabled","posts_per_week","publishes","sponsor_disclosure_surfaces","last_run_at","created_at","updated_at"]}}},"required":["items"]}},{"name":"get_collections_overview","description":"How is each collection doing? The Collections page as data: every collection with the site Letter hosts for it (null for the client's own blog), where it publishes, its running state in one word (publishing / paused / setting_up / needs_attention / manual), posts total and published with the last publish date, posts showing in Google (hosted sites), and AI reads — AI assistants fetching its pages to answer someone — over the last 30 days with the 30 before and a daily series (hosted sites). For the collection's settings use get_collection_config; for posts, list_articles. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ai_window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"The AI-reads window: the last 30 days, ending yesterday."},"items":{"type":"array","items":{"type":"object","properties":{"collection":{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string","description":"The collection's address in POST /api/collection/ingest/{public_id}. Not a secret."},"name":{"type":"string"},"description":{"type":["string","null"]},"profile":{"type":"string","description":"Display label: phantom | client_blog | owned_site. Never a code path."},"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"hosted":{"type":"boolean","description":"Whether a Letter-hosted site (a phantom) backs this collection."},"is_main":{"type":"boolean","description":"The org's MAIN blog — the collection get_onboarding_plan onboards, Measure defaults to and own-site probes bind to. Exactly one client collection; move it with set_main_collection."},"cadence_enabled":{"type":"boolean"},"posts_per_week":{"type":["number","null"]},"publishes":{"type":"object","properties":{"mode":{"type":"string","enum":["database","pull_request","handoff"],"description":"database: publishing puts the post live (Letter or a CMS); pull_request: it opens a PR in the customer's repo and is live once merged; handoff: a person publishes."},"destination":{"type":"string","description":"Where it lands, in words: Letter, Sanity, GitHub, …"}},"required":["mode","destination"],"description":"What publishing a post from this collection does (docs/architecture/content-databases.md)."},"sponsor_disclosure_surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where the site discloses its sponsor (get_collection_config's disclosure section). Empty = no disclosure; always empty on a client-brand collection."},"last_run_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","profile","brand_subject","publish_control","hosted","is_main","cadence_enabled","posts_per_week","publishes","sponsor_disclosure_surfaces","last_run_at","created_at","updated_at"]},"site":{"anyOf":[{"type":"object","properties":{"blog_id":{"type":"string"},"name":{"type":"string"},"url":{"type":["string","null"]},"status":{"type":"string","description":"provisioning | awaiting_dns | live | error"},"paused":{"type":"boolean"}},"required":["blog_id","name","url","status","paused"]},{"type":"null"}],"description":"The site Letter hosts for this collection; null for the client's own blog."},"destination":{"description":"Where its posts publish: a site host or a CMS name.","type":["string","null"]},"state":{"type":"string","enum":["publishing","paused","setting_up","needs_attention","manual"],"description":"The running state in one word, read from the site status and whether the cadence is on."},"posts":{"type":"object","properties":{"total":{"type":"number","description":"Every article in the collection, drafts included."},"published":{"type":"number"},"last_published_at":{"type":["string","null"]}},"required":["total","published","last_published_at"]},"in_google":{"anyOf":[{"type":"object","properties":{"showing":{"type":"number"},"of":{"type":"number"}},"required":["showing","of"]},{"type":"null"}],"description":"Published posts Google has shown at least once. Hosted sites only; null otherwise."},"ai_reads":{"anyOf":[{"type":"object","properties":{"reads":{"type":"number","description":"AI assistants fetching a page to answer someone (live fetch episodes)."},"previousReads":{"description":"The 30 days before; null when the site began reporting inside the window.","type":["number","null"]},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"aiReads":{"type":"number"}},"required":["date","aiReads"]}}},"required":["reads","previousReads","days"]},{"type":"null"}],"description":"Hosted sites only; null otherwise."},"upcoming":{"type":"object","properties":{"next_post_at":{"description":"When the next post publishes: a planner slot, a scheduled post or a draft's auto-publish.","type":["string","null"]},"awaiting_approval":{"type":"number","description":"Finished drafts that wait on a person before they publish."},"awaiting_approval_more":{"type":"boolean","description":"True when more drafts are finished than were checked: awaiting_approval is then a floor."}},"required":["next_post_at","awaiting_approval","awaiting_approval_more"],"description":"What happens next."}},"required":["collection","site","destination","state","posts","in_google","ai_reads","upcoming"]}},"totals":{"type":"object","properties":{"collections":{"type":"number"},"published":{"type":"number"},"showing":{"type":"number"},"ai_reads":{"type":"number"}},"required":["collections","published","showing","ai_reads"]}},"required":["ai_window","items","totals"]}},{"name":"create_collection","description":"Create a collection — a publication with its own configuration, cadence and topic map. Pass `config` to start it configured (cadence, content, publishing, targets, measurement — the same one-file document set_collection_config takes); its identity (brand_subject, publish_control) defaults to a client blog publishing on Letter. For a site Letter hosts as an independent publication, use create_phantom instead. Returns the collection with its public_id. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"config":{"description":"Optional starting configuration, applied after the collection exists.","type":"object","properties":{"identity":{"type":"object","properties":{"brand_subject":{"description":"'independent' = the publication never carries the client's name (a phantom); 'client' = it is the client's own.","type":"string","enum":["client","independent"]},"publish_control":{"description":"'platform' pins a version on Letter; 'connector' pushes through the bound CMS; 'advisory' files a revision proposal instead of publishing.","type":"string","enum":["platform","connector","advisory"]}}},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"cadence":{"type":"object","properties":{"enabled":{"description":"Whether the planner keeps the collection producing on cadence.","type":"boolean"},"posts_per_week":{"description":"Posts a week. A client's own blog goes up to 21 (three a day); a phantom (independent) site up to 7.","type":"integer","minimum":1,"maximum":21},"timezone":{"type":"string","minLength":1,"maxLength":64},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"description":"The steering prompt every post and the topic map follow. null clears.","anyOf":[{"type":"string","maxLength":4000},{"type":"null"}]},"lead_time_hours":{"description":"How many hours before a publish slot the planner starts writing.","type":"integer","minimum":1,"maximum":168},"review_window_hours":{"description":"review_window only: hours a ready post waits for a person before it publishes.","type":"integer","minimum":1,"maximum":336},"notifications":{"description":"Who the planner emails, and when. Empty emails = the planner's acting user.","type":"object","properties":{"emails":{"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"on_ready_for_review":{"type":"boolean"},"on_published":{"type":"boolean"},"on_failure":{"type":"boolean"}}}}},"planner":{"type":"object","properties":{"use_backlog":{"description":"Also claim from the Curate backlog, not just suggestions.","type":"boolean"},"min_confidence":{"description":"Minimum model confidence for a headline.","type":"number","minimum":0,"maximum":1},"avoid_recent_days":{"description":"Skip topics too close to anything produced in the last N days.","type":"integer","minimum":0,"maximum":365},"max_attempts_per_run":{"description":"Retries per phase before a run fails.","type":"integer","minimum":1,"maximum":5},"pause_on_failure_count":{"description":"Consecutive failed runs that pause the planner; 0 = never.","type":"integer","minimum":0,"maximum":10},"resolution_floor":{"description":"Vet new subtopics against the resolution floor.","type":"boolean"},"comparison_share_cap":{"description":"Cap on the share of slots that may be comparison/roundup topics; null = no cap.","anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"mention_degree":{"description":"The planner's own client-mention degree; inherit = content.mention_degree.","type":"string","enum":["inherit","off","subtle","woven"]}}},"publishing":{"type":"object","properties":{"publish":{"description":"Pin a version on Letter when a run finishes.","type":"boolean"},"cms":{"description":"Push through the bound CMS mapping when a run finishes.","type":"boolean"},"rebuild":{"description":"Hosted only: redeploy the site after each publish.","type":"boolean"},"live_edits":{"description":"Edits to live posts: applied automatically or held for review. null = the derived default.","anyOf":[{"type":"string","enum":["auto","review"]},{"type":"null"}]},"strategy_approval":{"description":"Changes to the automations proposed from the measures (press, ease, add): manual waits for Accept; review_window applies two days after it is sent unless someone declines. null = the derived default.","anyOf":[{"type":"string","enum":["manual","review_window"]},{"type":"null"}]},"sitemap_url":{"description":"This collection's own sitemap: the internal-links crawl target and sitemap-sync source.","anyOf":[{"type":"string","maxLength":2000,"format":"uri"},{"type":"null"}]}}},"targets":{"type":"object","properties":{"enabled":{"description":"Press the org's active ranking targets on this collection (binds each to the pillar it fits; the planner steers the tier's share of slots toward them). false dismisses the bindings.","type":"boolean"},"tier":{"description":"Manual steering tier, in ladder order: ramping (1 in 2, the floor), aggressive (2 in 3), steady (4 in 5). Omit to let the ramp earn it: a rung needs both distinct cited pages and published posts on the targets' ground to back it (targets.ramp in the readback). A tier set here is not held back by the posts.","type":"string","enum":["ramping","aggressive","steady"]}}},"measurement":{"type":"object","properties":{"geo_probing":{"description":"Probe AI engines for the collection's ground.","type":"boolean"}}},"sourcing":{"type":"object","properties":{"market":{"description":"The search market keyword demand is measured in; null = the organization's market.","anyOf":[{"type":"string","pattern":"^[A-Z]{2}$"},{"type":"null"}]},"demand_focus":{"description":"Which demand headline ranking leans on: 'seo' (keyword volume, measured Google performance), 'geo' (AI-answer gaps, ground assistants already name the client on), or 'balanced' (both, the default). It shifts scoring weights only; the other side still counts. How many slots go to ranking targets is the steering tier's share (targets.tier), under every focus.","type":"string","enum":["balanced","seo","geo"]}}},"relevance":{"type":"object","properties":{"min_topic_relevance":{"description":"The lowest spoke client_relevance the planner may write or steer toward: 'high' = only topics squarely on the client's product, 'medium' = on the product or close to it (the buyers' own questions), 'low' = anything on the map. Default 'low' (no floor) for both: the label is set by the topic generator and is unreliable as a filter; a main blog is filtered by brand_gate instead.","anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}]},"brand_gate":{"description":"The brand check on generated pillars and spokes: 'strict' drops off-brand topics, judged against the positioning (what the client is / is not) even before it has published, and flags unsure ones for a reviewer; 'narrow' also drops unsure ones (for a very tight blog); 'lenient' drops only clearly off-brand ones and skips a thin corpus; 'off' doesn't run. Main-blog default 'strict', phantom default 'off'.","anyOf":[{"type":"string","enum":["off","lenient","strict","narrow"]},{"type":"null"}]},"min_brand_fit":{"description":"The lowest judged brand fit a headline candidate may have (0 / 0.5 / 1); candidates nobody judged pass. Main-blog default 0.5 (drops judged misfits), phantom default 0.","anyOf":[{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},{"type":"null"}]},"pillar_breadth":{"description":"What a ranking target's new pillar proposal may be: 'on_product' (squarely about what the client sells) or 'tangential' (adjacent ground an independent publication covers). Main-blog default 'on_product', phantom default 'tangential'.","anyOf":[{"type":"string","enum":["on_product","tangential"]},{"type":"null"}]},"auto_approve_pillars":{"description":"Whether an unreviewed proposed target pillar auto-approves after its window. Main-blog default false (a person approves), phantom default true.","type":["boolean","null"]},"keyword_scope":{"description":"Which pillars a discovered SEO keyword is judged relevant against: 'collection' (this collection's own) or 'org' (every collection's, phantoms included). Main-blog default 'collection', phantom default 'org'.","anyOf":[{"type":"string","enum":["collection","org"]},{"type":"null"}]}}},"disclosure":{"type":"object","properties":{"surfaces":{"description":"Where this independent publication discloses its sponsor — the whole set, replacing the stored one; [] turns disclosure off. article_header: 'Sponsored by {Sponsor}' at the top of each article, beside the byline. article_footer: 'This article is sponsored by {Sponsor}.' after the body. cover_badge: 'Sponsored by {Sponsor}' set over the cover by the site. cover_baked: the same label drawn into the cover image itself, so it travels into link previews and shares (the stored cover is untouched); one or the other, not both. structured_data: schema.org `sponsor` on the article JSON-LD. sponsored_links: rel=\"sponsored\" on body links to the sponsor, Google's label for paid links (already nofollow, so this labels them as paid rather than changing what they pass). meta_description: 'Sponsored by {Sponsor}.' at the start of each article's meta and Open Graph description. site_statement: 'This publication is sponsored by {Sponsor}.' in the footer of every page, the About page, llms.txt and RSS. site_byline: articles are bylined to the site's editors instead of a named writer (author fields are served null). 'Safe mode' is every surface except the two cover ones. Only an independent collection may disclose.","maxItems":9,"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name to show instead of the resolved company name; null or '' clears it. Required to disclose on an internal or phantom_farm org.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}}},"custom_fields":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$","description":"Slug, e.g. \"faq\". The value shows up as custom_fields.<key> on the published API."},"label":{"type":"string","minLength":1,"maxLength":120},"generation_prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The instruction handed to the model when generating this field's value."},"shape":{"description":"The expected shape of a generated value — text, list_of_text, object, or list_of_objects.","$ref":"#/$defs/__schema0"},"write_policy":{"default":"always","description":"'always' regenerates on every pass; 'create_only' is written once and never touched again.","type":"string","enum":["always","create_only"]},"enabled":{"default":true,"type":"boolean"}},"required":["key","label","generation_prompt","shape"]}}}}},"required":["name"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection":{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string","description":"The collection's address in POST /api/collection/ingest/{public_id}. Not a secret."},"name":{"type":"string"},"description":{"type":["string","null"]},"profile":{"type":"string","description":"Display label: phantom | client_blog | owned_site. Never a code path."},"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"hosted":{"type":"boolean","description":"Whether a Letter-hosted site (a phantom) backs this collection."},"is_main":{"type":"boolean","description":"The org's MAIN blog — the collection get_onboarding_plan onboards, Measure defaults to and own-site probes bind to. Exactly one client collection; move it with set_main_collection."},"cadence_enabled":{"type":"boolean"},"posts_per_week":{"type":["number","null"]},"publishes":{"type":"object","properties":{"mode":{"type":"string","enum":["database","pull_request","handoff"],"description":"database: publishing puts the post live (Letter or a CMS); pull_request: it opens a PR in the customer's repo and is live once merged; handoff: a person publishes."},"destination":{"type":"string","description":"Where it lands, in words: Letter, Sanity, GitHub, …"}},"required":["mode","destination"],"description":"What publishing a post from this collection does (docs/architecture/content-databases.md)."},"sponsor_disclosure_surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where the site discloses its sponsor (get_collection_config's disclosure section). Empty = no disclosure; always empty on a client-brand collection."},"last_run_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","profile","brand_subject","publish_control","hosted","is_main","cadence_enabled","posts_per_week","publishes","sponsor_disclosure_surfaces","last_run_at","created_at","updated_at"]}},"required":["collection"]}},{"name":"get_collection_config","description":"Read a collection's configuration as ONE FILE: identity (brand_subject, publish_control), content (research depth, kernel family, verify/shred/visuals, cover, mention degree, template), cadence (on/off, posts per week, timezone, sourcing, approval mode, direction), publishing (publish locally, CMS, rebuild), targets (whether the org's ranking targets are pressed on this collection, the steering tier and the share of write slots it aims at target ground, what is bound), measurement (GEO probing), sourcing (demand_focus: whether headline ranking leans on search, AI answers, or both), disclosure (where an independent publication discloses its sponsor — the 'safe mode' surfaces — and who it names), and custom_fields (this collection's own declared post fields — e.g. faq, itemList, seoTitle — each generated automatically by run_custom_fields_pass). What set_collection_config patches, read back from the rows the engine actually reads. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"identity":{"type":"object","properties":{"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"profile":{"type":"string"},"hosted":{"type":"boolean"}},"required":["brand_subject","publish_control","profile","hosted"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"type":["string","null"]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null = the org's default.","type":["string","null"]},"visuals":{"type":"boolean"},"cover":{"type":"string","description":"editorial_image | full_ai | line_art | none (or another canvas kind)."},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"template_key":{"type":["string","null"]}},"required":["research_depth","kernel_family","verify","shred","shredder_endpoint_id","visuals","cover","mention_degree","template_key"]},"cadence":{"type":"object","properties":{"enabled":{"type":"boolean"},"posts_per_week":{"type":"number"},"timezone":{"type":"string"},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"type":["string","null"]},"configured":{"type":"boolean","description":"false = no cadence has ever been saved; the values shown are the kind-aware defaults."}},"required":["enabled","posts_per_week","timezone","approval_mode","direction","configured"]},"publishing":{"type":"object","properties":{"publish":{"type":"boolean"},"cms":{"type":"boolean"},"rebuild":{"type":"boolean"},"cms_provider":{"type":["string","null"]}},"required":["publish","cms","rebuild","cms_provider"]},"targets":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the org's active ranking targets are bound to (pressed on) this collection."},"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"steer_share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"],"description":"The share of write slots the tier steers at — `num` of every `den`: ramping 1 in 2, aggressive 2 in 3, steady 4 in 5."},"ramp":{"type":"object","properties":{"cited_pages":{"type":"number","description":"Distinct pages of this collection cited in AI answers."},"target_posts_published":{"type":"number","description":"Published posts on the targets' ground (their pillars)."},"next":{"anyOf":[{"type":"object","properties":{"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"]},"cited_pages":{"type":"number","description":"Cited pages the next rung needs."},"target_posts":{"type":"number","description":"Published target posts the next rung needs."}},"required":["tier","share","cited_pages","target_posts"]},{"type":"null"}],"description":"The next rung and what it takes — it needs BOTH. null at the top rung."}},"required":["cited_pages","target_posts_published","next"],"description":"What the steering ramp reads: a rung needs both the citations and the posts to back it."},"recent_share":{"type":"object","properties":{"target":{"type":"number"},"of":{"type":"number"}},"required":["target","of"],"description":"What the schedule actually gave target ground: `target` of the last `of` committed slots. Below steer_share means target headlines ran short."},"bound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"},"cluster_id":{"type":["string","null"]},"status":{"type":"string","enum":["bound","proposed","dismissed"]}},"required":["target_id","phrase","cluster_id","status"]}},"unbound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"}},"required":["target_id","phrase"]},"description":"Active org targets not bound here — what enabled:true would bind."},"cap":{"type":"number"}},"required":["enabled","tier","steer_share","ramp","recent_share","bound","unbound","cap"]},"measurement":{"type":"object","properties":{"geo_probing":{"type":"boolean"}},"required":["geo_probing"]},"sourcing":{"type":"object","properties":{"demand_focus":{"type":"string","enum":["balanced","seo","geo"]}},"required":["demand_focus"],"description":"How headline ranking weighs demand: 'seo' leans on search, 'geo' on AI answers, 'balanced' on both. Ranking targets keep the steering tier's share of slots under every focus."},"relevance":{"type":"object","properties":{"preset":{"type":"string","enum":["client","independent"],"description":"Whose defaults the dials start from: a main blog's or a phantom's."},"min_topic_relevance":{"type":"string","enum":["low","medium","high"]},"brand_gate":{"type":"string","enum":["off","lenient","strict","narrow"]},"min_brand_fit":{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},"pillar_breadth":{"type":"string","enum":["on_product","tangential"]},"auto_approve_pillars":{"type":"boolean"},"keyword_scope":{"type":"string","enum":["collection","org"]},"overridden":{"type":"array","items":{"type":"string"},"description":"The dials set on this collection; every other one follows the preset."}},"required":["preset","min_topic_relevance","brand_gate","min_brand_fit","pillar_breadth","auto_approve_pillars","keyword_scope","overridden"],"description":"How tightly the collection stays on the client's product (set_collection_config `relevance`; recheck_topic_relevance applies it to the pending topics)."},"disclosure":{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name override; null = the company name the context resolver gives.","type":["string","null"]},"sponsor":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"url":{"type":["string","null"]}},"required":["name","url"]},{"type":"null"}],"description":"Who the disclosure names (the label, else the resolved company), read-only; null on a client-brand collection, or where a label is required and missing."},"resolved_name":{"description":"The company the disclosure names when no sponsor_label is set, read-only; null on a client-brand collection and on internal/phantom_farm orgs, where a label is required.","type":["string","null"]},"last_change":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"note":{"type":["string","null"]},"actor_user_id":{"type":["string","null"]}},"required":["at","note","actor_user_id"]},{"type":"null"}],"description":"Who last changed the disclosure and why, read-only: from the collection's event log. An experiment's arm notes 'experiment <key> · <arm>'. null = never set."}},"required":["surfaces","sponsor_label","sponsor","resolved_name","last_change"],"description":"Sponsor disclosure ('safe mode'): where an independent publication says who sponsors it. Presentation only — added when a post is served (sponsor_disclosure on list_published/get_published/get_article), never written into the body, and it reaches every existing post on the next rebuild. Empty surfaces = off."},"custom_fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"generation_prompt":{"type":"string"},"shape":{"$ref":"#/$defs/__schema0"},"write_policy":{"type":"string","enum":["always","create_only"]},"enabled":{"type":"boolean"},"sort_order":{"type":"number"}},"required":["id","key","label","generation_prompt","shape","write_policy","enabled","sort_order"]},"description":"This collection's declared custom fields — set_collection_config's custom_fields section replaces the whole set."},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","identity","content","cadence","publishing","targets","measurement","sourcing","relevance","disclosure","custom_fields","updated_at"]}},"required":["config"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}}}},{"name":"set_collection_config","description":"PATCH a collection's one-file configuration — send only the sections and fields you mean to set; everything omitted is kept. `cadence.enabled: true` turns the planner on and materializes its publish slots (on a collection with automations, enabled and posts_per_week are their weekly total: the next automation change resets them; set paces with update_automation); `targets.enabled: true` binds the org's active ranking targets (add_ranking_target) to this collection so the tier's share of write slots press them (1 in 2, 2 in 3 or 4 in 5; a rung needs both cited pages and published target posts — targets.ramp) — the same share orders list_suggestions and the slot claim — at the tier it earns or the manual `targets.tier` you set. `content.mention_degree` must stay 'off' on an independent collection — the capability flag wins. `relevance` holds how tightly the collection stays on the client's product — min_topic_relevance, brand_gate, min_brand_fit, pillar_breadth, auto_approve_pillars, keyword_scope — each on its capability's preset (a client-brand main blog held to the product, an independent phantom free to cover its field) unless set here; null puts a dial back on the preset, `overridden` lists the ones set, and each change is logged. `sourcing.demand_focus` ('seo' | 'geo' | 'balanced') shifts headline-ranking weight toward search or AI-answer demand without dropping the other; ranking targets keep the steering tier's share of slots under every focus, and each change is logged with its actor. `disclosure` ('safe mode') sets where an independent publication discloses its sponsor: `surfaces` is the whole set (article_header, article_footer, cover_badge or cover_baked, structured_data, sponsored_links, meta_description, site_statement, site_byline; [] = off; safe mode = every surface except the cover ones; cover_baked serves cover_image as the cover with the label drawn in) and `sponsor_label` overrides the name (required on internal/phantom_farm orgs). It is served beside the body as sponsor_disclosure on list_published/get_published/get_article, never written into it, so a change reaches every existing post on the site's next rebuild (triggered here); a client-brand collection cannot disclose. `custom_fields` is the one array-shaped section: sending it REPLACES the whole declared set of this collection's own post fields (upsert-by-key, delete anything no longer listed) — each has a key, label, generation_prompt (the instruction handed to the model), a shape (text | list_of_text | object | list_of_objects), and write_policy ('always' regenerates every pass; 'create_only' is written once and never touched again). A field's generation_prompt is free text sent straight to the model and written into every future post — treat this capability accordingly when sharing the key that grants it. Values are generated by run_custom_fields_pass and appear on get_published/list_published as custom_fields.<key>. Returns the whole file as it now reads plus which sections were applied. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"config":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"identity":{"type":"object","properties":{"brand_subject":{"description":"'independent' = the publication never carries the client's name (a phantom); 'client' = it is the client's own.","type":"string","enum":["client","independent"]},"publish_control":{"description":"'platform' pins a version on Letter; 'connector' pushes through the bound CMS; 'advisory' files a revision proposal instead of publishing.","type":"string","enum":["platform","connector","advisory"]}}},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"cadence":{"type":"object","properties":{"enabled":{"description":"Whether the planner keeps the collection producing on cadence.","type":"boolean"},"posts_per_week":{"description":"Posts a week. A client's own blog goes up to 21 (three a day); a phantom (independent) site up to 7.","type":"integer","minimum":1,"maximum":21},"timezone":{"type":"string","minLength":1,"maxLength":64},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"description":"The steering prompt every post and the topic map follow. null clears.","anyOf":[{"type":"string","maxLength":4000},{"type":"null"}]},"lead_time_hours":{"description":"How many hours before a publish slot the planner starts writing.","type":"integer","minimum":1,"maximum":168},"review_window_hours":{"description":"review_window only: hours a ready post waits for a person before it publishes.","type":"integer","minimum":1,"maximum":336},"notifications":{"description":"Who the planner emails, and when. Empty emails = the planner's acting user.","type":"object","properties":{"emails":{"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"on_ready_for_review":{"type":"boolean"},"on_published":{"type":"boolean"},"on_failure":{"type":"boolean"}}}}},"planner":{"type":"object","properties":{"use_backlog":{"description":"Also claim from the Curate backlog, not just suggestions.","type":"boolean"},"min_confidence":{"description":"Minimum model confidence for a headline.","type":"number","minimum":0,"maximum":1},"avoid_recent_days":{"description":"Skip topics too close to anything produced in the last N days.","type":"integer","minimum":0,"maximum":365},"max_attempts_per_run":{"description":"Retries per phase before a run fails.","type":"integer","minimum":1,"maximum":5},"pause_on_failure_count":{"description":"Consecutive failed runs that pause the planner; 0 = never.","type":"integer","minimum":0,"maximum":10},"resolution_floor":{"description":"Vet new subtopics against the resolution floor.","type":"boolean"},"comparison_share_cap":{"description":"Cap on the share of slots that may be comparison/roundup topics; null = no cap.","anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"mention_degree":{"description":"The planner's own client-mention degree; inherit = content.mention_degree.","type":"string","enum":["inherit","off","subtle","woven"]}}},"publishing":{"type":"object","properties":{"publish":{"description":"Pin a version on Letter when a run finishes.","type":"boolean"},"cms":{"description":"Push through the bound CMS mapping when a run finishes.","type":"boolean"},"rebuild":{"description":"Hosted only: redeploy the site after each publish.","type":"boolean"},"live_edits":{"description":"Edits to live posts: applied automatically or held for review. null = the derived default.","anyOf":[{"type":"string","enum":["auto","review"]},{"type":"null"}]},"strategy_approval":{"description":"Changes to the automations proposed from the measures (press, ease, add): manual waits for Accept; review_window applies two days after it is sent unless someone declines. null = the derived default.","anyOf":[{"type":"string","enum":["manual","review_window"]},{"type":"null"}]},"sitemap_url":{"description":"This collection's own sitemap: the internal-links crawl target and sitemap-sync source.","anyOf":[{"type":"string","maxLength":2000,"format":"uri"},{"type":"null"}]}}},"targets":{"type":"object","properties":{"enabled":{"description":"Press the org's active ranking targets on this collection (binds each to the pillar it fits; the planner steers the tier's share of slots toward them). false dismisses the bindings.","type":"boolean"},"tier":{"description":"Manual steering tier, in ladder order: ramping (1 in 2, the floor), aggressive (2 in 3), steady (4 in 5). Omit to let the ramp earn it: a rung needs both distinct cited pages and published posts on the targets' ground to back it (targets.ramp in the readback). A tier set here is not held back by the posts.","type":"string","enum":["ramping","aggressive","steady"]}}},"measurement":{"type":"object","properties":{"geo_probing":{"description":"Probe AI engines for the collection's ground.","type":"boolean"}}},"sourcing":{"type":"object","properties":{"market":{"description":"The search market keyword demand is measured in; null = the organization's market.","anyOf":[{"type":"string","pattern":"^[A-Z]{2}$"},{"type":"null"}]},"demand_focus":{"description":"Which demand headline ranking leans on: 'seo' (keyword volume, measured Google performance), 'geo' (AI-answer gaps, ground assistants already name the client on), or 'balanced' (both, the default). It shifts scoring weights only; the other side still counts. How many slots go to ranking targets is the steering tier's share (targets.tier), under every focus.","type":"string","enum":["balanced","seo","geo"]}}},"relevance":{"type":"object","properties":{"min_topic_relevance":{"description":"The lowest spoke client_relevance the planner may write or steer toward: 'high' = only topics squarely on the client's product, 'medium' = on the product or close to it (the buyers' own questions), 'low' = anything on the map. Default 'low' (no floor) for both: the label is set by the topic generator and is unreliable as a filter; a main blog is filtered by brand_gate instead.","anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}]},"brand_gate":{"description":"The brand check on generated pillars and spokes: 'strict' drops off-brand topics, judged against the positioning (what the client is / is not) even before it has published, and flags unsure ones for a reviewer; 'narrow' also drops unsure ones (for a very tight blog); 'lenient' drops only clearly off-brand ones and skips a thin corpus; 'off' doesn't run. Main-blog default 'strict', phantom default 'off'.","anyOf":[{"type":"string","enum":["off","lenient","strict","narrow"]},{"type":"null"}]},"min_brand_fit":{"description":"The lowest judged brand fit a headline candidate may have (0 / 0.5 / 1); candidates nobody judged pass. Main-blog default 0.5 (drops judged misfits), phantom default 0.","anyOf":[{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},{"type":"null"}]},"pillar_breadth":{"description":"What a ranking target's new pillar proposal may be: 'on_product' (squarely about what the client sells) or 'tangential' (adjacent ground an independent publication covers). Main-blog default 'on_product', phantom default 'tangential'.","anyOf":[{"type":"string","enum":["on_product","tangential"]},{"type":"null"}]},"auto_approve_pillars":{"description":"Whether an unreviewed proposed target pillar auto-approves after its window. Main-blog default false (a person approves), phantom default true.","type":["boolean","null"]},"keyword_scope":{"description":"Which pillars a discovered SEO keyword is judged relevant against: 'collection' (this collection's own) or 'org' (every collection's, phantoms included). Main-blog default 'collection', phantom default 'org'.","anyOf":[{"type":"string","enum":["collection","org"]},{"type":"null"}]}}},"disclosure":{"type":"object","properties":{"surfaces":{"description":"Where this independent publication discloses its sponsor — the whole set, replacing the stored one; [] turns disclosure off. article_header: 'Sponsored by {Sponsor}' at the top of each article, beside the byline. article_footer: 'This article is sponsored by {Sponsor}.' after the body. cover_badge: 'Sponsored by {Sponsor}' set over the cover by the site. cover_baked: the same label drawn into the cover image itself, so it travels into link previews and shares (the stored cover is untouched); one or the other, not both. structured_data: schema.org `sponsor` on the article JSON-LD. sponsored_links: rel=\"sponsored\" on body links to the sponsor, Google's label for paid links (already nofollow, so this labels them as paid rather than changing what they pass). meta_description: 'Sponsored by {Sponsor}.' at the start of each article's meta and Open Graph description. site_statement: 'This publication is sponsored by {Sponsor}.' in the footer of every page, the About page, llms.txt and RSS. site_byline: articles are bylined to the site's editors instead of a named writer (author fields are served null). 'Safe mode' is every surface except the two cover ones. Only an independent collection may disclose.","maxItems":9,"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name to show instead of the resolved company name; null or '' clears it. Required to disclose on an internal or phantom_farm org.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}}},"custom_fields":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$","description":"Slug, e.g. \"faq\". The value shows up as custom_fields.<key> on the published API."},"label":{"type":"string","minLength":1,"maxLength":120},"generation_prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The instruction handed to the model when generating this field's value."},"shape":{"description":"The expected shape of a generated value — text, list_of_text, object, or list_of_objects.","$ref":"#/$defs/__schema0"},"write_policy":{"default":"always","description":"'always' regenerates on every pass; 'create_only' is written once and never touched again.","type":"string","enum":["always","create_only"]},"enabled":{"default":true,"type":"boolean"}},"required":["key","label","generation_prompt","shape"]}}},"description":"The sections to set; anything omitted is kept."}},"required":["collection_id","config"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"identity":{"type":"object","properties":{"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"profile":{"type":"string"},"hosted":{"type":"boolean"}},"required":["brand_subject","publish_control","profile","hosted"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"type":["string","null"]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null = the org's default.","type":["string","null"]},"visuals":{"type":"boolean"},"cover":{"type":"string","description":"editorial_image | full_ai | line_art | none (or another canvas kind)."},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"template_key":{"type":["string","null"]}},"required":["research_depth","kernel_family","verify","shred","shredder_endpoint_id","visuals","cover","mention_degree","template_key"]},"cadence":{"type":"object","properties":{"enabled":{"type":"boolean"},"posts_per_week":{"type":"number"},"timezone":{"type":"string"},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"type":["string","null"]},"configured":{"type":"boolean","description":"false = no cadence has ever been saved; the values shown are the kind-aware defaults."}},"required":["enabled","posts_per_week","timezone","approval_mode","direction","configured"]},"publishing":{"type":"object","properties":{"publish":{"type":"boolean"},"cms":{"type":"boolean"},"rebuild":{"type":"boolean"},"cms_provider":{"type":["string","null"]}},"required":["publish","cms","rebuild","cms_provider"]},"targets":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the org's active ranking targets are bound to (pressed on) this collection."},"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"steer_share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"],"description":"The share of write slots the tier steers at — `num` of every `den`: ramping 1 in 2, aggressive 2 in 3, steady 4 in 5."},"ramp":{"type":"object","properties":{"cited_pages":{"type":"number","description":"Distinct pages of this collection cited in AI answers."},"target_posts_published":{"type":"number","description":"Published posts on the targets' ground (their pillars)."},"next":{"anyOf":[{"type":"object","properties":{"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"]},"cited_pages":{"type":"number","description":"Cited pages the next rung needs."},"target_posts":{"type":"number","description":"Published target posts the next rung needs."}},"required":["tier","share","cited_pages","target_posts"]},{"type":"null"}],"description":"The next rung and what it takes — it needs BOTH. null at the top rung."}},"required":["cited_pages","target_posts_published","next"],"description":"What the steering ramp reads: a rung needs both the citations and the posts to back it."},"recent_share":{"type":"object","properties":{"target":{"type":"number"},"of":{"type":"number"}},"required":["target","of"],"description":"What the schedule actually gave target ground: `target` of the last `of` committed slots. Below steer_share means target headlines ran short."},"bound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"},"cluster_id":{"type":["string","null"]},"status":{"type":"string","enum":["bound","proposed","dismissed"]}},"required":["target_id","phrase","cluster_id","status"]}},"unbound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"}},"required":["target_id","phrase"]},"description":"Active org targets not bound here — what enabled:true would bind."},"cap":{"type":"number"}},"required":["enabled","tier","steer_share","ramp","recent_share","bound","unbound","cap"]},"measurement":{"type":"object","properties":{"geo_probing":{"type":"boolean"}},"required":["geo_probing"]},"sourcing":{"type":"object","properties":{"demand_focus":{"type":"string","enum":["balanced","seo","geo"]}},"required":["demand_focus"],"description":"How headline ranking weighs demand: 'seo' leans on search, 'geo' on AI answers, 'balanced' on both. Ranking targets keep the steering tier's share of slots under every focus."},"relevance":{"type":"object","properties":{"preset":{"type":"string","enum":["client","independent"],"description":"Whose defaults the dials start from: a main blog's or a phantom's."},"min_topic_relevance":{"type":"string","enum":["low","medium","high"]},"brand_gate":{"type":"string","enum":["off","lenient","strict","narrow"]},"min_brand_fit":{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},"pillar_breadth":{"type":"string","enum":["on_product","tangential"]},"auto_approve_pillars":{"type":"boolean"},"keyword_scope":{"type":"string","enum":["collection","org"]},"overridden":{"type":"array","items":{"type":"string"},"description":"The dials set on this collection; every other one follows the preset."}},"required":["preset","min_topic_relevance","brand_gate","min_brand_fit","pillar_breadth","auto_approve_pillars","keyword_scope","overridden"],"description":"How tightly the collection stays on the client's product (set_collection_config `relevance`; recheck_topic_relevance applies it to the pending topics)."},"disclosure":{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name override; null = the company name the context resolver gives.","type":["string","null"]},"sponsor":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"url":{"type":["string","null"]}},"required":["name","url"]},{"type":"null"}],"description":"Who the disclosure names (the label, else the resolved company), read-only; null on a client-brand collection, or where a label is required and missing."},"resolved_name":{"description":"The company the disclosure names when no sponsor_label is set, read-only; null on a client-brand collection and on internal/phantom_farm orgs, where a label is required.","type":["string","null"]},"last_change":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"note":{"type":["string","null"]},"actor_user_id":{"type":["string","null"]}},"required":["at","note","actor_user_id"]},{"type":"null"}],"description":"Who last changed the disclosure and why, read-only: from the collection's event log. An experiment's arm notes 'experiment <key> · <arm>'. null = never set."}},"required":["surfaces","sponsor_label","sponsor","resolved_name","last_change"],"description":"Sponsor disclosure ('safe mode'): where an independent publication says who sponsors it. Presentation only — added when a post is served (sponsor_disclosure on list_published/get_published/get_article), never written into the body, and it reaches every existing post on the next rebuild. Empty surfaces = off."},"custom_fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"generation_prompt":{"type":"string"},"shape":{"$ref":"#/$defs/__schema0"},"write_policy":{"type":"string","enum":["always","create_only"]},"enabled":{"type":"boolean"},"sort_order":{"type":"number"}},"required":["id","key","label","generation_prompt","shape","write_policy","enabled","sort_order"]},"description":"This collection's declared custom fields — set_collection_config's custom_fields section replaces the whole set."},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","identity","content","cadence","publishing","targets","measurement","sourcing","relevance","disclosure","custom_fields","updated_at"]},"applied":{"type":"array","items":{"type":"string"},"description":"Which sections this call set."}},"required":["config","applied"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}}}},{"name":"recheck_topic_relevance","description":"Re-judge a collection's PENDING topic-map spokes (open and queued) against its relevance dials (get_collection_config `relevance`) — the same brand check the topic map runs on new topics, for a backlog planned before the dials changed. Under the collection's brand_gate: strict drops off-brand topics and flags unsure ones; narrow drops unsure too; lenient drops off-brand only; off judges nothing. A dropped OPEN topic is set to 'dismissed' with a brand_check evidence note saying why (reopen it to undo); a dropped QUEUED topic is only flagged, since it is already in a scheduled run. apply: false (default) returns the verdicts and writes nothing; apply: true writes them and logs the pass, with its counts and your note, to the collection's event history. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"apply":{"description":"false (default) = report the verdicts and write nothing; true = dismiss the open topics the collection's brand_gate drops, flag the rest, and log the pass.","type":"boolean"},"note":{"description":"Why — recorded on the collection's event log.","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"mode":{"type":"string","enum":["off","lenient","strict","narrow"],"description":"The brand_gate mode the topics were judged under."},"skipped":{"anyOf":[{"type":"string","enum":["brand_gate_off"]},{"type":"null"}]},"applied":{"type":"boolean","description":"false = a dry run: the verdicts, nothing written."},"judged":{"type":"number"},"dismissed":{"type":"array","items":{"type":"object","properties":{"topic_id":{"type":"string"},"subtopic":{"type":"string"},"pillar":{"type":"string"},"status":{"type":"string","enum":["open","queued"]},"verdict":{"type":"string","enum":["off_brand","unsure"]},"reason":{"type":"string"}},"required":["topic_id","subtopic","pillar","status","verdict","reason"]},"description":"Open topics set to 'dismissed' (or that would be) — reopen one in the topic map to undo."},"flagged_queued":{"type":"array","items":{"type":"object","properties":{"topic_id":{"type":"string"},"subtopic":{"type":"string"},"pillar":{"type":"string"},"status":{"type":"string","enum":["open","queued"]},"verdict":{"type":"string","enum":["off_brand","unsure"]},"reason":{"type":"string"}},"required":["topic_id","subtopic","pillar","status","verdict","reason"]},"description":"Queued topics the mode would drop: flagged, left in their scheduled run."},"flagged_unsure":{"type":"array","items":{"type":"object","properties":{"topic_id":{"type":"string"},"subtopic":{"type":"string"},"pillar":{"type":"string"},"status":{"type":"string","enum":["open","queued"]},"verdict":{"type":"string","enum":["off_brand","unsure"]},"reason":{"type":"string"}},"required":["topic_id","subtopic","pillar","status","verdict","reason"]},"description":"Kept, flagged unsure for a reviewer."}},"required":["collection_id","mode","skipped","applied","judged","dismissed","flagged_queued","flagged_unsure"]}},{"name":"delete_collection","description":"Delete a collection from the caller's organization. Articles in the collection are preserved but become orphaned (hidden from listings) until reassigned with assign_article_to_collection. A hosted phantom's collection cannot be deleted this way — tear the phantom down (delete_phantom). Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"collection_id":{"type":"string"}},"required":["deleted","collection_id"]}},{"name":"set_main_collection","description":"Make a client collection the organization's MAIN blog: the collection get_onboarding_plan treats as the main blog (its collection and planner steps target it), Measure defaults to, and the company's own-site AI-search probes and Search Console binding attach to. Exactly one per organization; the previous main blog stays as it is, just no longer main. Use it when the organization has more than one client collection and the plan points at the wrong one (list_collections shows `is_main`). Refuses a phantom (an independent publication). Idempotent: `changed` is false when it was already the main blog. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The client collection (from list_collections) to make the organization's main blog"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string","description":"The collection that is now the main blog."},"previous_collection_id":{"description":"The collection that was the main blog before; null if there was none.","type":["string","null"]},"changed":{"type":"boolean","description":"False when it was already the main blog."}},"required":["collection_id","previous_collection_id","changed"]}},{"name":"run_collection","description":"Produce ONE POST on a collection, THE DOOR: source the topic (given, a spoke of the map, a pool headline, or the next open spoke), write it (research through an arena, the kernel, the link/visual/metadata passes, optional shred — all in-process), gate it (approval_mode: 'manual' pauses at 'ready' for approve_collection_run, 'review_window' publishes after the hold, 'autopilot' goes straight through), publish it by the collection's publish_control, mark the spoke covered, and — when the collection is a hosted phantom — rebuild the site. Every run gets a row (get_collection_run / list_collection_runs) recording the constraints it ran with and a verification read-back (sourced, written, independent, linked, illustrated, published, covered, rebuilt). Every field is optional and inherits the collection's file; `schedule_for` places it at a future slot instead of now. This always WRITES a new post: to publish a post that is already written, ingest_article it (title + body, nothing is written) and give it a day with schedule_article_publish or publish it now with publish_article. A DIRECT run spends the collection's weekly cadence budget (its posts_per_week): on a phantom (independent) collection it is refused, never queued silently, once the week is spent; on the client's own blog it is never refused, and the response carries a cadence_warning instead. ASYNCHRONOUS AND SLOW: 20–60 minutes; returns a run_id immediately, poll get_collection_run (30–60 s apart; polling nudges the run forward). Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"topic":{"description":"Free-text headline to write. Prefer topic_id: a free-text topic is not bound to the collection's topic map, so it is refused if the collection already has that post.","type":"string","minLength":1,"maxLength":300},"topic_id":{"description":"An OPEN spoke of this collection's topic map (list_topics). Binds the post to it; the map records it covered.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"suggestion_id":{"description":"A headline from the collection's pool (list_suggestions). Provide at most one of topic / topic_id / suggestion_id; none = the next open spoke.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"source_urls":{"description":"Public URLs research reads first, before searching the web itself. Up to 20.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"context":{"description":"Your notes for this post: an angle, background research, links to read first, your own data. They become the post's creation context; a headline's own notes (add_headline `context`) are used when this is omitted.","type":"string","maxLength":2000},"must_include":{"description":"Sections the outline must cover, on top of the template's.","maxItems":40,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"research_depth":{"description":"Outline depth for this run. Omit to inherit the collection's.","type":"string","enum":["barebones","fleshed_out"]},"kernel_id":{"description":"Writing-kernel UUID (list_kernels) for this run. Omit to inherit.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"verify":{"description":"Run the adversarial fact-check on the outline. Omit to inherit (default true).","type":"boolean"},"shred":{"description":"Shred the composed prose. Omit to inherit.","type":"boolean"},"mention_degree":{"description":"How much the post names the publisher. Forced 'off' on an independent collection — the capability flag wins.","type":"string","enum":["off","subtle","woven"]},"approval_mode":{"description":"Per-run gate: 'manual' pauses at ready for approve_collection_run; 'review_window' publishes after the hold; 'autopilot' goes straight through. Omit to inherit.","type":"string","enum":["autopilot","review_window","manual"]},"publish":{"description":"false = leave the written draft (the run stops at ready, by design). Omit to inherit.","type":"boolean"},"rebuild":{"description":"Hosted only: redeploy the site after publishing. Omit to inherit.","type":"boolean"},"schedule_for":{"description":"A future instant to publish at instead of now; production starts on the collection's lead time. Omit to run immediately.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string","description":"Poll target for get_collection_run."},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"]},"collection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"run":{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]},"cadence_warning":{"description":"Present when this run pushed a collection past its own weekly cadence. Never a refusal.","type":"string"}},"required":["run_id","status","collection","run"]}},{"name":"get_collection_run","description":"Poll a collection run: status (scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled), the step in flight, the headline, the article it wrote, the constraints it ran with, the verification read-back once settled, and the arena/kernel runs it composed. Polling nudges a run in flight forward. A run at 'ready' under manual approval waits for approve_collection_run. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run id returned by run_collection."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]}},"required":["run"]}},{"name":"list_collection_runs","description":"Recent collection runs — the whole org, or one collection — newest first, with origin (direct vs the planner's own slots), caller, status, and verification. This is the planner's calendar too: 'scheduled' rows are its upcoming slots. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Filter to one collection; omitted = the whole org.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"]},"limit":{"type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]}}},"required":["runs"]}},{"name":"approve_collection_run","description":"Answer the gate: approve a run waiting at 'ready' (or one still producing — the approval is remembered and it publishes at its slot). `publish_now: true` publishes a ready run immediately instead of waiting for the slot. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A run waiting at 'ready' under manual or review_window approval."},"publish_now":{"description":"Publish immediately instead of at the run's slot (default false).","type":"boolean"}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]}},"required":["run"]}},{"name":"reschedule_collection_run","description":"Move a post on the collection's calendar to another day (YYYY-MM-DD; it goes live at the collection's publish time). Moves a slot that hasn't started writing (status 'scheduled', with a headline or open) — its writing starts its lead time ahead of the new day — or a written post already approved (status 'ready' with approved_at), whose approval stands. A written post nobody approved yet gets its day from schedule_article_publish instead (choosing the day is the approval). Use this to clear room on a day or pull a post earlier; list_collection_runs shows the calendar. A minute or two may be added to dodge a post already on that instant. Refused once a run is writing or publishing. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"From list_collection_runs: a slot that hasn't started writing (status 'scheduled'), or a written post already approved (status 'ready' with approved_at)."},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"The new day, YYYY-MM-DD. The post goes live that day at the collection's publish time and timezone."}},"required":["run_id","date"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string"},"scheduled_for":{"type":"string","description":"When it goes live now (UTC)."},"produce_at":{"type":"string","description":"When writing starts (UTC); for a written post, when it was produced."},"run":{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]}},"required":["run_id","scheduled_for","produce_at","run"]}},{"name":"cancel_collection_run","description":"Cancel a run in flight (scheduled, sourcing, writing, ready or publishing). Its slot stays occupied; a queued topic returns to the pool; a claimed spoke goes back on the map. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A run in flight (scheduled, sourcing, writing, ready or publishing)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string"},"cancelled":{"type":"boolean"},"run":{"anyOf":[{"type":"object","properties":{"run_id":{"type":"string"},"collection_id":{"type":"string"},"collection_name":{"type":["string","null"]},"article_id":{"description":"The post the run wrote; null until sourced.","type":["string","null"]},"status":{"type":"string","enum":["scheduled","sourcing","writing","ready","publishing","published","failed","skipped","cancelled"],"description":"scheduled → sourcing → writing → ready → publishing → published; failed | skipped | cancelled are terminal. 'ready' is the approval gate."},"phase":{"anyOf":[{"type":"string","enum":["source","write","review","publish"]},{"type":"null"}],"description":"The step currently running, or null when none is in flight."},"phase_detail":{"type":["string","null"]},"topic":{"description":"The headline being written; null on a placeholder that has not sourced yet.","type":["string","null"]},"topic_id":{"description":"The topic-map spoke the run advances, when sourced from the map.","type":["string","null"]},"origin":{"type":"string","enum":["direct","internal"],"description":"direct = a caller asked the door (spends the week's cadence); internal = the planner's own slot, seeding, or migrated history."},"caller":{"type":["string","null"]},"scheduled_for":{"type":"string","description":"The publish slot."},"produce_at":{"type":"string","description":"When production starts (the slot minus the collection's lead time)."},"manually_scheduled":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["source","write","review","publish"]},"status":{"type":"string","enum":["pending","running","completed","skipped","failed"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]}},"required":["key","status","detail","warning"]}},"approval":{"type":"object","properties":{"mode":{"type":"string"},"ready_at":{"type":["string","null"]},"approved_at":{"type":["string","null"]}},"required":["mode","ready_at","approved_at"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The resolved run document the door wrote (what it ran with, and which fields were declared)."},"verification":{"anyOf":[{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["sourced","written","independent","linked","illustrated","published","covered","rebuilt"]},"verdict":{"type":"string","enum":["pass","fail","skipped"]},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"}},"required":["checks","at"],"description":"What was read back on settle against the run's constraints. Report-only; a skip is never a pass."},{"type":"null"}]},"research_run_id":{"description":"The arena run that researched it.","type":["string","null"]},"kernel_run_id":{"description":"The kernel run that wrote it (get_kernel_endpoint_run).","type":["string","null"]},"deployment_id":{"description":"The hosted site rebuilt after publishing, when exactly one backs the collection.","type":["string","null"]},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"error":{"type":["string","null"]},"latency_ms":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["run_id","collection_id","collection_name","article_id","status","phase","phase_detail","topic","topic_id","origin","caller","scheduled_for","produce_at","manually_scheduled","steps","approval","constraints","verification","research_run_id","kernel_run_id","deployment_id","published_version","published_at","error","latency_ms","created_at","updated_at","completed_at"]},{"type":"null"}]}},"required":["run_id","cancelled","run"]}},{"name":"update_auto_pen_config","description":"Set a collection's Auto-Pen configuration — the same settings the app's Auto-Pen panel writes, including which content template its posts compose with. This is a PATCH: omitted fields are left as they are, so changing one setting never blanks the rest. template_key binds a template (from list_templates) as the collection's default, which is how a template authored through this API actually gets used. Returns the full resulting configuration. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"},"template_key":{"description":"The collection's DEFAULT content template key (from list_templates). Every post drafted here composes with it unless the post overrides it. null clears it — posts are then written untemplated.","anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}]},"outline":{"description":"The research step","type":"object","properties":{"enabled":{"type":"boolean"},"depth":{"type":"string","enum":["barebones","fleshed_out"]},"direction_gate":{"description":"Pause after research so an author picks the direction","type":"boolean"}}},"kernel":{"description":"The writing step","type":"object","properties":{"enabled":{"type":"boolean"},"family":{"description":"Writing-kernel family name; null inherits the org's selected kernel","anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}}},"mention":{"description":"How much a finished article names the publishing org","type":"object","properties":{"degree":{"type":"string","enum":["off","subtle","woven"]},"rate":{"description":"Phantom collections only","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]}}},"enhance":{"type":"object","properties":{"enabled":{"type":"boolean"},"flow_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}}},"auto_start_on_curate":{"description":"Start the sequence automatically when a post is added from Curate","type":"boolean"},"shred":{"type":"object","properties":{"enabled":{"type":"boolean"},"endpoint_id":{"description":"Shredder endpoint the in-loop shred is attributed to. Omit for the org's default, provisioned on first use.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["enabled"]},"relink":{"description":"Internal-link refresh","type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"preferred_sources":{"description":"REPLACES the collection's preferred sources: SITES (zip.security, cisa.gov — a URL with a path is saved as its site) that research reaches for first. For every post, each site is searched for pages on that post's topic, and a figure from them is used where it supports a claim as well as anything else would. The client's own site may be listed: its reports, surveys and docs are then cited and linked like any other source. A preference never makes a page citable that the source policy would not cite; on an independent collection the client's homepage and sales pages are never used, its customer stories only beside other companies' results, a post that does not name the client credits its figure without the name, at most one of its figures goes in a post, and at most one in client_one_in published posts cites it. [] clears the list.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"The ceiling on the client's own site: at most one in this many of the collection's published posts cites it (default 4). A ceiling, not a target: relevance decides how many do.","type":"integer","minimum":2,"maximum":50}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"auto_pen_config":{"type":"object","properties":{"outline":{"type":"object","properties":{"enabled":{"type":"boolean"},"depth":{"type":"string","enum":["barebones","fleshed_out"]},"directionGate":{"default":false,"type":"boolean"},"sourceUrls":{"maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"mustInclude":{"maxItems":40,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"verify":{"type":"boolean"}},"required":["enabled","depth","directionGate"]},"positioning":{"default":{"enabled":false},"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"kernel":{"type":"object","properties":{"enabled":{"type":"boolean"},"family":{"default":null,"anyOf":[{"type":"string","enum":["Aspen","Birch","Alder","Hazel","Cedar","Balsam","Cypress","Myrtle"]},{"type":"null"}]},"words":{"default":null,"anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]}},"required":["enabled","family","words"]},"mention":{"default":{"degree":"off","rate":null},"type":"object","properties":{"degree":{"type":"string","enum":["off","subtle","woven"]},"rate":{"default":null,"anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"choice":{"type":"object","properties":{"fieldWeights":{"type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priorityTopics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"namePhraseBonus":{"type":"number","minimum":0,"maximum":20},"primaryBonus":{"type":"number","minimum":0,"maximum":20}}}},"required":["degree","rate"]},"verifier":{"default":{"enabled":false},"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"enhance":{"type":"object","properties":{"enabled":{"type":"boolean"},"flowId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"assignment":{"type":"string","enum":["auto","assigned"]},"reviewerIds":{"maxItems":50,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["enabled","flowId","assignment","reviewerIds"]},"templateKey":{"default":null,"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}]},"autoStartOnCurate":{"default":false,"type":"boolean"},"shred":{"default":{"enabled":false},"type":"object","properties":{"enabled":{"type":"boolean"},"endpoint_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["enabled"]},"relink":{"default":{"enabled":false},"type":"object","properties":{"enabled":{"type":"boolean"},"additionalSitemaps":{"anyOf":[{"maxItems":20,"type":"array","items":{"type":"string","format":"uri"}},{"type":"null"}]}},"required":["enabled"]},"voice":{"default":{"person":"third","authorName":null,"allowExperienceClaims":false},"type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"authorName":{"default":null,"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allowExperienceClaims":{"default":false,"type":"boolean"}},"required":["person","authorName","allowExperienceClaims"]},"visuals":{"type":"object","properties":{"enabled":{"type":"boolean"},"includeGenerative":{"type":"boolean"},"includeTables":{"type":"boolean"}},"required":["enabled"]},"externalLinks":{"type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"metadata":{"type":"object","properties":{"enabled":{"type":"boolean"},"jsonLd":{"type":"boolean"},"metaTags":{"type":"boolean"},"llmsTxt":{"type":"boolean"}},"required":["enabled"]},"sources":{"type":"object","properties":{"preferred":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"clientOneIn":{"type":"integer","minimum":2,"maximum":50}},"required":["preferred"]}},"required":["outline","positioning","kernel","mention","verifier","enhance","templateKey","autoStartOnCurate","shred","relink","voice"],"description":"The full resulting Auto-Pen configuration after the patch."}},"required":["collection_id","auto_pen_config"]}},{"name":"run_auto_pen","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles). It must already be in a collection."},"from_scratch":{"description":"Re-run from scratch: cancel whatever is driving the post and clear its body and cover first, so Research re-outlines from the headline. Without it, a run over a written post builds on the content it finds.","type":"boolean"},"skip_steps":{"description":"Steps to leave out of THIS run only (the app's Start checklist): outline, kernel, enhance, finish (visuals), internal_links, external_links, metadata. The collection's configuration is unchanged.","maxItems":7,"type":"array","items":{"type":"string","enum":["outline","kernel","enhance","finish","internal_links","external_links","metadata"]}},"source_urls":{"description":"Public URLs research reads first on this run, over the collection's own list. Up to 20.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"must_include":{"description":"Sections this run's outline must cover, over the collection's own list. Up to 40.","maxItems":40,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auto_pen_id":{"type":"string","description":"Poll target for get_auto_pen_status."},"status":{"type":"string","description":"Run status, e.g. running."},"steps":{"type":"array","items":{},"description":"Per-step progress records for the run."},"template_key":{"description":"The template the run picked up; null means the post will be written untemplated.","type":["string","null"]}},"required":["auto_pen_id","status","steps","template_key"]}},{"name":"get_auto_pen_status","description":"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 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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auto_pen":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"Run status column, e.g. running | done | failed | cancelled."},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"outline (Research) | kernel (Write) | enhance | finish (visuals) | internal_links | external_links | metadata"},"status":{"type":"string","description":"pending | running | awaiting_review | completed | skipped | failed"},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]},"startedAt":{"type":["string","null"]},"finishedAt":{"type":["string","null"]},"run":{"anyOf":[{"type":"object","properties":{"primitive":{"type":"string","enum":["arena","kernel","flow","enhance"]},"id":{"type":"string","description":"The run row: research_runs (arena), kernel_endpoint_runs (kernel), enhance_runs (enhance, read with get_enhance_run). A historical flow run (primitive flow) has no row any more: the Enhance flow tables were dropped."},"version":{"description":"The version that ran, as the primitive labels it (`Hazel@20`).","type":["string","null"]}},"required":["primitive","id","version"]},{"type":"null"}],"description":"The run this step made through another primitive's door; null for a pass with no run row."},"model":{"description":"The model the step wrote with, where the record says.","type":["string","null"]},"before":{"description":"article_history id of the post version the step started from.","type":["string","null"]},"after":{"description":"article_history id of the last post version the step wrote; null when it wrote none.","type":["string","null"]}},"required":["key","status","detail","warning","startedAt","finishedAt","run","model","before","after"],"additionalProperties":{}},"description":"Per-step progress and record, in chain order."},"error":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"research_run_id":{"description":"The Research step's run.","type":["string","null"]},"kernel_job_id":{"description":"The Write step's (last) kernel job.","type":["string","null"]},"kernel_endpoint_run_id":{"description":"That job's kernel endpoint run (get_kernel_endpoint_run).","type":["string","null"]},"shred_run_id":{"description":"The in-loop shred run, when the kernel shredded.","type":["string","null"]},"verification_run_id":{"type":["string","null"]},"arena_version":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":["string","null"]}},"required":["id","label"]},{"type":"null"}],"description":"The arena (research agent) version the Research step ran."},"kernel_version":{"description":"The kernel and version the Write step ran, `<name>@<version>`.","type":["string","null"]}},"required":["id","status","steps","error","created_at","updated_at","research_run_id","kernel_job_id","kernel_endpoint_run_id","shred_run_id","verification_run_id","arena_version","kernel_version"]},{"type":"null"}],"description":"The latest Auto-Pen run for the article with its chain record, or null if none has run."},"template_key":{"description":"Effective template key for the article (present only when a run exists).","type":["string","null"]}},"required":["auto_pen"]}},{"name":"cancel_auto_pen","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"cancelled":{"type":"boolean","const":true},"auto_pen_id":{"type":"string","description":"The run that was cancelled."}},"required":["cancelled","auto_pen_id"]},{"type":"object","properties":{"cancelled":{"type":"boolean","const":false},"reason":{"type":"string","description":"Why nothing was cancelled (e.g. no run in progress)."}},"required":["cancelled","reason"]}],"type":"object"}},{"name":"update_collection","description":"Update a collection in the caller's organization. Provide any of name, description, cadence_target, cadence_period, cover_canvas_type_id, or seo_market; omitted fields are left unchanged. seo_market is the country this collection's keyword demand is measured in (null inherits the org's). cadence_target + cadence_period set the target publishing pace (e.g. 3 per week). cover_canvas_type_id sets which Canvas type mints cover images for the collection's articles (null clears it). See list_canvas_types. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID"},"name":{"description":"New name","type":"string","minLength":1,"maxLength":200},"description":{"description":"New description (null to clear)","anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"cadence_target":{"description":"Target number of posts per cadence_period (null to clear)","anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":1000},{"type":"null"}]},"cadence_period":{"description":"Cadence window","type":"string","enum":["week","month"]},"cover_canvas_type_id":{"description":"Canvas type to use as the cover-image template for this collection's articles (null to clear). Must be one of the org's canvas types with kind editorial_image, line_art, full_ai, or semi_structured — see list_canvas_types.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"planner_config":{"description":"Full Planner (recurring content autopilot) config for this collection — the raw, granular path. Most callers want configure_collection instead, which builds this from a few high-level settings. Set enabled:true to turn the autopilot on; the acting user is taken from the API key. null clears the config.","anyOf":[{"type":"object","properties":{"enabled":{"type":"boolean"},"prompt":{"default":"","type":"string","maxLength":4000},"schedule":{"type":"object","properties":{"daysOfWeek":{"minItems":1,"maxItems":7,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"publishTime":{"type":"string","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string","minLength":1,"maxLength":64},"leadTimeHours":{"default":24,"type":"integer","minimum":1,"maximum":168},"jitter":{"type":"object","properties":{"postsPerWeek":{"type":"integer","minimum":1,"maximum":21},"varyCountBy":{"type":"integer","minimum":0,"maximum":3},"dayPool":{"minItems":1,"maxItems":7,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"timeJitterMinutes":{"type":"integer","minimum":0,"maximum":360},"window":{"type":"array","prefixItems":[{"type":"string"},{"type":"string"}],"items":false,"minItems":2,"maxItems":2},"tailWindow":{"type":"array","prefixItems":[{"type":"string"},{"type":"string"}],"items":false,"minItems":2,"maxItems":2},"tailChance":{"type":"number","minimum":0,"maximum":1}},"required":["postsPerWeek"]}},"required":["daysOfWeek","publishTime","timezone"]},"sourcing":{"default":{"useBacklog":true,"minConfidence":0.6,"avoidRecentDays":90},"type":"object","properties":{"useBacklog":{"default":true,"type":"boolean"},"minConfidence":{"default":0.6,"type":"number","minimum":0,"maximum":1},"avoidRecentDays":{"default":90,"type":"integer","minimum":0,"maximum":365}}},"writing":{"default":{"researchDepth":"fleshed_out","mentionDegree":"inherit"},"type":"object","properties":{"researchDepth":{"default":"fleshed_out","type":"string","enum":["inherit","barebones","fleshed_out"]},"mentionDegree":{"default":"inherit","type":"string","enum":["inherit","off","subtle","woven"]}}},"approval":{"type":"object","properties":{"mode":{"type":"string","enum":["autopilot","review_window","manual"]},"windowHours":{"default":24,"type":"integer","minimum":1,"maximum":336}},"required":["mode"]},"publishing":{"default":{"publishLocally":true,"publishToCms":false},"type":"object","properties":{"publishLocally":{"default":true,"type":"boolean"},"publishToCms":{"default":false,"type":"boolean"}}},"guardrails":{"default":{"maxAttemptsPerRun":2,"pauseOnFailureCount":3},"type":"object","properties":{"maxAttemptsPerRun":{"default":2,"type":"integer","minimum":1,"maximum":5},"pauseOnFailureCount":{"default":3,"type":"integer","minimum":0,"maximum":10}}},"curation":{"default":{"resolutionFloor":false,"nicheTier":null},"type":"object","properties":{"resolutionFloor":{"default":false,"type":"boolean"},"nicheTier":{"default":null,"anyOf":[{"type":"string","enum":["technical","ymyl","commercial"]},{"type":"null"}]},"comparisonShareCap":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]}}},"notifications":{"default":{"emails":[],"onReadyForReview":true,"onPublished":true,"onFailure":true},"type":"object","properties":{"emails":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"onReadyForReview":{"default":true,"type":"boolean"},"onPublished":{"default":true,"type":"boolean"},"onFailure":{"default":true,"type":"boolean"}}},"actorUserId":{"default":null,"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"syncCadenceGoal":{"default":true,"type":"boolean"},"pausedReason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}]}},"required":["enabled","schedule"]},{"type":"null"}]},"seo_market":{"description":"ISO 3166-1 alpha-2 code of the search market THIS collection's keyword demand is measured in, e.g. 'AU'. Volume and difficulty are per-country and not comparable across them, so this decides what discovery finds for these pillars and what the numbers under a headline mean. null inherits the organization's market (set that with set_company_info). Changing it re-prices this collection's keywords on the next sync.","anyOf":[{"type":"string","pattern":"^[A-Z]{2}$"},{"type":"null"}]}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"cadence_target":{"type":["number","null"]},"cadence_period":{"type":["string","null"]},"cover_canvas_type_id":{"description":"Canvas type that mints cover images, or null.","type":["string","null"]},"planner_enabled":{"description":"Whether the collection's Planner autopilot is on after this update (null if the collection has no Planner config).","type":["boolean","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["collection_id","name","description","cadence_target","cadence_period","cover_canvas_type_id","planner_enabled","created_at","updated_at"]}},{"name":"configure_collection","description":"Turn on (or reconfigure) a collection's Planner — the recurring content autopilot that sources headlines, drafts them, runs Auto-Pen (Research → Write → Enhance), and publishes on a cadence. One call sets cadence, approval posture, and an optional steering prompt, then materializes the upcoming publish slots. On a collection with automations (list_automations), the cadence is their weekly total and the next automation change resets it: change paces with update_automation instead. On a collection with no Planner config yet, enabled defaults to true and cadence to 3/week; otherwise every knob you don't set is left as it was — read it with get_collection_config first. Approval defaults to manual on a client blog; pass approval_mode 'autopilot' only for a blog you're comfortable publishing to with nobody in the loop. `decisions` says, per knob, whether the value came from you, the saved config, or the default. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID to turn the Planner (recurring autopilot) on for"},"enabled":{"description":"Turn the Planner on (true) or configure it while leaving it off (false). Omit to keep the saved state; on a collection with no Planner config yet an omitted value means true.","type":"boolean"},"cadence_per_week":{"description":"Publishing cadence — posts per week. Days and times are de-robotized automatically. Omit to keep the saved cadence; on a collection with no Planner config yet an omitted value means 3.","type":"integer","minimum":1,"maximum":7},"approval_mode":{"description":"How pieces are approved before publishing: 'manual' (default for a client collection — drafts wait for a human), 'review_window' (auto-publish after a hold window), or 'autopilot' (fully unattended — only for a blog you're comfortable publishing to with nobody in the loop). Omit for the safe per-collection default.","type":"string","enum":["autopilot","review_window","manual"]},"steering_prompt":{"description":"Optional steering prompt biasing what the blog covers (e.g. 'a comprehensive blog on X and Y for CTOs'). Omit to leave the current prompt unchanged.","type":"string","maxLength":4000}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"planner_enabled":{"type":"boolean","description":"Whether the Planner autopilot is on after this call."},"cadence_per_week":{"type":"number","description":"Posts-per-week cadence the Planner will publish at."},"approval_mode":{"type":"string","description":"Approval posture (autopilot | review_window | manual)."},"slots_scheduled":{"type":"number","description":"Upcoming publish slots materialized for this collection by this call."},"decisions":{"type":"object","properties":{"enabled":{"type":"object","properties":{"value":{"type":"boolean"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]},"cadence_per_week":{"type":"object","properties":{"value":{"type":"number"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]},"approval_mode":{"type":"object","properties":{"value":{"type":"string"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]}},"required":["enabled","cadence_per_week","approval_mode"],"description":"Per knob, where the applied value came from: 'caller' (you set it in this call), 'existing' (kept from the saved config), or 'default' (no config existed; the kind-aware default). Nothing here is silent."},"note":{"type":"string","description":"Human-readable summary of what was configured and any follow-up."}},"required":["collection_id","planner_enabled","cadence_per_week","approval_mode","slots_scheduled","decisions","note"]}},{"name":"activate_collection","description":"The review is done — turn the collection on. Builds the topic map from the collection's current content areas, seeds a first headline batch from it, then starts the Planner at cadence_per_week/approval_mode with the queue filled — the exact activation the self-serve Guided review step runs on Continue, callable by an integration with its own consent flow. Use after onboard_collection finished (or after adjusting areas with create_cluster/delete_cluster) when you have the user's go-ahead on cadence and approval; omit both to keep the saved values. Returns activated:false with a reason and remedies when the collection has no content areas to build from (set_positioning / create_cluster / re-run onboard_collection) — a precondition, not an error. To reconfigure an already-running Planner without regenerating anything, use configure_collection. Needs a user-scoped key (an acting user for attribution). Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection to activate (from list_collections / onboard_collection)."},"cadence_per_week":{"description":"Posts per week. Omit to keep the collection's saved cadence (3 when nothing is saved yet).","type":"integer","minimum":1,"maximum":7},"approval_mode":{"description":"How pieces are approved before publishing. Omit for the saved posture (manual for a client blog — drafts wait for a human). Pass 'autopilot' only with the user's explicit go-ahead.","type":"string","enum":["autopilot","review_window","manual"]},"regenerate_topic_map":{"description":"Rebuild the topic map from the collection's current content areas before activating (default true). Pass false to keep the existing map — e.g. when nothing changed in review.","type":"boolean"},"stock_headlines":{"description":"Seed a first batch of headline suggestions from the map before activating (default true).","type":"boolean"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"activated":{"type":"boolean","const":true},"collection_id":{"type":"string"},"planner_enabled":{"type":"boolean","const":true},"cadence_per_week":{"type":"number"},"approval_mode":{"type":"string"},"topic_map":{"anyOf":[{"type":"object","properties":{"pillars":{"type":"number"},"spokes":{"type":"number"}},"required":["pillars","spokes"]},{"type":"null"}],"description":"Counts after generation; null when regenerate_topic_map was false."},"headlines_seeded":{"type":"number","description":"Headline suggestions seeded by this call."},"slots_scheduled":{"type":"number"},"slots_filled":{"description":"Queue slots filled from the fresh headlines.","type":["number","null"]},"decisions":{"type":"object","properties":{"enabled":{"type":"object","properties":{"value":{"type":"boolean"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]},"cadence_per_week":{"type":"object","properties":{"value":{"type":"number"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]},"approval_mode":{"type":"object","properties":{"value":{"type":"string"},"source":{"type":"string","enum":["caller","existing","default"],"description":"caller: set in this call; existing: kept from the saved config; default: no config yet."},"why":{"type":"string","description":"One line the caller can disagree with."}},"required":["value","source","why"]}},"required":["enabled","cadence_per_week","approval_mode"]},"warnings":{"type":"array","items":{"type":"string"},"description":"Best-effort steps that failed; the Planner is still on."},"note":{"type":"string"}},"required":["activated","collection_id","planner_enabled","cadence_per_week","approval_mode","topic_map","headlines_seeded","slots_scheduled","slots_filled","decisions","warnings","note"]},{"type":"object","properties":{"activated":{"type":"boolean","const":false},"collection_id":{"type":"string"},"reason":{"type":"string","description":"Why nothing was activated — the collection has no content areas to build from."},"remedies":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"why":{"type":"string"}},"required":["tool","why"]},"description":"Tools that clear the precondition; call one, then activate again."}},"required":["activated","collection_id","reason","remedies"]}],"type":"object"}},{"name":"regenerate_covers","description":"Enqueue a durable cover-regeneration job for a collection and return immediately (covers are minted one at a time, ~45-60s each, so a full sweep can take hours). 'regenerate' (default) re-mints every published post's cover with the collection's current template; 'missing' only mints posts that have no cover yet; 'stock' only sets covers where a stock photo clears the ranker's bar, leaving every other post untouched. At most one job runs per collection at a time: calling this while one is already running returns that job's id instead of starting a second. Poll get_cover_regen_status until status is 'done' (or 'failed'). Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose posts' covers to regenerate"},"mode":{"description":"'regenerate' re-mints every published post's cover with the collection's current template; 'missing' only mints posts that have no cover yet; 'stock' only replaces a cover where a stock photo clears the ranker's bar and leaves every other post untouched — the safe way to add photography to a back catalogue (default 'regenerate')","type":"string","enum":["regenerate","missing","stock"]}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string"},"already_running":{"type":"boolean","description":"True if an existing job was returned instead of starting a new one."},"total":{"description":"Total posts the sweep will process, when known.","type":["number","null"]}},"required":["job_id","already_running","total"]}},{"name":"get_cover_regen_status","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"job":{"type":"null"}},"required":["job"]},{"type":"object","properties":{"job":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"running / done / failed."},"stage":{"type":["string","null"]},"mode":{"type":"string","description":"regenerate / missing / stock."},"total":{"type":["number","null"]},"generated":{"type":"number"},"skipped":{"type":"number"},"failed":{"type":"number"},"fallback_generated":{"type":"number"},"remaining":{"type":"number","description":"Count of article ids still pending."},"failed_ids":{"type":"array","items":{"type":"string"}},"error":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["id","status","stage","mode","total","generated","skipped","failed","fallback_generated","remaining","failed_ids","error","updated_at"]}},"required":["job"]}],"type":"object"}},{"name":"list_collection_authors","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose author bank to list"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"authors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string","description":"Collection-unique slug; the public author-page key."},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"},"description":"Topic areas the author covers."},"started_at":{"description":"YYYY-MM-DD; may predate the site.","type":["string","null"]},"active":{"type":"boolean"},"sort_order":{"type":"number","description":"Masthead ordering position."},"created_at":{"type":"string"},"updated_at":{"type":"string"},"post_count":{"type":"number","description":"Posts currently bylined by this author."}},"required":["id","org_id","collection_id","name","slug","bio","role","expertise","started_at","active","sort_order","created_at","updated_at","post_count"]}},"unlinked_posts":{"type":"number","description":"Posts with no bank-linked byline."},"total_posts":{"type":"number"}},"required":["authors","unlinked_posts","total_posts"]}},{"name":"generate_collection_authors","description":"Generate a bank of believable recurring authors for a collection using its name/description and topic map, and add them as active authors. Names only by default; pass with_bios for a one-line bio and masthead role per author. Idempotent by name. Use this to give a phantom site real bylines instead of a fresh throwaway author per post. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID to generate authors for"},"count":{"description":"How many recurring authors to generate (default 4)","type":"integer","minimum":1,"maximum":8},"with_bios":{"description":"Also write a one-line bio and a masthead role for each author. Default false: authors are names only, and the site credits them by name alone.","type":"boolean"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created_count":{"type":"number"},"authors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string","description":"Collection-unique slug; the public author-page key."},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"},"description":"Topic areas the author covers."},"started_at":{"description":"YYYY-MM-DD; may predate the site.","type":["string","null"]},"active":{"type":"boolean"},"sort_order":{"type":"number","description":"Masthead ordering position."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","name","slug","bio","role","expertise","started_at","active","sort_order","created_at","updated_at"]},"description":"Newly inserted authors (excludes already-existing names)."}},"required":["created_count","authors"]}},{"name":"upsert_collection_author","description":"Create or edit one author in a collection's bank. Provide author_id to edit (omitted fields unchanged) or omit it to create (name required). Fields: name, bio, role, expertise (topic areas), started_at (YYYY-MM-DD, may predate the site), active. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID the author belongs to"},"author_id":{"description":"Author UUID to update; omit to create a new author","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"description":"Author display name / byline","type":"string","minLength":1,"maxLength":120},"bio":{"description":"Short 'about the author' blurb","type":"string","maxLength":1000},"role":{"description":"Masthead role (null to clear)","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"expertise":{"description":"Topic areas the author covers (soft assignment affinity)","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":80}},"started_at":{"description":"Writing-since date YYYY-MM-DD (may predate the site; null to clear)","anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}]},"active":{"description":"Whether eligible for new auto-assignments","type":"boolean"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string","description":"Collection-unique slug; the public author-page key."},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"},"description":"Topic areas the author covers."},"started_at":{"description":"YYYY-MM-DD; may predate the site.","type":["string","null"]},"active":{"type":"boolean"},"sort_order":{"type":"number","description":"Masthead ordering position."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","name","slug","bio","role","expertise","started_at","active","sort_order","created_at","updated_at"]}},{"name":"delete_collection_author","description":"Remove an author from a collection's bank. Posts already bylined by them keep their byline until reassigned with assign_collection_authors. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID the author belongs to"},"author_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Author UUID to delete"}},"required":["collection_id","author_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"author_id":{"type":"string"}},"required":["deleted","author_id"]}},{"name":"assign_collection_authors","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose posts to re-byline"},"mode":{"type":"string","enum":["set","auto"],"description":"'set' pins one author to all targets; 'auto' distributes across active authors by topic"},"author_id":{"description":"Target author UUID (required when mode='set')","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"article_ids":{"description":"Posts to reassign; omit (or pass all=true) to reassign every post in the collection","type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"all":{"description":"Reassign every post in the collection","type":"boolean"},"rebuild":{"description":"Trigger a rebuild of the collection's live phantom sites after reassigning (default true)","type":"boolean"}},"required":["collection_id","mode"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"updated":{"type":"number","description":"Posts re-bylined."},"skipped":{"type":"number","description":"Posts left unchanged (no author could be picked)."},"rebuilt":{"type":"boolean","description":"True if the live phantom sites were rebuilt after the swap."}},"required":["updated","skipped","rebuilt"]}},{"name":"assign_article_to_collection","description":"Move an article into a collection (or between collections) within the caller's organization. Both the article and the target collection must belong to the caller's org. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Target collection UUID"}},"required":["article_id","collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"collection_id":{"type":["string","null"]}},"required":["article_id","collection_id"]}},{"name":"get_article","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cover_image":{"description":"Cover image URL, flat (also in metadata.cover_image).","type":["string","null"]},"cover_image_alt":{"description":"Alt text for the cover; null when there is no cover.","type":["string","null"]},"cover_image_credit":{"anyOf":[{"type":"object","properties":{"source":{"type":"string"},"photographer":{"type":["string","null"]},"photographerUrl":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"license":{"type":"string"},"required":{"type":"boolean","description":"Whether the licence requires the credit be shown."}},"required":["source","photographer","photographerUrl","sourceUrl","license","required"]},{"type":"null"}],"description":"Photo credit for a stock-photo cover; null for generated/uploaded covers."},"author":{"description":"Byline, flat (also in metadata.author).","type":["string","null"]},"author_slug":{"description":"Bank author page key; null for legacy free-form bylines.","type":["string","null"]},"author_profile":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"}},"started_at":{"type":["string","null"]}},"required":["name","slug","bio","role","expertise","started_at"]},{"type":"null"}],"description":"Masthead profile of a bank author; null for legacy bylines."},"summary":{"description":"The dek (also in metadata.summary).","type":["string","null"]},"tags":{"type":"array","items":{"type":"string"},"description":"Flattened from metadata.tags."},"custom_fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"This collection's declared custom fields (set_collection_config's custom_fields section), keyed by field key. Only enabled, already-generated fields appear; a field with no value yet is simply absent."},"sponsor_disclosure":{"anyOf":[{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where to disclose: article_header (`label` at the top of the article, beside the byline), article_footer (`text` after the body), cover_badge (`label` set over the cover by the site), cover_baked (`label` drawn into the image itself; `cover_image` is then that rendered image), structured_data (schema.org `sponsor` on the article JSON-LD), sponsored_links (rel=\"sponsored\" on body links to the sponsor's domain), meta_description (`label` prefixed to the meta and Open Graph descriptions), site_statement (`site_text` on every page, the About page, llms.txt and RSS), site_byline (the byline is the site's editors; author fields are served null)."},"sponsor":{"type":"object","properties":{"name":{"type":"string"},"url":{"description":"The sponsor's site, for JSON-LD and sponsored_links; null when unknown.","type":["string","null"]}},"required":["name","url"]},"text":{"type":"string","description":"The article sentence, verbatim: 'This article is sponsored by …'."},"label":{"type":"string","description":"The short label, verbatim: 'Sponsored by …'."},"site_text":{"type":"string","description":"The site-wide sentence, verbatim: 'This publication is sponsored by …'."}},"required":["surfaces","sponsor","text","label","site_text"]},{"type":"null"}],"description":"The publication's sponsor disclosure (set_collection_config's disclosure section). Presentation only: never part of `content`. null = disclose nothing."},"article_id":{"type":"string"},"title":{"type":"string"},"content":{"description":"Current (draft) article body.","type":["string","null"]},"status":{"type":"string"},"collection_id":{"type":["string","null"]},"updated_at":{"type":"string"},"published_version":{"description":"The article_history version currently published, or null if never published (or unpublished).","type":["number","null"]},"published_at":{"type":["string","null"]},"draft_ahead_of_published":{"type":"boolean","description":"True when the live title/content differs from the published_version snapshot — publishing now would freeze different content than what's live. Always false when published_version is null."},"metadata":{"description":"The raw article metadata blob ({} when absent)."},"lifecycle_phase":{"type":"string","description":"queued · writing · editing · in_review · finished · published (published = live)."},"state":{"type":"object","properties":{"phase":{"type":"string","enum":["empty","writing","ready","scheduled","published"]},"sentence":{"type":"string","description":"Where the post is, in one sentence a person reads."},"primary":{"anyOf":[{"type":"string","enum":["write","publish","publish_now","update_live"]},{"type":"null"}],"description":"The one thing to do next: write it (run_auto_pen), publish (publish_article), publish now instead of on its day, or publish the draft's changes over the live copy. null when nothing pressed would help: a tool is writing, a reviewer or a hold stands in the way, or it is live and current."},"waitingOn":{"type":"array","items":{"type":"string"},"description":"What it is waiting for: the writing tool, reviewers by name, a hold."}},"required":["phase","sentence","primary","waitingOn"],"description":"Where the post is and what to do next (lib/posts/state.ts postState)."}},"required":["cover_image","cover_image_alt","cover_image_credit","author","author_slug","author_profile","summary","tags","custom_fields","sponsor_disclosure","article_id","title","content","status","collection_id","updated_at","published_version","published_at","draft_ahead_of_published","metadata","lifecycle_phase","state"]}},{"name":"publish_article","description":"Publish an article now: pin its current content as the live published version (list_published/get_published and the public published API) and rebuild the collection's live phantom sites. It does NOT push to the collection's CMS (Webflow, Framer): for a CMS-wired collection use schedule_article_publish, whose run pushes to the CMS. Blocks if the article has un-approved reviewers, is held for accuracy review, or 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. Requires capability article:publish.","capability":"article:publish","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles or ingest_article)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"published_version":{"description":"Version number pinned as the live published copy, or null if the publish RPC returned no version.","type":["number","null"]},"published_at":{"description":"Timestamp the version was published, or null if unavailable.","type":["string","null"]}},"required":["article_id","published_version","published_at"]}},{"name":"schedule_article_publish","description":"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. Cadence never limits it: any number of written posts can be given days, several on one day included. Requires capability article:publish.","capability":"article:publish","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles); must be finished and not yet published"},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar day to publish on (YYYY-MM-DD). The post goes live that day at the collection's publish time, in its timezone (get_collection_config's cadence). Must be in the future."}},"required":["article_id","date"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"schedule":{"type":"object","properties":{"run_id":{"type":"string","description":"The collection run that publishes it (get_collection_run / approve_collection_run)."},"article_id":{"type":"string"},"scheduled_for":{"type":"string","description":"UTC instant the post goes live."},"timezone":{"type":"string","description":"The collection's publish timezone the day was read in."}},"required":["run_id","article_id","scheduled_for","timezone"]}},"required":["schedule"]}},{"name":"unpublish_article","description":"Unpublish an article: clear its published pointer so list_published/get_published and the public published API stop serving it. It does not rebuild phantom sites (the post stays on a live phantom until its next rebuild) and does not remove a copy already pushed to the client's CMS. Content and version history are untouched: the article returns to a draft state. Idempotent. Requires capability article:publish.","capability":"article:publish","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_published or list_articles)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"unpublished":{"type":"boolean","const":true}},"required":["article_id","unpublished"]}},{"name":"delete_article","description":"Permanently delete one or more articles from the caller's organization: content, version history and the published version are removed from Letter and this cannot be undone. It does not rebuild phantom sites (a live phantom keeps the post until its next rebuild) or delete a copy pushed to the client's CMS. Pass 1-100 article_ids. Returns which ids were deleted vs not found (not in this org, or already deleted) rather than erroring on a partial match. Use unpublish_article to stop serving a post without losing the draft — that's reversible, this isn't. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_ids":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"Article UUID(s) to permanently delete (1-100), from list_articles or a prior ingest_article call. Deletion is permanent: content, version history, and any published/live copy are removed. Unpublish first if you only want to stop serving it — that's reversible, this is not."}},"required":["article_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted_ids":{"type":"array","items":{"type":"string"},"description":"Article UUIDs actually deleted."},"not_found_ids":{"type":"array","items":{"type":"string"},"description":"Requested UUIDs that were not deleted — not in this org, or already deleted."}},"required":["deleted_ids","not_found_ids"]}},{"name":"update_article","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"title":{"type":"string","minLength":1,"maxLength":500},"content":{"description":"The post's body, as HTML.","type":"string","minLength":1},"status":{"description":"The post's legacy status label. Setting 'published' does NOT publish: nothing goes live and no gate runs. Publish with publish_article or schedule_article_publish; lifecycle_phase and published_version say whether a post is live.","type":"string","enum":["draft","published","archived"]},"metadata":{"description":"Replace the WHOLE metadata object. Almost never what you want: use metadataPatch to set a few keys.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"metadataPatch":{"description":"Set these metadata keys and leave the rest (an atomic shallow merge; null deletes a key).","type":"object","properties":{"summary":{"description":"The dek: the one-sentence subheadline under the title.","type":["string","null"]},"tags":{"description":"Free-form tags.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"owners":{"description":"User ids of the post's owners.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"due_date":{"description":"When the post is due (YYYY-MM-DD).","type":["string","null"]},"author":{"description":"The byline as typed. Clear author_id for a typed byline to win.","type":["string","null"]},"author_id":{"description":"A collection author's id (list_collection_authors).","type":["string","null"]},"slug":{"description":"URL slug; defaults to the slugified title.","type":["string","null"]},"meta_title":{"description":"SEO title; defaults to the title.","type":["string","null"]},"meta_description":{"description":"SEO description; defaults to the dek.","type":["string","null"]},"published_url":{"description":"Where the post lives on the client's site.","type":["string","null"]},"template_key":{"description":"This post's content template, over the collection default. null = inherit the collection's; 'none' = no template.","type":["string","null"]},"mention_degree":{"description":"This post's brand-mention strength, over the collection's. null = inherit.","anyOf":[{"type":"string","enum":["off","subtle","woven"]},{"type":"null"}]},"direction_gate":{"description":"Stop for a research direction on this post, over the collection's setting. null = inherit.","type":["boolean","null"]},"paper_trail":{"description":"The Paper Trail: { candidates, published, audited_at } (get_article metadata.paper_trail).","anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"additionalProperties":{}}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string","description":"The post's body, as HTML."},"status":{"type":"string"},"lifecycle_phase":{"type":"string","description":"queued · writing · editing · in_review · finished · published"},"metadata":{"description":"The article metadata blob (dek under summary, tags, slug, …)."},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]}},"required":["id","org_id","collection_id","title","content","status","lifecycle_phase","metadata","published_version","published_at","creator_id","created_at","updated_at","creator"],"additionalProperties":{}}},"required":["article"]}},{"name":"edit_article_with_prompt","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"instruction":{"type":"string","minLength":1,"maxLength":2000,"description":"What to change, in plain words (e.g. 'shorten the intro', 'make the tone warmer')."}},"required":["article_id","instruction"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string","description":"The post's body, as HTML."},"status":{"type":"string"},"lifecycle_phase":{"type":"string","description":"queued · writing · editing · in_review · finished · published"},"metadata":{"description":"The article metadata blob (dek under summary, tags, slug, …)."},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]}},"required":["id","org_id","collection_id","title","content","status","lifecycle_phase","metadata","published_version","published_at","creator_id","created_at","updated_at","creator"],"additionalProperties":{}}},"required":["article"]}},{"name":"run_mention_pass","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"detail":{"type":"string"},"warning":{"type":["string","null"]},"changed":{"type":"boolean","description":"True when the post's text changed."}},"required":["article_id","detail","warning","changed"]}},{"name":"get_article_history","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"history":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"version":{"type":"number"},"title":{"type":"string"},"content":{"type":"string"},"source":{"type":"string","description":"What made it: edit, a flow or Auto-Pen writeback, …"},"updated_by":{"description":"The person whose edit it was; null for an unattended write.","type":["string","null"]},"created_at":{"type":"string"},"updated_by_user":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]}},"required":["id","article_id","version","title","content","source","updated_by","created_at","updated_by_user"],"additionalProperties":{}},"description":"Newest first."},"total_versions":{"type":"number"},"published_version":{"description":"The version that is live, or null.","type":["number","null"]}},"required":["history","total_versions","published_version"]}},{"name":"restore_article_version","description":"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). Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"version":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"The version to restore (get_article_history). It comes back as a new latest version."}},"required":["article_id","version"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string","description":"The post's body, as HTML."},"status":{"type":"string"},"lifecycle_phase":{"type":"string","description":"queued · writing · editing · in_review · finished · published"},"metadata":{"description":"The article metadata blob (dek under summary, tags, slug, …)."},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]}},"required":["id","org_id","collection_id","title","content","status","lifecycle_phase","metadata","published_version","published_at","creator_id","created_at","updated_at","creator"],"additionalProperties":{},"description":"The post, the restored version now its latest."}},"required":["article"]}},{"name":"export_article","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"format":{"type":"string","enum":["md","pdf"],"description":"md = Markdown with YAML frontmatter, as text; pdf = the rendered PDF, base64."},"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["format","article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"format":{"type":"string","enum":["md","pdf"]},"filename":{"type":"string"},"content_type":{"type":"string"},"encoding":{"type":"string","enum":["utf8","base64"],"description":"utf8 for md (content is the text); base64 for pdf."},"content":{"type":"string"}},"required":["article_id","format","filename","content_type","encoding","content"]}},{"name":"list_article_comments","description":"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. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"comments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"parent_id":{"description":"The top-level comment this replies to, or null.","type":["string","null"]},"creator_id":{"description":"Set for a member's comment.","type":["string","null"]},"guest_access_id":{"description":"Set for a guest reviewer's comment.","type":["string","null"]},"body":{"type":"string"},"kind":{"type":"string","description":"note, or ai_esque (the quoted passage reads like AI)."},"quote":{"description":"The text the comment is anchored to.","type":["string","null"]},"quote_prefix":{"type":["string","null"]},"quote_suffix":{"type":["string","null"]},"mentioned_user_ids":{"type":"array","items":{"type":"string"}},"resolved":{"type":"boolean"},"resolved_by":{"type":["string","null"]},"resolved_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"author":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}],"description":"The member who wrote it."},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","parent_id","creator_id","guest_access_id","body","kind","quote","quote_prefix","quote_suffix","mentioned_user_ids","resolved","resolved_by","resolved_at","created_at","updated_at","author","guest"],"additionalProperties":{}},"description":"Top-level comments and replies, oldest first."}},"required":["comments"]}},{"name":"add_article_comment","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":10000,"description":"The comment."},"kind":{"description":"note (default) = an ordinary comment; ai_esque = the quoted passage reads like AI. An ai_esque comment is also sent to the writing kernel as feedback.","type":"string","enum":["note","ai_esque"]},"scope":{"description":"For ai_esque: sentence (default) = about the quoted passage; document = about the whole post.","type":"string","enum":["sentence","document"]},"parent_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Reply to this top-level comment (list_article_comments). Replies are one level deep."},"quote":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"description":"The exact text the comment is anchored to, as it reads in the post. Needed for resolve_comment_with_ai."},"quote_prefix":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"A little of the text just before the quote, to find it again if it appears twice."},"quote_suffix":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"A little of the text just after the quote."},"mentioned_user_ids":{"description":"Teammates to @mention (user ids).","maxItems":50,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["body","article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"comment":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"parent_id":{"description":"The top-level comment this replies to, or null.","type":["string","null"]},"creator_id":{"description":"Set for a member's comment.","type":["string","null"]},"guest_access_id":{"description":"Set for a guest reviewer's comment.","type":["string","null"]},"body":{"type":"string"},"kind":{"type":"string","description":"note, or ai_esque (the quoted passage reads like AI)."},"quote":{"description":"The text the comment is anchored to.","type":["string","null"]},"quote_prefix":{"type":["string","null"]},"quote_suffix":{"type":["string","null"]},"mentioned_user_ids":{"type":"array","items":{"type":"string"}},"resolved":{"type":"boolean"},"resolved_by":{"type":["string","null"]},"resolved_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"author":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}],"description":"The member who wrote it."},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","parent_id","creator_id","guest_access_id","body","kind","quote","quote_prefix","quote_suffix","mentioned_user_ids","resolved","resolved_by","resolved_at","created_at","updated_at","author","guest"],"additionalProperties":{}}},"required":["comment"]}},{"name":"update_article_comment","description":"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). Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"comment_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Comment UUID (list_article_comments)"},"body":{"description":"New text. Only the comment's author can edit it.","type":"string","minLength":1,"maxLength":10000},"resolved":{"description":"true resolves the thread, false reopens it. Any member can.","type":"boolean"},"mentioned_user_ids":{"description":"Replace the @mentions (author only).","maxItems":50,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["article_id","comment_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"comment":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"parent_id":{"description":"The top-level comment this replies to, or null.","type":["string","null"]},"creator_id":{"description":"Set for a member's comment.","type":["string","null"]},"guest_access_id":{"description":"Set for a guest reviewer's comment.","type":["string","null"]},"body":{"type":"string"},"kind":{"type":"string","description":"note, or ai_esque (the quoted passage reads like AI)."},"quote":{"description":"The text the comment is anchored to.","type":["string","null"]},"quote_prefix":{"type":["string","null"]},"quote_suffix":{"type":["string","null"]},"mentioned_user_ids":{"type":"array","items":{"type":"string"}},"resolved":{"type":"boolean"},"resolved_by":{"type":["string","null"]},"resolved_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"author":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}],"description":"The member who wrote it."},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","parent_id","creator_id","guest_access_id","body","kind","quote","quote_prefix","quote_suffix","mentioned_user_ids","resolved","resolved_by","resolved_at","created_at","updated_at","author","guest"],"additionalProperties":{}}},"required":["comment"]}},{"name":"delete_article_comment","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"comment_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Comment UUID (list_article_comments)"}},"required":["article_id","comment_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"comment_id":{"type":"string"}},"required":["deleted","comment_id"]}},{"name":"resolve_comment_with_ai","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"comment_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Comment UUID (list_article_comments)"}},"required":["article_id","comment_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string","description":"The post's body, as HTML."},"status":{"type":"string"},"lifecycle_phase":{"type":"string","description":"queued · writing · editing · in_review · finished · published"},"metadata":{"description":"The article metadata blob (dek under summary, tags, slug, …)."},"published_version":{"type":["number","null"]},"published_at":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]}},"required":["id","org_id","collection_id","title","content","status","lifecycle_phase","metadata","published_version","published_at","creator_id","created_at","updated_at","creator"],"additionalProperties":{},"description":"The post with the passage revised."},"comment":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"parent_id":{"description":"The top-level comment this replies to, or null.","type":["string","null"]},"creator_id":{"description":"Set for a member's comment.","type":["string","null"]},"guest_access_id":{"description":"Set for a guest reviewer's comment.","type":["string","null"]},"body":{"type":"string"},"kind":{"type":"string","description":"note, or ai_esque (the quoted passage reads like AI)."},"quote":{"description":"The text the comment is anchored to.","type":["string","null"]},"quote_prefix":{"type":["string","null"]},"quote_suffix":{"type":["string","null"]},"mentioned_user_ids":{"type":"array","items":{"type":"string"}},"resolved":{"type":"boolean"},"resolved_by":{"type":["string","null"]},"resolved_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"author":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}],"description":"The member who wrote it."},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","parent_id","creator_id","guest_access_id","body","kind","quote","quote_prefix","quote_suffix","mentioned_user_ids","resolved","resolved_by","resolved_at","created_at","updated_at","author","guest"],"additionalProperties":{},"description":"The thread, now resolved."}},"required":["article","comment"]}},{"name":"list_article_reviewers","description":"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. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reviews":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"reviewer_id":{"description":"Set for org-member reviewers.","type":["string","null"]},"guest_access_id":{"description":"Set for guest reviewers.","type":["string","null"]},"requested_by":{"type":["string","null"]},"status":{"type":"string","enum":["pending","approved","changes_requested"]},"body":{"description":"Optional summary note with the verdict.","type":["string","null"]},"submitted_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reviewer":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","reviewer_id","guest_access_id","requested_by","status","body","submitted_at","created_at","updated_at","reviewer","guest"]}},"summary":{"type":"object","properties":{"total":{"type":"number"},"approved":{"type":"number"},"all_approved":{"type":"boolean","description":"True only when there is >=1 reviewer and all have approved."},"pending":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},"description":"Reviewers who have not yet approved (pending or changes_requested)."}},"required":["total","approved","all_approved","pending"]}},"required":["reviews","summary"]}},{"name":"add_article_reviewer","description":"Add a teammate as a reviewer on an article (reviewer_id from list_eligible_reviewers: members on the org's email domain). No notification is sent: the reviewer sees the request in-app. For someone outside the org, invite_guest_reviewer. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"},"reviewer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"User UUID of the teammate to add (from list_eligible_reviewers; other members are refused)."}},"required":["article_id","reviewer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"review":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"reviewer_id":{"description":"Set for org-member reviewers.","type":["string","null"]},"guest_access_id":{"description":"Set for guest reviewers.","type":["string","null"]},"requested_by":{"type":["string","null"]},"status":{"type":"string","enum":["pending","approved","changes_requested"]},"body":{"description":"Optional summary note with the verdict.","type":["string","null"]},"submitted_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reviewer":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","reviewer_id","guest_access_id","requested_by","status","body","submitted_at","created_at","updated_at","reviewer","guest"]}},"required":["review"]}},{"name":"remove_article_reviewer","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Review UUID (from list_article_reviewers)"}},"required":["review_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"review_id":{"type":"string"},"article_id":{"type":"string"}},"required":["deleted","review_id","article_id"]}},{"name":"submit_review","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Review UUID (from list_article_reviewers) for YOUR OWN review row — reviewer_id must match the calling API key's creator, same as the UI's 'only the assigned reviewer' rule."},"status":{"type":"string","enum":["approved","changes_requested"],"description":"Your verdict."},"body":{"description":"An optional note explaining the verdict.","type":"string","maxLength":10000}},"required":["review_id","status"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"review":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":"string"},"org_id":{"type":"string"},"reviewer_id":{"description":"Set for org-member reviewers.","type":["string","null"]},"guest_access_id":{"description":"Set for guest reviewers.","type":["string","null"]},"requested_by":{"type":["string","null"]},"status":{"type":"string","enum":["pending","approved","changes_requested"]},"body":{"description":"Optional summary note with the verdict.","type":["string","null"]},"submitted_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reviewer":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}]},"guest":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"]},{"type":"null"}]}},"required":["id","article_id","org_id","reviewer_id","guest_access_id","requested_by","status","body","submitted_at","created_at","updated_at","reviewer","guest"]}},"required":["review"]}},{"name":"revoke_guest_reviewer","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"access_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Guest reviewer access UUID (the guest entry's id field from list_article_reviewers)"}},"required":["access_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"revoked":{"type":"boolean","const":true},"access_id":{"type":"string"},"article_id":{"type":"string"}},"required":["revoked","access_id","article_id"]}},{"name":"invite_guest_reviewer","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"email":{"type":"string","maxLength":255,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The guest's email. They get a link and a 6-digit PIN by email; no account needed."},"name":{"description":"The guest's name, for the invite and the Reviews list.","type":"string","maxLength":120},"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID"}},"required":["email","article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"access":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","email","name"],"description":"The guest's access; its id is what revoke_guest_reviewer takes."},"review_link":{"type":"string","description":"The guest's review page. It asks for the PIN."},"email_sent":{"type":"boolean"},"pin":{"description":"Only when the email could not be sent, so you can pass it on yourself.","type":"string"}},"required":["access","review_link","email_sent"]}},{"name":"list_standing_reviewers","description":"List the caller's organization's standing reviewers — external contacts auto-invited as a guest reviewer when an article becomes a finished, never-published draft with no reviewer yet. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","email","name","created_at"]}}},"required":["items"]}},{"name":"add_standing_reviewer","description":"Add a standing reviewer to the caller's organization. Writes only a row — no email now; within about 5 minutes of an article becoming a finished, never-published draft, a cron invites this contact as a guest reviewer. Skipped on an article that already has any reviewer (a teammate or guest added by hand). Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"email":{"type":"string","maxLength":255,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"External contact's email. Auto-invited as a guest reviewer on every future finished draft."},"name":{"type":"string","maxLength":120}},"required":["email"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reviewer":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","email","name","created_at"]}},"required":["reviewer"]}},{"name":"remove_standing_reviewer","description":"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). Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"reviewer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Standing-reviewer row UUID (from list_standing_reviewers). Stops FUTURE auto-invites only — never revokes an invite already sent (use revoke_guest_reviewer for that)."}},"required":["reviewer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"reviewer_id":{"type":"string"}},"required":["deleted","reviewer_id"]}},{"name":"list_published","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional collection UUID (from list_collections) to filter by","type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"format":{"default":"html","description":"html returns the frozen HTML; md converts it on read","type":"string","enum":["html","md"]},"limit":{"default":1000,"description":"Maximum number of published articles to return per page (defaults to all, capped at 1000)","type":"integer","minimum":1,"maximum":1000},"cursor":{"description":"Opaque pagination cursor: pass the previous response's next_cursor to fetch the next page. Omit for the first page.","type":"string","minLength":1,"maxLength":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"cover_image":{"description":"Cover image URL, flat (also in metadata.cover_image).","type":["string","null"]},"cover_image_alt":{"description":"Alt text for the cover; null when there is no cover.","type":["string","null"]},"cover_image_credit":{"anyOf":[{"type":"object","properties":{"source":{"type":"string"},"photographer":{"type":["string","null"]},"photographerUrl":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"license":{"type":"string"},"required":{"type":"boolean","description":"Whether the licence requires the credit be shown."}},"required":["source","photographer","photographerUrl","sourceUrl","license","required"]},{"type":"null"}],"description":"Photo credit for a stock-photo cover; null for generated/uploaded covers."},"author":{"description":"Byline, flat (also in metadata.author).","type":["string","null"]},"author_slug":{"description":"Bank author page key; null for legacy free-form bylines.","type":["string","null"]},"author_profile":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"}},"started_at":{"type":["string","null"]}},"required":["name","slug","bio","role","expertise","started_at"]},{"type":"null"}],"description":"Masthead profile of a bank author; null for legacy bylines."},"summary":{"description":"The dek (also in metadata.summary).","type":["string","null"]},"tags":{"type":"array","items":{"type":"string"},"description":"Flattened from metadata.tags."},"custom_fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"This collection's declared custom fields (set_collection_config's custom_fields section), keyed by field key. Only enabled, already-generated fields appear; a field with no value yet is simply absent."},"sponsor_disclosure":{"anyOf":[{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where to disclose: article_header (`label` at the top of the article, beside the byline), article_footer (`text` after the body), cover_badge (`label` set over the cover by the site), cover_baked (`label` drawn into the image itself; `cover_image` is then that rendered image), structured_data (schema.org `sponsor` on the article JSON-LD), sponsored_links (rel=\"sponsored\" on body links to the sponsor's domain), meta_description (`label` prefixed to the meta and Open Graph descriptions), site_statement (`site_text` on every page, the About page, llms.txt and RSS), site_byline (the byline is the site's editors; author fields are served null)."},"sponsor":{"type":"object","properties":{"name":{"type":"string"},"url":{"description":"The sponsor's site, for JSON-LD and sponsored_links; null when unknown.","type":["string","null"]}},"required":["name","url"]},"text":{"type":"string","description":"The article sentence, verbatim: 'This article is sponsored by …'."},"label":{"type":"string","description":"The short label, verbatim: 'Sponsored by …'."},"site_text":{"type":"string","description":"The site-wide sentence, verbatim: 'This publication is sponsored by …'."}},"required":["surfaces","sponsor","text","label","site_text"]},{"type":"null"}],"description":"The publication's sponsor disclosure (set_collection_config's disclosure section). Presentation only: never part of `content`. null = disclose nothing."},"article_id":{"type":["string","null"]},"slug":{"description":"The slug set on the post, or else one derived from the published title (lowercase, dashes). A derived slug follows title edits; set it explicitly if routes depend on it.","type":["string","null"]},"collection_id":{"type":["string","null"]},"title":{"type":["string","null"]},"content":{"description":"Frozen body: TipTap HTML, or Markdown when format=md.","type":["string","null"]},"format":{"type":"string","enum":["html","md"]},"published_version":{"type":["number","null"]},"published_at":{"description":"datePublished — set once at first publish (a republish keeps it; unpublish then publish restamps it). list_published orders by it.","type":["string","null"]},"updated_at":{"description":"dateModified — advances only when content changes.","type":["string","null"]},"metadata":{"description":"The raw article metadata blob ({} when absent)."},"paper_trail":{"anyOf":[{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"note":{"type":"string"}},"required":["url","title","note"]}},"citation":{"type":"array","items":{"type":"object","properties":{"@type":{"type":"string","const":"CreativeWork"},"name":{"type":"string"},"url":{"type":"string"}},"required":["@type","name","url"]},"description":"schema.org citation fragment, ready for Article JSON-LD."}},"required":["sources","citation"]},{"type":"null"}],"description":"Audited provenance: the sources this article used; null when none."}},"required":["cover_image","cover_image_alt","cover_image_credit","author","author_slug","author_profile","summary","tags","custom_fields","sponsor_disclosure","article_id","slug","collection_id","title","content","format","published_version","published_at","updated_at","metadata","paper_trail"],"description":"A published article, frozen at its published version."}},"count":{"type":"number","description":"Number of articles in this page (items.length)."},"next_cursor":{"description":"Opaque keyset cursor for the next page; pass back as `cursor`. Null on the last page.","type":["string","null"]},"has_more":{"type":"boolean","description":"True when another page exists — fetch it with next_cursor."}},"required":["items","count","next_cursor","has_more"]}},{"name":"get_published","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"article_id":{"description":"Article UUID","type":"string","minLength":1,"maxLength":200},"slug":{"description":"Published slug (alternative to article_id)","type":"string","minLength":1,"maxLength":200},"format":{"default":"html","description":"html returns the frozen HTML; md converts it on read","type":"string","enum":["html","md"]},"collection_id":{"description":"Only match within this collection (from list_collections). Pass it with slug: slugs are unique per collection, not per organization","type":"string","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cover_image":{"description":"Cover image URL, flat (also in metadata.cover_image).","type":["string","null"]},"cover_image_alt":{"description":"Alt text for the cover; null when there is no cover.","type":["string","null"]},"cover_image_credit":{"anyOf":[{"type":"object","properties":{"source":{"type":"string"},"photographer":{"type":["string","null"]},"photographerUrl":{"type":["string","null"]},"sourceUrl":{"type":["string","null"]},"license":{"type":"string"},"required":{"type":"boolean","description":"Whether the licence requires the credit be shown."}},"required":["source","photographer","photographerUrl","sourceUrl","license","required"]},{"type":"null"}],"description":"Photo credit for a stock-photo cover; null for generated/uploaded covers."},"author":{"description":"Byline, flat (also in metadata.author).","type":["string","null"]},"author_slug":{"description":"Bank author page key; null for legacy free-form bylines.","type":["string","null"]},"author_profile":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"bio":{"type":"string"},"role":{"type":["string","null"]},"expertise":{"type":"array","items":{"type":"string"}},"started_at":{"type":["string","null"]}},"required":["name","slug","bio","role","expertise","started_at"]},{"type":"null"}],"description":"Masthead profile of a bank author; null for legacy bylines."},"summary":{"description":"The dek (also in metadata.summary).","type":["string","null"]},"tags":{"type":"array","items":{"type":"string"},"description":"Flattened from metadata.tags."},"custom_fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"This collection's declared custom fields (set_collection_config's custom_fields section), keyed by field key. Only enabled, already-generated fields appear; a field with no value yet is simply absent."},"sponsor_disclosure":{"anyOf":[{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]},"description":"Where to disclose: article_header (`label` at the top of the article, beside the byline), article_footer (`text` after the body), cover_badge (`label` set over the cover by the site), cover_baked (`label` drawn into the image itself; `cover_image` is then that rendered image), structured_data (schema.org `sponsor` on the article JSON-LD), sponsored_links (rel=\"sponsored\" on body links to the sponsor's domain), meta_description (`label` prefixed to the meta and Open Graph descriptions), site_statement (`site_text` on every page, the About page, llms.txt and RSS), site_byline (the byline is the site's editors; author fields are served null)."},"sponsor":{"type":"object","properties":{"name":{"type":"string"},"url":{"description":"The sponsor's site, for JSON-LD and sponsored_links; null when unknown.","type":["string","null"]}},"required":["name","url"]},"text":{"type":"string","description":"The article sentence, verbatim: 'This article is sponsored by …'."},"label":{"type":"string","description":"The short label, verbatim: 'Sponsored by …'."},"site_text":{"type":"string","description":"The site-wide sentence, verbatim: 'This publication is sponsored by …'."}},"required":["surfaces","sponsor","text","label","site_text"]},{"type":"null"}],"description":"The publication's sponsor disclosure (set_collection_config's disclosure section). Presentation only: never part of `content`. null = disclose nothing."},"article_id":{"type":["string","null"]},"slug":{"description":"The slug set on the post, or else one derived from the published title (lowercase, dashes). A derived slug follows title edits; set it explicitly if routes depend on it.","type":["string","null"]},"collection_id":{"type":["string","null"]},"title":{"type":["string","null"]},"content":{"description":"Frozen body: TipTap HTML, or Markdown when format=md.","type":["string","null"]},"format":{"type":"string","enum":["html","md"]},"published_version":{"type":["number","null"]},"published_at":{"description":"datePublished — set once at first publish (a republish keeps it; unpublish then publish restamps it). list_published orders by it.","type":["string","null"]},"updated_at":{"description":"dateModified — advances only when content changes.","type":["string","null"]},"metadata":{"description":"The raw article metadata blob ({} when absent)."},"paper_trail":{"anyOf":[{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"note":{"type":"string"}},"required":["url","title","note"]}},"citation":{"type":"array","items":{"type":"object","properties":{"@type":{"type":"string","const":"CreativeWork"},"name":{"type":"string"},"url":{"type":"string"}},"required":["@type","name","url"]},"description":"schema.org citation fragment, ready for Article JSON-LD."}},"required":["sources","citation"]},{"type":"null"}],"description":"Audited provenance: the sources this article used; null when none."}},"required":["cover_image","cover_image_alt","cover_image_credit","author","author_slug","author_profile","summary","tags","custom_fields","sponsor_disclosure","article_id","slug","collection_id","title","content","format","published_version","published_at","updated_at","metadata","paper_trail"],"description":"A published article, frozen at its published version."}},{"name":"list_publish_targets","description":"List the external destinations configured for the caller's organization (Webflow collections, Framer collections, Google Docs destinations, GitHub repos from set_github_publish_target) that publish_to_connector can target. Returns each target's connector, target_id, and name. Empty if no connectors are configured: set them up in the app first (or via set_github_publish_target for GitHub). Requires capability connector:read.","capability":"connector:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"connector":{"type":"string","enum":["webflow","framer","google_docs","contentful","github"]},"name":{"type":"string"}},"required":["target_id","connector","name"]}},"count":{"type":"number"}},"required":["items","count"]}},{"name":"publish_to_connector","description":"Publish an article to an external connector (Webflow / Framer / Google Docs / GitHub). Pick a target from list_publish_targets. Blocks if the article has un-approved reviewers. Also blocks when re-publishing an article whose item was deleted or unpublished in the client's CMS — that needs confirm_missing_target, so ask a human before setting it (not applicable to GitHub, which always opens or updates a PR). Publishing is asynchronous: returns a publish_id you can poll with get_publish_status. For GitHub, this only OPENS a pull request on the target repo — nothing goes live on the client's site until they review and merge it themselves; Letter is not the CMS in that setup, their repo is. Refuses an article written for an independent publication (a phantom): the org's connectors are its own site. To have the Planner publish every post here on its own, bind the collection (bind_collection_cms) and turn publishing.cms on (set_collection_config) instead of calling this per article. Requires capability connector:publish.","capability":"connector:publish","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles or ingest_article)"},"connector":{"type":"string","enum":["webflow","framer","google_docs","contentful","github"],"description":"Target connector (from list_publish_targets)"},"target_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Destination id from list_publish_targets (a field-mapping or destination UUID)"},"confirm_missing_target":{"description":"Only after a publish was refused because its target no longer exists (or is unpublished) on the provider: set true to re-create/re-publish it there anyway. Never set it pre-emptively — the refusal exists because the client may have removed that content on purpose.","type":"boolean"}},"required":["article_id","connector","target_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"publish_id":{"type":"string"},"connector":{"type":"string","enum":["webflow","framer","google_docs","contentful","github"]},"status":{"type":"string","const":"pending"}},"required":["publish_id","connector","status"]}},{"name":"get_publish_status","description":"Get the status of a connector publish started with publish_to_connector. Pass the connector and publish_id. Returns status (publishing/completed/failed), any error, and the external item id/url once available (for GitHub: the PR url/number). Requires capability connector:read.","capability":"connector:read","inputSchema":{"type":"object","properties":{"connector":{"type":"string","enum":["webflow","framer","google_docs","contentful","github"],"description":"The connector the publish_id belongs to"},"publish_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Publish id returned by publish_to_connector"}},"required":["connector","publish_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"publish_id":{"type":"string"},"connector":{"type":"string","enum":["webflow","framer","google_docs","contentful","github"]},"status":{"type":"string","description":"publishing / completed / failed."},"error":{"type":["string","null"]},"article_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"external":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Connector-specific external ids/urls (e.g. google_doc_url, contentful_entry_id)."}},"required":["publish_id","connector","status","error","article_id","target_id","created_at","updated_at","external"]}},{"name":"list_phantoms","description":"List the caller's phantoms — sites Letter hosts as independent publications (Phantomstory) that never name the client. Not the org's own website or blog (The Site; list_collections for its blog). Each renders a collection; run_collection on that collection_id publishes a post to the site. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]}},"configured":{"type":"boolean"}},"required":["items","configured"]}},{"name":"get_phantom","description":"One phantom: provisioning status and phase (provisioning → awaiting_dns → live), its URL, domain and DNS state, the collection it renders. Polling a still-provisioning site nudges it forward. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom UUID from list_phantoms."}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]}},"required":["phantom"]}},{"name":"create_phantom","description":"Create a phantom — a site Letter hosts as an independent publication. Provisions the site on its instant subdomain, marks its collection independent (it never names the client; a name or description containing the client's company name is rejected, and the name seeds the URL), and unless configure:false brings it to spec in the background: strategy, visuals, writing kernel, cadence and topic map. Pass `config` to seed the collection's file. METERED IN SITES: an account billed in tokens pays per live site-day (phantom_site_day) under its spend cap; any other account is refused past its phantom credits (get_phantom_balance). Pausing or deleting a site stops both. Returns the phantom in status 'provisioning' plus the configuration job; poll get_phantom until 'live' and get_phantom_config for the configuration. For Letter's own recipe from a client domain (positioning inferred, a masthead proposed, the first posts seeded) use plan_phantom. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":140},"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]},"collection_id":{"description":"An existing collection the site renders (its brand_subject becomes 'independent'). Omit to create one named after the site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"customer_ref":{"type":"string","minLength":1,"maxLength":200},"configure":{"description":"Bring the site to spec after provisioning — strategy, visuals, kernel, cadence, topic map (default true). false = a bare site.","type":"boolean"},"config":{"description":"The collection's starting file (cadence, content, publishing, measurement), applied once the site is configured.","type":"object","properties":{"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"cadence":{"type":"object","properties":{"enabled":{"description":"Whether the planner keeps the collection producing on cadence.","type":"boolean"},"posts_per_week":{"description":"Posts a week. A client's own blog goes up to 21 (three a day); a phantom (independent) site up to 7.","type":"integer","minimum":1,"maximum":21},"timezone":{"type":"string","minLength":1,"maxLength":64},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"description":"The steering prompt every post and the topic map follow. null clears.","anyOf":[{"type":"string","maxLength":4000},{"type":"null"}]},"lead_time_hours":{"description":"How many hours before a publish slot the planner starts writing.","type":"integer","minimum":1,"maximum":168},"review_window_hours":{"description":"review_window only: hours a ready post waits for a person before it publishes.","type":"integer","minimum":1,"maximum":336},"notifications":{"description":"Who the planner emails, and when. Empty emails = the planner's acting user.","type":"object","properties":{"emails":{"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"on_ready_for_review":{"type":"boolean"},"on_published":{"type":"boolean"},"on_failure":{"type":"boolean"}}}}},"planner":{"type":"object","properties":{"use_backlog":{"description":"Also claim from the Curate backlog, not just suggestions.","type":"boolean"},"min_confidence":{"description":"Minimum model confidence for a headline.","type":"number","minimum":0,"maximum":1},"avoid_recent_days":{"description":"Skip topics too close to anything produced in the last N days.","type":"integer","minimum":0,"maximum":365},"max_attempts_per_run":{"description":"Retries per phase before a run fails.","type":"integer","minimum":1,"maximum":5},"pause_on_failure_count":{"description":"Consecutive failed runs that pause the planner; 0 = never.","type":"integer","minimum":0,"maximum":10},"resolution_floor":{"description":"Vet new subtopics against the resolution floor.","type":"boolean"},"comparison_share_cap":{"description":"Cap on the share of slots that may be comparison/roundup topics; null = no cap.","anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"mention_degree":{"description":"The planner's own client-mention degree; inherit = content.mention_degree.","type":"string","enum":["inherit","off","subtle","woven"]}}},"publishing":{"type":"object","properties":{"publish":{"description":"Pin a version on Letter when a run finishes.","type":"boolean"},"cms":{"description":"Push through the bound CMS mapping when a run finishes.","type":"boolean"},"rebuild":{"description":"Hosted only: redeploy the site after each publish.","type":"boolean"},"live_edits":{"description":"Edits to live posts: applied automatically or held for review. null = the derived default.","anyOf":[{"type":"string","enum":["auto","review"]},{"type":"null"}]},"strategy_approval":{"description":"Changes to the automations proposed from the measures (press, ease, add): manual waits for Accept; review_window applies two days after it is sent unless someone declines. null = the derived default.","anyOf":[{"type":"string","enum":["manual","review_window"]},{"type":"null"}]},"sitemap_url":{"description":"This collection's own sitemap: the internal-links crawl target and sitemap-sync source.","anyOf":[{"type":"string","maxLength":2000,"format":"uri"},{"type":"null"}]}}},"targets":{"type":"object","properties":{"enabled":{"description":"Press the org's active ranking targets on this collection (binds each to the pillar it fits; the planner steers the tier's share of slots toward them). false dismisses the bindings.","type":"boolean"},"tier":{"description":"Manual steering tier, in ladder order: ramping (1 in 2, the floor), aggressive (2 in 3), steady (4 in 5). Omit to let the ramp earn it: a rung needs both distinct cited pages and published posts on the targets' ground to back it (targets.ramp in the readback). A tier set here is not held back by the posts.","type":"string","enum":["ramping","aggressive","steady"]}}},"measurement":{"type":"object","properties":{"geo_probing":{"description":"Probe AI engines for the collection's ground.","type":"boolean"}}},"sourcing":{"type":"object","properties":{"market":{"description":"The search market keyword demand is measured in; null = the organization's market.","anyOf":[{"type":"string","pattern":"^[A-Z]{2}$"},{"type":"null"}]},"demand_focus":{"description":"Which demand headline ranking leans on: 'seo' (keyword volume, measured Google performance), 'geo' (AI-answer gaps, ground assistants already name the client on), or 'balanced' (both, the default). It shifts scoring weights only; the other side still counts. How many slots go to ranking targets is the steering tier's share (targets.tier), under every focus.","type":"string","enum":["balanced","seo","geo"]}}},"relevance":{"type":"object","properties":{"min_topic_relevance":{"description":"The lowest spoke client_relevance the planner may write or steer toward: 'high' = only topics squarely on the client's product, 'medium' = on the product or close to it (the buyers' own questions), 'low' = anything on the map. Default 'low' (no floor) for both: the label is set by the topic generator and is unreliable as a filter; a main blog is filtered by brand_gate instead.","anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}]},"brand_gate":{"description":"The brand check on generated pillars and spokes: 'strict' drops off-brand topics, judged against the positioning (what the client is / is not) even before it has published, and flags unsure ones for a reviewer; 'narrow' also drops unsure ones (for a very tight blog); 'lenient' drops only clearly off-brand ones and skips a thin corpus; 'off' doesn't run. Main-blog default 'strict', phantom default 'off'.","anyOf":[{"type":"string","enum":["off","lenient","strict","narrow"]},{"type":"null"}]},"min_brand_fit":{"description":"The lowest judged brand fit a headline candidate may have (0 / 0.5 / 1); candidates nobody judged pass. Main-blog default 0.5 (drops judged misfits), phantom default 0.","anyOf":[{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},{"type":"null"}]},"pillar_breadth":{"description":"What a ranking target's new pillar proposal may be: 'on_product' (squarely about what the client sells) or 'tangential' (adjacent ground an independent publication covers). Main-blog default 'on_product', phantom default 'tangential'.","anyOf":[{"type":"string","enum":["on_product","tangential"]},{"type":"null"}]},"auto_approve_pillars":{"description":"Whether an unreviewed proposed target pillar auto-approves after its window. Main-blog default false (a person approves), phantom default true.","type":["boolean","null"]},"keyword_scope":{"description":"Which pillars a discovered SEO keyword is judged relevant against: 'collection' (this collection's own) or 'org' (every collection's, phantoms included). Main-blog default 'collection', phantom default 'org'.","anyOf":[{"type":"string","enum":["collection","org"]},{"type":"null"}]}}},"disclosure":{"type":"object","properties":{"surfaces":{"description":"Where this independent publication discloses its sponsor — the whole set, replacing the stored one; [] turns disclosure off. article_header: 'Sponsored by {Sponsor}' at the top of each article, beside the byline. article_footer: 'This article is sponsored by {Sponsor}.' after the body. cover_badge: 'Sponsored by {Sponsor}' set over the cover by the site. cover_baked: the same label drawn into the cover image itself, so it travels into link previews and shares (the stored cover is untouched); one or the other, not both. structured_data: schema.org `sponsor` on the article JSON-LD. sponsored_links: rel=\"sponsored\" on body links to the sponsor, Google's label for paid links (already nofollow, so this labels them as paid rather than changing what they pass). meta_description: 'Sponsored by {Sponsor}.' at the start of each article's meta and Open Graph description. site_statement: 'This publication is sponsored by {Sponsor}.' in the footer of every page, the About page, llms.txt and RSS. site_byline: articles are bylined to the site's editors instead of a named writer (author fields are served null). 'Safe mode' is every surface except the two cover ones. Only an independent collection may disclose.","maxItems":9,"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name to show instead of the resolved company name; null or '' clears it. Required to disclose on an internal or phantom_farm org.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}}},"custom_fields":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$","description":"Slug, e.g. \"faq\". The value shows up as custom_fields.<key> on the published API."},"label":{"type":"string","minLength":1,"maxLength":120},"generation_prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The instruction handed to the model when generating this field's value."},"shape":{"description":"The expected shape of a generated value — text, list_of_text, object, or list_of_objects.","$ref":"#/$defs/__schema0"},"write_policy":{"default":"always","description":"'always' regenerates on every pass; 'create_only' is written once and never touched again.","type":"string","enum":["always","create_only"]},"enabled":{"default":true,"type":"boolean"}},"required":["key","label","generation_prompt","shape"]}}}},"composition":{"description":"Bundle this phantom with a named primitive_compositions.key (get/list_composition): its content section (kernel, shred, shredder endpoint, content template) seeds the collection's file and its phantom section seeds theme — anything also given in `theme`/`config` wins, field by field. When the composition names a seer, one is stood up bound to this phantom's collection right after it's created, unless skip_seer is set.","type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":64,"description":"A primitive_compositions.key (list/get_composition)."},"skip_seer":{"description":"true = apply content/phantom defaults only, even when the composition declares a seer.","type":"boolean"}},"required":["key"]},"client_domain":{"description":"Domain to infer the strategy from (bare, no scheme). Omit to use the org's own.","type":"string","maxLength":253},"dry_run":{"description":"Return the configuration plan without provisioning anything.","type":"boolean"}},"required":["name"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}},"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"dry_run":{"type":"boolean","const":true},"plan":{}},"required":["dry_run","plan"]},{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]},"config_job_id":{"description":"The configuration job bringing the site to spec; poll get_phantom_config.","type":["string","null"]},"composition_seer_id":{"description":"The seer created from the named composition's seer spec, when it declared one and skip_seer wasn't set.","type":["string","null"]}},"required":["phantom","config_job_id"]}],"type":"object"}},{"name":"get_phantom_config","description":"A phantom's configuration as one read: the site (identity, theme, domain) joined to its collection's one-file configuration (the editorial half — get_collection_config) and the latest configuration job's status. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom UUID from list_phantoms."}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]},"collection":{"anyOf":[{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"identity":{"type":"object","properties":{"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"profile":{"type":"string"},"hosted":{"type":"boolean"}},"required":["brand_subject","publish_control","profile","hosted"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"type":["string","null"]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null = the org's default.","type":["string","null"]},"visuals":{"type":"boolean"},"cover":{"type":"string","description":"editorial_image | full_ai | line_art | none (or another canvas kind)."},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"template_key":{"type":["string","null"]}},"required":["research_depth","kernel_family","verify","shred","shredder_endpoint_id","visuals","cover","mention_degree","template_key"]},"cadence":{"type":"object","properties":{"enabled":{"type":"boolean"},"posts_per_week":{"type":"number"},"timezone":{"type":"string"},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"type":["string","null"]},"configured":{"type":"boolean","description":"false = no cadence has ever been saved; the values shown are the kind-aware defaults."}},"required":["enabled","posts_per_week","timezone","approval_mode","direction","configured"]},"publishing":{"type":"object","properties":{"publish":{"type":"boolean"},"cms":{"type":"boolean"},"rebuild":{"type":"boolean"},"cms_provider":{"type":["string","null"]}},"required":["publish","cms","rebuild","cms_provider"]},"targets":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the org's active ranking targets are bound to (pressed on) this collection."},"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"steer_share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"],"description":"The share of write slots the tier steers at — `num` of every `den`: ramping 1 in 2, aggressive 2 in 3, steady 4 in 5."},"ramp":{"type":"object","properties":{"cited_pages":{"type":"number","description":"Distinct pages of this collection cited in AI answers."},"target_posts_published":{"type":"number","description":"Published posts on the targets' ground (their pillars)."},"next":{"anyOf":[{"type":"object","properties":{"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"]},"cited_pages":{"type":"number","description":"Cited pages the next rung needs."},"target_posts":{"type":"number","description":"Published target posts the next rung needs."}},"required":["tier","share","cited_pages","target_posts"]},{"type":"null"}],"description":"The next rung and what it takes — it needs BOTH. null at the top rung."}},"required":["cited_pages","target_posts_published","next"],"description":"What the steering ramp reads: a rung needs both the citations and the posts to back it."},"recent_share":{"type":"object","properties":{"target":{"type":"number"},"of":{"type":"number"}},"required":["target","of"],"description":"What the schedule actually gave target ground: `target` of the last `of` committed slots. Below steer_share means target headlines ran short."},"bound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"},"cluster_id":{"type":["string","null"]},"status":{"type":"string","enum":["bound","proposed","dismissed"]}},"required":["target_id","phrase","cluster_id","status"]}},"unbound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"}},"required":["target_id","phrase"]},"description":"Active org targets not bound here — what enabled:true would bind."},"cap":{"type":"number"}},"required":["enabled","tier","steer_share","ramp","recent_share","bound","unbound","cap"]},"measurement":{"type":"object","properties":{"geo_probing":{"type":"boolean"}},"required":["geo_probing"]},"sourcing":{"type":"object","properties":{"demand_focus":{"type":"string","enum":["balanced","seo","geo"]}},"required":["demand_focus"],"description":"How headline ranking weighs demand: 'seo' leans on search, 'geo' on AI answers, 'balanced' on both. Ranking targets keep the steering tier's share of slots under every focus."},"relevance":{"type":"object","properties":{"preset":{"type":"string","enum":["client","independent"],"description":"Whose defaults the dials start from: a main blog's or a phantom's."},"min_topic_relevance":{"type":"string","enum":["low","medium","high"]},"brand_gate":{"type":"string","enum":["off","lenient","strict","narrow"]},"min_brand_fit":{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},"pillar_breadth":{"type":"string","enum":["on_product","tangential"]},"auto_approve_pillars":{"type":"boolean"},"keyword_scope":{"type":"string","enum":["collection","org"]},"overridden":{"type":"array","items":{"type":"string"},"description":"The dials set on this collection; every other one follows the preset."}},"required":["preset","min_topic_relevance","brand_gate","min_brand_fit","pillar_breadth","auto_approve_pillars","keyword_scope","overridden"],"description":"How tightly the collection stays on the client's product (set_collection_config `relevance`; recheck_topic_relevance applies it to the pending topics)."},"disclosure":{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name override; null = the company name the context resolver gives.","type":["string","null"]},"sponsor":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"url":{"type":["string","null"]}},"required":["name","url"]},{"type":"null"}],"description":"Who the disclosure names (the label, else the resolved company), read-only; null on a client-brand collection, or where a label is required and missing."},"resolved_name":{"description":"The company the disclosure names when no sponsor_label is set, read-only; null on a client-brand collection and on internal/phantom_farm orgs, where a label is required.","type":["string","null"]},"last_change":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"note":{"type":["string","null"]},"actor_user_id":{"type":["string","null"]}},"required":["at","note","actor_user_id"]},{"type":"null"}],"description":"Who last changed the disclosure and why, read-only: from the collection's event log. An experiment's arm notes 'experiment <key> · <arm>'. null = never set."}},"required":["surfaces","sponsor_label","sponsor","resolved_name","last_change"],"description":"Sponsor disclosure ('safe mode'): where an independent publication says who sponsors it. Presentation only — added when a post is served (sponsor_disclosure on list_published/get_published/get_article), never written into the body, and it reaches every existing post on the next rebuild. Empty surfaces = off."},"custom_fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"generation_prompt":{"type":"string"},"shape":{"$ref":"#/$defs/__schema0"},"write_policy":{"type":"string","enum":["always","create_only"]},"enabled":{"type":"boolean"},"sort_order":{"type":"number"}},"required":["id","key","label","generation_prompt","shape","write_policy","enabled","sort_order"]},"description":"This collection's declared custom fields — set_collection_config's custom_fields section replaces the whole set."},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","identity","content","cadence","publishing","targets","measurement","sourcing","relevance","disclosure","custom_fields","updated_at"]},{"type":"null"}],"description":"The collection's file — the editorial half of the phantom."},"configuration":{"anyOf":[{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string"},"stage":{"type":"string"}},"required":["job_id","status","stage"]},{"type":"null"}],"description":"The latest configuration job (strategy → visuals → enhance → kernel → planner → map)."}},"required":["phantom","collection","configuration"]}},"required":["config"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}}}},{"name":"set_phantom_config","description":"PATCH a phantom: `config` for the site's own knobs (name, tagline, theme, redesign + layout, style (palette and fonts), stock_cover, a logo pick to compose, customer_ref, the collection it renders — identity and look changes trigger a rebuild), `collection` for its collection's file (cadence, content, publishing, targets, measurement — the same sections set_collection_config takes), `configure: true` to re-run the configuration job (strategy, visuals, kernel, cadence, topic map) in the background; with `dry_run` it returns the plan that job would apply and persists nothing. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"config":{"description":"The site's own knobs; omitted fields are kept.","type":"object","properties":{"name":{"description":"The publication's masthead. An independent publication is named after its subject, never after the client — a name containing the client's company name is rejected.","type":"string","minLength":1,"maxLength":100},"description":{"description":"One-sentence tagline under the masthead.","anyOf":[{"type":"string","maxLength":140},{"type":"null"}]},"theme":{"description":"Visual theme (default gazette).","type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]},"customer_ref":{"description":"Integrator attribution: the owning customer's id or email in YOUR system. Metadata only.","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"collection_id":{"description":"Repoint the site at a different collection of the org's.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"redesign":{"description":"Render the redesigned blog at the site's normal URLs (home, posts, sections). Off by default; the change rebuilds the site.","type":"boolean"},"layout":{"description":"The redesign's layout: notes (Linear/Vercel-style: three featured stories, a filterable grid, a text archive. Tech, SaaS, product.); journal (Ahrefs/Intercom-style: topic-grouped ruled lists, credits and sources up front. B2B, data, finance, analysis.); magazine (Aeon/Works-in-Progress-style: full-bleed lead, labelled essays, pull quotes. Culture, lifestyle, ideas.); essay (HEY World/Substack-style: one quiet centred column, no image grid. Opinion, newsletters, long reads.); broadsheet (NYT/FT-style: dated front page in ruled columns, most-read, section rows. News, policy, markets.); clinical (NEJM/STAT-style: summaries, numbered sections and figures, references. Health, science, research.). null clears the choice and the site's theme suggests one.","anyOf":[{"type":"string","enum":["notes","journal","magazine","essay","broadsheet","clinical"]},{"type":"null"}]},"style":{"description":"The site's palette and fonts (colors as CSS values, fonts as Google Fonts families). The change rebuilds the site and recolors its derived visuals.","anyOf":[{"type":"object","properties":{"accentColor":{"type":"string","maxLength":30},"secondaryColor":{"type":"string","maxLength":30},"popColor":{"type":"string","maxLength":30},"backgroundColor":{"type":"string","maxLength":30},"surfaceColor":{"type":"string","maxLength":30},"textColor":{"type":"string","maxLength":30},"headingColor":{"type":"string","maxLength":30},"linkColor":{"type":"string","maxLength":30},"heroFrom":{"type":"string","maxLength":30},"heroTo":{"type":"string","maxLength":30},"fontBody":{"type":"string","maxLength":60},"fontHeading":{"type":"string","maxLength":60}},"additionalProperties":false},{"type":"null"}]},"stock_cover":{"description":"Stock-photo covers: when enabled, a post's cover is chosen from free stock sources at publish time. null turns it off.","anyOf":[{"type":"object","properties":{"enabled":{"type":"boolean"},"sources":{"type":"array","items":{"type":"string","enum":["openverse","pexels","unsplash"]}},"keywords":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]}},"required":["enabled"],"additionalProperties":false},{"type":"null"}]},"logo":{"description":"Compose the site's logo from a pick (an archetype spec, or a glyph + wordmark), drawn in the site's palette. The change rebuilds the site.","anyOf":[{"type":"object","properties":{"spec":{"type":"object","properties":{"archetype":{"type":"string","enum":["geometric","display-serif","blocky-sans","expressive"]},"variantId":{"type":"string","maxLength":120}},"required":["archetype","variantId"]}},"required":["spec"]},{"type":"object","properties":{"glyphId":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"wordmarkId":{"type":"string","maxLength":60}},"required":["glyphId","wordmarkId"]}]},"maverick_enabled":{"description":"Opt this site into visitor identification. Off by default. Enabling registers the site's domain for identification automatically and records its customer id; disabling deactivates the registration. A site whose registration hasn't landed reports pending_registration in get_phantom_visitors — re-sending enabled:true retries.","type":"boolean"},"maverick_customer_id":{"description":"The identification account's customer id, recorded automatically when enabling registers the site; settable directly only as a repair. Baked into the site as the pixel's data-customer-id; the change rebuilds the site. null clears it (identification back to pending).","anyOf":[{"type":"string","maxLength":50},{"type":"null"}]}}},"collection":{"description":"The collection's file, patched through the same seam set_collection_config uses.","type":"object","properties":{"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"cadence":{"type":"object","properties":{"enabled":{"description":"Whether the planner keeps the collection producing on cadence.","type":"boolean"},"posts_per_week":{"description":"Posts a week. A client's own blog goes up to 21 (three a day); a phantom (independent) site up to 7.","type":"integer","minimum":1,"maximum":21},"timezone":{"type":"string","minLength":1,"maxLength":64},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"description":"The steering prompt every post and the topic map follow. null clears.","anyOf":[{"type":"string","maxLength":4000},{"type":"null"}]},"lead_time_hours":{"description":"How many hours before a publish slot the planner starts writing.","type":"integer","minimum":1,"maximum":168},"review_window_hours":{"description":"review_window only: hours a ready post waits for a person before it publishes.","type":"integer","minimum":1,"maximum":336},"notifications":{"description":"Who the planner emails, and when. Empty emails = the planner's acting user.","type":"object","properties":{"emails":{"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"on_ready_for_review":{"type":"boolean"},"on_published":{"type":"boolean"},"on_failure":{"type":"boolean"}}}}},"planner":{"type":"object","properties":{"use_backlog":{"description":"Also claim from the Curate backlog, not just suggestions.","type":"boolean"},"min_confidence":{"description":"Minimum model confidence for a headline.","type":"number","minimum":0,"maximum":1},"avoid_recent_days":{"description":"Skip topics too close to anything produced in the last N days.","type":"integer","minimum":0,"maximum":365},"max_attempts_per_run":{"description":"Retries per phase before a run fails.","type":"integer","minimum":1,"maximum":5},"pause_on_failure_count":{"description":"Consecutive failed runs that pause the planner; 0 = never.","type":"integer","minimum":0,"maximum":10},"resolution_floor":{"description":"Vet new subtopics against the resolution floor.","type":"boolean"},"comparison_share_cap":{"description":"Cap on the share of slots that may be comparison/roundup topics; null = no cap.","anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"mention_degree":{"description":"The planner's own client-mention degree; inherit = content.mention_degree.","type":"string","enum":["inherit","off","subtle","woven"]}}},"publishing":{"type":"object","properties":{"publish":{"description":"Pin a version on Letter when a run finishes.","type":"boolean"},"cms":{"description":"Push through the bound CMS mapping when a run finishes.","type":"boolean"},"rebuild":{"description":"Hosted only: redeploy the site after each publish.","type":"boolean"},"live_edits":{"description":"Edits to live posts: applied automatically or held for review. null = the derived default.","anyOf":[{"type":"string","enum":["auto","review"]},{"type":"null"}]},"strategy_approval":{"description":"Changes to the automations proposed from the measures (press, ease, add): manual waits for Accept; review_window applies two days after it is sent unless someone declines. null = the derived default.","anyOf":[{"type":"string","enum":["manual","review_window"]},{"type":"null"}]},"sitemap_url":{"description":"This collection's own sitemap: the internal-links crawl target and sitemap-sync source.","anyOf":[{"type":"string","maxLength":2000,"format":"uri"},{"type":"null"}]}}},"targets":{"type":"object","properties":{"enabled":{"description":"Press the org's active ranking targets on this collection (binds each to the pillar it fits; the planner steers the tier's share of slots toward them). false dismisses the bindings.","type":"boolean"},"tier":{"description":"Manual steering tier, in ladder order: ramping (1 in 2, the floor), aggressive (2 in 3), steady (4 in 5). Omit to let the ramp earn it: a rung needs both distinct cited pages and published posts on the targets' ground to back it (targets.ramp in the readback). A tier set here is not held back by the posts.","type":"string","enum":["ramping","aggressive","steady"]}}},"measurement":{"type":"object","properties":{"geo_probing":{"description":"Probe AI engines for the collection's ground.","type":"boolean"}}},"sourcing":{"type":"object","properties":{"market":{"description":"The search market keyword demand is measured in; null = the organization's market.","anyOf":[{"type":"string","pattern":"^[A-Z]{2}$"},{"type":"null"}]},"demand_focus":{"description":"Which demand headline ranking leans on: 'seo' (keyword volume, measured Google performance), 'geo' (AI-answer gaps, ground assistants already name the client on), or 'balanced' (both, the default). It shifts scoring weights only; the other side still counts. How many slots go to ranking targets is the steering tier's share (targets.tier), under every focus.","type":"string","enum":["balanced","seo","geo"]}}},"relevance":{"type":"object","properties":{"min_topic_relevance":{"description":"The lowest spoke client_relevance the planner may write or steer toward: 'high' = only topics squarely on the client's product, 'medium' = on the product or close to it (the buyers' own questions), 'low' = anything on the map. Default 'low' (no floor) for both: the label is set by the topic generator and is unreliable as a filter; a main blog is filtered by brand_gate instead.","anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}]},"brand_gate":{"description":"The brand check on generated pillars and spokes: 'strict' drops off-brand topics, judged against the positioning (what the client is / is not) even before it has published, and flags unsure ones for a reviewer; 'narrow' also drops unsure ones (for a very tight blog); 'lenient' drops only clearly off-brand ones and skips a thin corpus; 'off' doesn't run. Main-blog default 'strict', phantom default 'off'.","anyOf":[{"type":"string","enum":["off","lenient","strict","narrow"]},{"type":"null"}]},"min_brand_fit":{"description":"The lowest judged brand fit a headline candidate may have (0 / 0.5 / 1); candidates nobody judged pass. Main-blog default 0.5 (drops judged misfits), phantom default 0.","anyOf":[{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},{"type":"null"}]},"pillar_breadth":{"description":"What a ranking target's new pillar proposal may be: 'on_product' (squarely about what the client sells) or 'tangential' (adjacent ground an independent publication covers). Main-blog default 'on_product', phantom default 'tangential'.","anyOf":[{"type":"string","enum":["on_product","tangential"]},{"type":"null"}]},"auto_approve_pillars":{"description":"Whether an unreviewed proposed target pillar auto-approves after its window. Main-blog default false (a person approves), phantom default true.","type":["boolean","null"]},"keyword_scope":{"description":"Which pillars a discovered SEO keyword is judged relevant against: 'collection' (this collection's own) or 'org' (every collection's, phantoms included). Main-blog default 'collection', phantom default 'org'.","anyOf":[{"type":"string","enum":["collection","org"]},{"type":"null"}]}}},"disclosure":{"type":"object","properties":{"surfaces":{"description":"Where this independent publication discloses its sponsor — the whole set, replacing the stored one; [] turns disclosure off. article_header: 'Sponsored by {Sponsor}' at the top of each article, beside the byline. article_footer: 'This article is sponsored by {Sponsor}.' after the body. cover_badge: 'Sponsored by {Sponsor}' set over the cover by the site. cover_baked: the same label drawn into the cover image itself, so it travels into link previews and shares (the stored cover is untouched); one or the other, not both. structured_data: schema.org `sponsor` on the article JSON-LD. sponsored_links: rel=\"sponsored\" on body links to the sponsor, Google's label for paid links (already nofollow, so this labels them as paid rather than changing what they pass). meta_description: 'Sponsored by {Sponsor}.' at the start of each article's meta and Open Graph description. site_statement: 'This publication is sponsored by {Sponsor}.' in the footer of every page, the About page, llms.txt and RSS. site_byline: articles are bylined to the site's editors instead of a named writer (author fields are served null). 'Safe mode' is every surface except the two cover ones. Only an independent collection may disclose.","maxItems":9,"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name to show instead of the resolved company name; null or '' clears it. Required to disclose on an internal or phantom_farm org.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}}},"custom_fields":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$","description":"Slug, e.g. \"faq\". The value shows up as custom_fields.<key> on the published API."},"label":{"type":"string","minLength":1,"maxLength":120},"generation_prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The instruction handed to the model when generating this field's value."},"shape":{"description":"The expected shape of a generated value — text, list_of_text, object, or list_of_objects.","$ref":"#/$defs/__schema0"},"write_policy":{"default":"always","description":"'always' regenerates on every pass; 'create_only' is written once and never touched again.","type":"string","enum":["always","create_only"]},"enabled":{"default":true,"type":"boolean"}},"required":["key","label","generation_prompt","shape"]}}}},"configure":{"description":"Re-run the configuration job (strategy, visuals, kernel, cadence, topic map) in the background.","type":"boolean"},"dry_run":{"description":"With configure: return the plan the job would apply, persisting nothing.","type":"boolean"}},"required":["phantom_id"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]},"collection":{"anyOf":[{"type":"object","properties":{"collection_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"identity":{"type":"object","properties":{"brand_subject":{"type":"string","enum":["client","independent"]},"publish_control":{"type":"string","enum":["platform","connector","advisory"]},"profile":{"type":"string"},"hosted":{"type":"boolean"}},"required":["brand_subject","publish_control","profile","hosted"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"type":["string","null"]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null = the org's default.","type":["string","null"]},"visuals":{"type":"boolean"},"cover":{"type":"string","description":"editorial_image | full_ai | line_art | none (or another canvas kind)."},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"template_key":{"type":["string","null"]}},"required":["research_depth","kernel_family","verify","shred","shredder_endpoint_id","visuals","cover","mention_degree","template_key"]},"cadence":{"type":"object","properties":{"enabled":{"type":"boolean"},"posts_per_week":{"type":"number"},"timezone":{"type":"string"},"approval_mode":{"type":"string","enum":["autopilot","review_window","manual"]},"direction":{"type":["string","null"]},"configured":{"type":"boolean","description":"false = no cadence has ever been saved; the values shown are the kind-aware defaults."}},"required":["enabled","posts_per_week","timezone","approval_mode","direction","configured"]},"publishing":{"type":"object","properties":{"publish":{"type":"boolean"},"cms":{"type":"boolean"},"rebuild":{"type":"boolean"},"cms_provider":{"type":["string","null"]}},"required":["publish","cms","rebuild","cms_provider"]},"targets":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the org's active ranking targets are bound to (pressed on) this collection."},"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"steer_share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"],"description":"The share of write slots the tier steers at — `num` of every `den`: ramping 1 in 2, aggressive 2 in 3, steady 4 in 5."},"ramp":{"type":"object","properties":{"cited_pages":{"type":"number","description":"Distinct pages of this collection cited in AI answers."},"target_posts_published":{"type":"number","description":"Published posts on the targets' ground (their pillars)."},"next":{"anyOf":[{"type":"object","properties":{"tier":{"type":"string","enum":["ramping","aggressive","steady"]},"share":{"type":"object","properties":{"num":{"type":"number"},"den":{"type":"number"}},"required":["num","den"]},"cited_pages":{"type":"number","description":"Cited pages the next rung needs."},"target_posts":{"type":"number","description":"Published target posts the next rung needs."}},"required":["tier","share","cited_pages","target_posts"]},{"type":"null"}],"description":"The next rung and what it takes — it needs BOTH. null at the top rung."}},"required":["cited_pages","target_posts_published","next"],"description":"What the steering ramp reads: a rung needs both the citations and the posts to back it."},"recent_share":{"type":"object","properties":{"target":{"type":"number"},"of":{"type":"number"}},"required":["target","of"],"description":"What the schedule actually gave target ground: `target` of the last `of` committed slots. Below steer_share means target headlines ran short."},"bound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"},"cluster_id":{"type":["string","null"]},"status":{"type":"string","enum":["bound","proposed","dismissed"]}},"required":["target_id","phrase","cluster_id","status"]}},"unbound":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"}},"required":["target_id","phrase"]},"description":"Active org targets not bound here — what enabled:true would bind."},"cap":{"type":"number"}},"required":["enabled","tier","steer_share","ramp","recent_share","bound","unbound","cap"]},"measurement":{"type":"object","properties":{"geo_probing":{"type":"boolean"}},"required":["geo_probing"]},"sourcing":{"type":"object","properties":{"demand_focus":{"type":"string","enum":["balanced","seo","geo"]}},"required":["demand_focus"],"description":"How headline ranking weighs demand: 'seo' leans on search, 'geo' on AI answers, 'balanced' on both. Ranking targets keep the steering tier's share of slots under every focus."},"relevance":{"type":"object","properties":{"preset":{"type":"string","enum":["client","independent"],"description":"Whose defaults the dials start from: a main blog's or a phantom's."},"min_topic_relevance":{"type":"string","enum":["low","medium","high"]},"brand_gate":{"type":"string","enum":["off","lenient","strict","narrow"]},"min_brand_fit":{"anyOf":[{"type":"number","const":0},{"type":"number","const":0.5},{"type":"number","const":1}]},"pillar_breadth":{"type":"string","enum":["on_product","tangential"]},"auto_approve_pillars":{"type":"boolean"},"keyword_scope":{"type":"string","enum":["collection","org"]},"overridden":{"type":"array","items":{"type":"string"},"description":"The dials set on this collection; every other one follows the preset."}},"required":["preset","min_topic_relevance","brand_gate","min_brand_fit","pillar_breadth","auto_approve_pillars","keyword_scope","overridden"],"description":"How tightly the collection stays on the client's product (set_collection_config `relevance`; recheck_topic_relevance applies it to the pending topics)."},"disclosure":{"type":"object","properties":{"surfaces":{"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name override; null = the company name the context resolver gives.","type":["string","null"]},"sponsor":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"url":{"type":["string","null"]}},"required":["name","url"]},{"type":"null"}],"description":"Who the disclosure names (the label, else the resolved company), read-only; null on a client-brand collection, or where a label is required and missing."},"resolved_name":{"description":"The company the disclosure names when no sponsor_label is set, read-only; null on a client-brand collection and on internal/phantom_farm orgs, where a label is required.","type":["string","null"]},"last_change":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"note":{"type":["string","null"]},"actor_user_id":{"type":["string","null"]}},"required":["at","note","actor_user_id"]},{"type":"null"}],"description":"Who last changed the disclosure and why, read-only: from the collection's event log. An experiment's arm notes 'experiment <key> · <arm>'. null = never set."}},"required":["surfaces","sponsor_label","sponsor","resolved_name","last_change"],"description":"Sponsor disclosure ('safe mode'): where an independent publication says who sponsors it. Presentation only — added when a post is served (sponsor_disclosure on list_published/get_published/get_article), never written into the body, and it reaches every existing post on the next rebuild. Empty surfaces = off."},"custom_fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"generation_prompt":{"type":"string"},"shape":{"$ref":"#/$defs/__schema0"},"write_policy":{"type":"string","enum":["always","create_only"]},"enabled":{"type":"boolean"},"sort_order":{"type":"number"}},"required":["id","key","label","generation_prompt","shape","write_policy","enabled","sort_order"]},"description":"This collection's declared custom fields — set_collection_config's custom_fields section replaces the whole set."},"updated_at":{"type":"string"}},"required":["collection_id","public_id","name","description","identity","content","cadence","publishing","targets","measurement","sourcing","relevance","disclosure","custom_fields","updated_at"]},{"type":"null"}],"description":"The collection's file — the editorial half of the phantom."},"configuration":{"anyOf":[{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string"},"stage":{"type":"string"}},"required":["job_id","status","stage"]},{"type":"null"}],"description":"The latest configuration job (strategy → visuals → enhance → kernel → planner → map)."}},"required":["phantom","collection","configuration"]},"applied":{"type":"array","items":{"type":"string"}},"plan":{"description":"With configure + dry_run: the plan the job would apply."}},"required":["config","applied"],"$defs":{"__schema0":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_text"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"object"},"properties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","properties"]},{"type":"object","properties":{"kind":{"type":"string","const":"list_of_objects"},"item":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["kind","item"]}]}}}},{"name":"delete_phantom","description":"Tear a phantom down: the hosted site is removed (its domain released), its collection and posts are kept. Frees one phantom credit. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom UUID from list_phantoms."}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"phantom_id":{"type":"string"}},"required":["deleted","phantom_id"]}},{"name":"rebuild_phantom","description":"Redeploy a phantom's site so newly published posts appear (a collection run already rebuilds on publish; this is for content published another way). Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom UUID from list_phantoms."}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rebuild_started":{"type":"boolean","const":true},"phantom_id":{"type":"string"}},"required":["rebuild_started","phantom_id"]}},{"name":"set_phantom_paused","description":"Pause or resume a phantom. A paused site stays live and serving but takes no new work and spends nothing: no post is written or advanced (its upcoming slots are dropped and run_collection is refused), and it is not probed. Nothing is deleted and your probe switches are untouched, so resuming (paused: false) restores the site as it was and re-plans its slots. A paused site stops counting against your phantom credits, and resuming re-checks them. Read the state back as `paused` on get_phantom. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"paused":{"type":"boolean","description":"true pauses the site; false resumes it."}},"required":["phantom_id","paused"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]}},"required":["phantom"]}},{"name":"spread_phantom_looks","description":"Give each of your live phantoms a different look (redesign layouts and classic front pages), so sibling sites don't read as one template. Oldest sites keep theirs; palettes, fonts and logos never change. A dry run unless apply is true; apply rebuilds the changed sites. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"apply":{"description":"Write the new looks and rebuild the changed sites. Default false: return the plan only.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"applied":{"type":"boolean","description":"False for a dry run: nothing was written."},"distinct_before":{"type":"number","description":"Distinct looks across the org's live sites now."},"distinct_after":{"type":"number","description":"Distinct looks once the plan is applied."},"changes":{"type":"array","items":{"type":"object","properties":{"phantom_id":{"type":"string"},"name":{"type":"string"},"from":{"type":"string","description":"The current look: \"redesign:<layout>\" or \"classic:<home>\"."},"to":{"type":"string","description":"The planned look."},"theme_from":{"type":"string"},"theme_to":{"type":"string","description":"Differs from theme_from only when a classic look needs a theme that supplies it."},"redesign":{"type":"boolean"},"layout":{"type":"string"},"changed":{"type":"boolean"}},"required":["phantom_id","name","from","to","theme_from","theme_to","redesign","layout","changed"]}}},"required":["applied","distinct_before","distinct_after","changes"]}},{"name":"get_phantom_diagnostics","description":"Why is my site empty? — what is actually baked on the live site right now against what the collection has published, and the state of the site's content key. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom UUID from list_phantoms."}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collectionId":{"description":"Collection the phantom row points at.","type":["string","null"]},"publishedInCollection":{"type":"number","description":"Published articles available in that collection."},"publishedInOrg":{"type":"number","description":"Published articles across the whole org (context)."},"apiKey":{"type":"object","properties":{"present":{"type":"boolean"},"revoked":{"type":"boolean"},"hasArticleRead":{"type":"boolean"}},"required":["present","revoked","hasArticleRead"],"description":"State of the minted content key."},"vercel":{"type":"object","properties":{"apiUrl":{"type":["string","null"]},"collectionId":{"type":["string","null"]},"apiKeySet":{"type":"boolean"},"fetchError":{"type":"string"}},"required":["apiUrl","collectionId","apiKeySet"],"description":"What is ACTUALLY baked on the live Vercel project right now."},"deploymentUrl":{"type":["string","null"]},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"lastError":{"type":["string","null"]},"rebuildPendingSince":{"description":"Set when the last rebuild attempt failed and hasn't succeeded since — the live site may be serving stale content even though everything else here reads correct. Null means the last rebuild (if any) succeeded.","type":["string","null"]}},"required":["collectionId","publishedInCollection","publishedInOrg","apiKey","vercel","deploymentUrl","status","lastError","rebuildPendingSince"]}},{"name":"get_phantom_balance","description":"The org's phantom balance — SITES, not runs: how many hosted phantoms may be live at once (3 on the house), how many count now (live, not test, not paused), and the cadence cap (the most posts/week any collection may run at). When tokensDecide is true the account is billed in tokens: each live site costs a site-day (phantom_site_day), this cap does not apply, and get_billing is the balance to read. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"granted":{"type":"number","description":"Hosted sites the org may run at once."},"used":{"type":"number","description":"Live, non-test, unpaused sites right now."},"available":{"type":"number"},"cadence_cap":{"type":"number","description":"The most posts/week any collection may run at."},"tokensDecide":{"type":"boolean","description":"True when this account is billed on Lago: live sites bill per site-day and this cap doesn't limit it. A free account keeps the cap."}},"required":["granted","used","available","cadence_cap","tokensDecide"]}},{"name":"check_domain","description":"Price a domain for a phantom (read-only, no charge): availability, first-year and renewal prices, and whether buy_domain would accept it under the per-domain price cap. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Bare domain to price, e.g. myblog.com (no scheme)."}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"domain":{"type":"string","description":"Normalized domain that was quoted."},"available":{"type":"boolean","description":"Whether the domain is available to register."},"purchasePrice":{"description":"Server-side first-year purchase price (USD), null if unpriced.","type":["number","null"]},"renewalPrice":{"description":"Server-side yearly renewal price (USD), null if unpriced.","type":["number","null"]},"withinPriceCap":{"type":"boolean","description":"Whether the purchase price clears Letter's per-domain cap."},"maxPriceUsd":{"type":"number","description":"The per-domain price cap (USD)."},"purchasable":{"type":"boolean","description":"available AND withinPriceCap — whether buy_domain would be accepted."}},"required":["ok","domain","available","purchasePrice","renewalPrice","withinPriceCap","maxPriceUsd","purchasable"]}},{"name":"buy_domain","description":"Buy a domain and attach it to a live phantom. SPENDS REAL MONEY (price under $30; an org may hold 3 custom domains unless staff set another cap; propose_phantom_plan shows the headroom) — check_domain first. The site moves to the DNS phase and goes live on the domain once it resolves. Passing the phantom's OWN recorded domain completes a purchase that never went through (the domain is still available), including for a phantom that errored with 'Domain purchase failed'; if that domain is already registered it is refused. Requires capability phantom:domain.","capability":"phantom:domain","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A live phantom with no custom domain yet."},"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Bare domain to buy, e.g. myblog.com (no scheme)."}},"required":["phantom_id","domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]}},"required":["phantom"]}},{"name":"connect_domain","description":"Attach a domain you already own to a live phantom — no purchase. The response's dns carries the record to add; the site goes live on the domain once it resolves. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A live phantom."},"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Bare domain you already own. Attaches it — no purchase."}},"required":["phantom_id","domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"phantom":{"type":"object","properties":{"phantom_id":{"type":"string"},"public_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"status":{"type":"string","enum":["provisioning","awaiting_dns","live","error","deleting"]},"phase":{"type":["string","null"]},"steps":{},"collection_id":{"description":"The collection the site renders — run_collection on it to publish a post.","type":["string","null"]},"customer_ref":{"type":["string","null"]},"subdomain":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{},{"type":"null"}]},"paused":{"type":"boolean"},"is_test":{"type":"boolean"},"demo_expires_at":{"type":["string","null"]},"redesign":{"type":"boolean","description":"The redesigned blog is this site's design (set_phantom_config redesign)."},"layout":{"description":"The chosen redesign layout; null when nobody chose one (resolved_layout is what builds use).","type":["string","null"]},"resolved_layout":{"type":"string","description":"The layout the site builds with: the chosen one, else the theme's suggestion."},"look":{"type":"string","description":"How the front page is set: \"redesign:<layout>\" or \"classic:<home>\". spread_phantom_looks spreads these."},"style":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","null"]}},{"type":"null"}],"description":"The site's authored palette and fonts (accentColor, backgroundColor, … fontHeading, fontBody)."},"maverick_enabled":{"type":"boolean","description":"Visitor identification is opted in (set_phantom_config)."},"maverick_customer_id":{"description":"The identification customer id recorded when enabling auto-registered the site. null while enabled = pending registration (nothing identified yet; re-enable retries).","type":["string","null"]},"last_error":{"type":["string","null"]},"resolved_from_composition":{"description":"The primitive_compositions.key create_phantom was given, when any. Not a live pointer — the composition may since have changed or been deleted.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["phantom_id","public_id","name","description","theme","status","phase","steps","collection_id","customer_ref","subdomain","domain","url","dns","paused","is_test","demo_expires_at","redesign","layout","resolved_layout","look","style","maverick_enabled","maverick_customer_id","last_error","resolved_from_composition","created_at","updated_at"]}},"required":["phantom"]}},{"name":"export_org_config","description":"The organization's whole configuration as ONE JSON document — an `Organization` bundle (apiVersion letterstory.com/org-config/v1) whose spec mirrors the app's menus: `settings` (identity, branding, appearance, default kernel), `primitives` (phantoms, kernels with their endpoints, arenas) and `data` (context: positioning, offerings, competitors; collections with their enhance file; templates; compositions). Records are keyed by name, never id, and each carries its own `kind`. Primitive files export the LIVE version: a customer's own copy inline, a platform version by reference. Config only — no runs, history, queues or credentials; a value under a secret-looking key is replaced by `ref:redacted` and listed in metadata.redacted. metadata.pending lists config sections no reader exports yet; metadata.warnings lists parts that could not be read. Pass `sections` to read only some. Docs: docs/architecture/org-config.md. Requires capability org_config:read.","capability":"org_config:read","inputSchema":{"type":"object","properties":{"sections":{"description":"Only these sections (default: every section with a reader).","type":"array","items":{"type":"string","enum":["settings","primitives.phantoms","primitives.kernels","primitives.seers","primitives.shredders","primitives.arenas","data.context","data.collections","data.templates","data.compositions","primitives.ledes","primitives.mailers","primitives.prospects","primitives.dispatches","data.sequences","data.landings","data.canvasTypes","data.profiles","tools.measure"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"apiVersion":{"type":"string","const":"letterstory.com/org-config/v1"},"kind":{"type":"string","const":"Organization"},"metadata":{"type":"object","properties":{"org":{"type":"string","description":"The org's slug."},"exportedAt":{"type":"string"},"source":{"type":"string","description":"The Letter origin the bundle was read from."},"sections":{"type":"array","items":{"type":"string"},"description":"The sections this bundle carries."},"pending":{"type":"array","items":{"type":"string"},"description":"Config sections no reader exports yet."},"redacted":{"type":"array","items":{"type":"string"},"description":"Paths whose value looked like a secret and was replaced by `ref:redacted`."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts that could not be read (e.g. the kernels service was unreachable)."}},"required":["org","exportedAt","source","sections","pending","redacted","warnings"]},"spec":{"type":"object","properties":{"settings":{"type":"object","properties":{"organization":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"branding":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"appearance":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"defaultKernel":{"type":["string","null"]},"reviewers":{"description":"Standing reviewers: invited to review every approval.","type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["email","name"]}},"mail":{"description":"Org-wide mail settings.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["organization","branding","appearance","defaultKernel"],"additionalProperties":{}},"primitives":{"type":"object","properties":{"phantoms":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Phantom"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Phantom, keyed by name."}},"kernels":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Kernel"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Kernel, keyed by name."}},"seers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Seer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Seer, keyed by name."}},"shredders":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Shredder"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Shredder, keyed by name."}},"arenas":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Arena"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Arena, keyed by name."}},"ledes":{"type":"object","properties":{"library":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Lede"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Lede, keyed by name."}},"endpoints":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"LedeEndpoint"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One LedeEndpoint, keyed by name."}}},"required":["library","endpoints"]},"mailers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Mailer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Mailer, keyed by name."}},"prospects":{"description":"Prospect lists, columns and enclosures (the generators that fill columns) — never the prospects themselves.","type":"object","properties":{"lists":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectList"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectList, keyed by name."}},"columns":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectColumn"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectColumn, keyed by name."}},"enclosures":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Enclosure"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Enclosure, keyed by name."}}},"required":["lists","columns"]},"dispatches":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Dispatch"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Dispatch, keyed by name."}}},"additionalProperties":{}},"data":{"type":"object","properties":{"context":{"type":"object","properties":{"positioning":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"offerings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Offering"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Offering, keyed by name."}},"competitors":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Competitor"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Competitor, keyed by name."}},"brandProfiles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"BrandProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One BrandProfile, keyed by name."}},"sourceRoles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SourceRole"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SourceRole, keyed by name."}},"memory":{"description":"Custom memory sections, by slug, with their entries.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"MemorySection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One MemorySection, keyed by name."}}},"required":["positioning","offerings","competitors"]},"collections":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Collection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Collection, keyed by name."}},"templates":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Template"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Template, keyed by name."}},"compositions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Composition"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Composition, keyed by name."}},"sequences":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Sequence"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Sequence, keyed by name."}},"landings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Landing"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Landing, keyed by name."}},"canvasTypes":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"CanvasType"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One CanvasType, keyed by name."}},"profiles":{"description":"Social publishing personas.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SocialProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SocialProfile, keyed by name."}}},"additionalProperties":{}},"tools":{"type":"object","properties":{"measure":{"type":"object","properties":{"rankingTargets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"RankingTarget"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One RankingTarget, keyed by name."}},"topics":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"TrackedTopic"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One TrackedTopic, keyed by name."}},"keywordExclusions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"KeywordExclusion"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One KeywordExclusion, keyed by name."}},"native":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"cap":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["rankingTargets","topics","keywordExclusions","native"]}},"additionalProperties":{}}},"required":["primitives","data","tools"]}},"required":["apiVersion","kind","metadata","spec"],"description":"The org's configuration as one `Organization` bundle."}},"required":["config"]}},{"name":"describe_org_config_fields","description":"What each setting in an export_org_config bundle means: per section, the fields the Instances → Config editor shows — label, dot path inside the record, how it is edited (toggle / number / choice with labelled options / text / list / ref to another record by name), whether Simple mode shows it, when it applies (a capability field like identity.brand_subject, never profile), the apply gate a change needs (sends / spends / destroys), and why a field is read-only. Use it to turn \"3 posts a week, I approve each one\" into the leaf paths to change, then diff_org_config / apply_org_config the edited bundle. Fields not listed are still config: edit them as JSON. Docs: docs/architecture/config-editor.md. Requires capability org_config:read.","capability":"org_config:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},"description":"Where instances sit, in order."},"sections":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The bundle section (spec.<path>)."},"noun":{"type":"string"},"group":{"type":"string"},"singleton":{"type":"boolean"},"forkable":{"type":"boolean"}},"required":["path","noun","group"]},"description":"Every section the editor lists, and whether its records can be forked."},"fields":{"type":"array","items":{"type":"object","properties":{"section":{"type":"string"},"path":{"type":"string","description":"Dot path inside the record."},"label":{"type":"string"},"help":{"type":"string"},"control":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"How it is edited: toggle, number, choice, text, list, ref."},"simple":{"description":"Shown in Simple mode.","type":"boolean"},"group":{"type":"string"},"appliesWhen":{"description":"Shown only for records whose field at `path` is one of `in`.","type":"object","properties":{"path":{"type":"string"},"in":{"type":"array","items":{}}},"required":["path","in"]},"readOnly":{"description":"Why it is shown but not written.","type":"string"},"gate":{"description":"The apply_org_config gate a change to it needs.","type":"string"}},"required":["section","path","label","control","group"]},"description":"The settings, labelled. Anything else in a record is edited as JSON."}},"required":["groups","sections","fields"]}},{"name":"diff_org_config","description":"Plan an `Organization` bundle against the org's live config — what applying it would change, without changing anything. Pass `config` (an edited export_org_config result, or only some of its sections: only sections present in it are compared). Records match by name: `add` (in the bundle only), `change` (with leaf-level `fields`, live → bundle; arrays compare whole) and `remove` (in the org only — apply leaves these alone unless pruning). A `ref:redacted` value is never a change. Warnings flag sections that could not be compared. Docs: docs/architecture/org-config.md. Requires capability org_config:read.","capability":"org_config:read","inputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"apiVersion":{"type":"string","const":"letterstory.com/org-config/v1"},"kind":{"type":"string","const":"Organization"},"metadata":{"type":"object","properties":{"org":{"type":"string","description":"The org's slug."},"exportedAt":{"type":"string"},"source":{"type":"string","description":"The Letter origin the bundle was read from."},"sections":{"type":"array","items":{"type":"string"},"description":"The sections this bundle carries."},"pending":{"type":"array","items":{"type":"string"},"description":"Config sections no reader exports yet."},"redacted":{"type":"array","items":{"type":"string"},"description":"Paths whose value looked like a secret and was replaced by `ref:redacted`."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts that could not be read (e.g. the kernels service was unreachable)."}},"required":["org","exportedAt","source","sections","pending","redacted","warnings"]},"spec":{"type":"object","properties":{"settings":{"type":"object","properties":{"organization":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"branding":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"appearance":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"defaultKernel":{"type":["string","null"]},"reviewers":{"description":"Standing reviewers: invited to review every approval.","type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["email","name"]}},"mail":{"description":"Org-wide mail settings.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["organization","branding","appearance","defaultKernel"],"additionalProperties":{}},"primitives":{"type":"object","properties":{"phantoms":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Phantom"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Phantom, keyed by name."}},"kernels":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Kernel"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Kernel, keyed by name."}},"seers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Seer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Seer, keyed by name."}},"shredders":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Shredder"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Shredder, keyed by name."}},"arenas":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Arena"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Arena, keyed by name."}},"ledes":{"type":"object","properties":{"library":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Lede"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Lede, keyed by name."}},"endpoints":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"LedeEndpoint"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One LedeEndpoint, keyed by name."}}},"required":["library","endpoints"]},"mailers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Mailer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Mailer, keyed by name."}},"prospects":{"description":"Prospect lists, columns and enclosures (the generators that fill columns) — never the prospects themselves.","type":"object","properties":{"lists":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectList"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectList, keyed by name."}},"columns":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectColumn"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectColumn, keyed by name."}},"enclosures":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Enclosure"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Enclosure, keyed by name."}}},"required":["lists","columns"]},"dispatches":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Dispatch"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Dispatch, keyed by name."}}},"additionalProperties":{}},"data":{"type":"object","properties":{"context":{"type":"object","properties":{"positioning":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"offerings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Offering"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Offering, keyed by name."}},"competitors":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Competitor"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Competitor, keyed by name."}},"brandProfiles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"BrandProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One BrandProfile, keyed by name."}},"sourceRoles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SourceRole"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SourceRole, keyed by name."}},"memory":{"description":"Custom memory sections, by slug, with their entries.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"MemorySection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One MemorySection, keyed by name."}}},"required":["positioning","offerings","competitors"]},"collections":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Collection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Collection, keyed by name."}},"templates":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Template"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Template, keyed by name."}},"compositions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Composition"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Composition, keyed by name."}},"sequences":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Sequence"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Sequence, keyed by name."}},"landings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Landing"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Landing, keyed by name."}},"canvasTypes":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"CanvasType"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One CanvasType, keyed by name."}},"profiles":{"description":"Social publishing personas.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SocialProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SocialProfile, keyed by name."}}},"additionalProperties":{}},"tools":{"type":"object","properties":{"measure":{"type":"object","properties":{"rankingTargets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"RankingTarget"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One RankingTarget, keyed by name."}},"topics":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"TrackedTopic"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One TrackedTopic, keyed by name."}},"keywordExclusions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"KeywordExclusion"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One KeywordExclusion, keyed by name."}},"native":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"cap":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["rankingTargets","topics","keywordExclusions","native"]}},"additionalProperties":{}}},"required":["primitives","data","tools"]}},"required":["apiVersion","kind","metadata","spec"],"description":"The desired `Organization` bundle — e.g. an edited export_org_config result, or only some of its sections."}},"required":["config"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"plan":{"type":"object","properties":{"org":{"type":"string"},"sections":{"type":"array","items":{"type":"string"},"description":"The sections compared — those present in the bundle."},"changes":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"],"description":"`remove`: in the org but not the bundle — apply leaves it unless pruning."},"section":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Leaf changes, live → bundle (`change` only)."}},"required":["op","section","kind","name","fields"]}},"summary":{"type":"object","properties":{"add":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["add","change","remove","unchanged"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["org","sections","changes","summary","warnings"],"description":"What applying the bundle would change."}},"required":["plan"]}},{"name":"apply_org_config","description":"Apply an `Organization` bundle to the org — diff_org_config's plan, executed record by record through each section's own service. Writes settings (including CMS field mappings and Google Docs folders; which integrations are connected is read-only), collection ranking weights, positioning, templates, compositions, dispatches, landings, prospect lists, sequences (never activated), mailer limits, Measure targets/probes/exclusions, kernel/arena files (arenas are also created, retuned and, with prune, deleted), kernel endpoints, canvas types, collection enhance files and the main collection, kernel descriptions (archived with prune), keyword-exclusion removals (prune), the mail postal address and the company ranking-target cap, phantoms, collections (with offerings, context, authors and competitor opinions), offerings, competitors and source roles; anything else is listed in `skipped` with a reason. Additive by default: records only in the org are deleted only with `prune: true`. Run with `dry_run: true` first — it returns the same result and writes nothing. Gated changes (`result.gated`) are held back unless an org admin passes `allow` (spends = create a phantom, launch a mailbox, buy a phantom's domain (buyDomain: true); sends = turn a sequence or a collection planner on, start a mailbox warming; destroys = connect a phantom to a domain you own, delete a phantom, collection or canvas type, archive a sequence, retire a mailbox; access = invite, remove or change the role of a member in settings.members) together with the dry run's `confirm` hash. A `ref:redacted` value keeps the live value on update and is refused on create. A failed record is listed in `failed` and the rest still apply; re-running converges. Docs: docs/architecture/org-config.md. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"apiVersion":{"type":"string","const":"letterstory.com/org-config/v1"},"kind":{"type":"string","const":"Organization"},"metadata":{"type":"object","properties":{"org":{"type":"string","description":"The org's slug."},"exportedAt":{"type":"string"},"source":{"type":"string","description":"The Letter origin the bundle was read from."},"sections":{"type":"array","items":{"type":"string"},"description":"The sections this bundle carries."},"pending":{"type":"array","items":{"type":"string"},"description":"Config sections no reader exports yet."},"redacted":{"type":"array","items":{"type":"string"},"description":"Paths whose value looked like a secret and was replaced by `ref:redacted`."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts that could not be read (e.g. the kernels service was unreachable)."}},"required":["org","exportedAt","source","sections","pending","redacted","warnings"]},"spec":{"type":"object","properties":{"settings":{"type":"object","properties":{"organization":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"branding":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"appearance":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"defaultKernel":{"type":["string","null"]},"reviewers":{"description":"Standing reviewers: invited to review every approval.","type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["email","name"]}},"mail":{"description":"Org-wide mail settings.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["organization","branding","appearance","defaultKernel"],"additionalProperties":{}},"primitives":{"type":"object","properties":{"phantoms":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Phantom"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Phantom, keyed by name."}},"kernels":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Kernel"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Kernel, keyed by name."}},"seers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Seer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Seer, keyed by name."}},"shredders":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Shredder"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Shredder, keyed by name."}},"arenas":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Arena"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Arena, keyed by name."}},"ledes":{"type":"object","properties":{"library":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Lede"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Lede, keyed by name."}},"endpoints":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"LedeEndpoint"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One LedeEndpoint, keyed by name."}}},"required":["library","endpoints"]},"mailers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Mailer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Mailer, keyed by name."}},"prospects":{"description":"Prospect lists, columns and enclosures (the generators that fill columns) — never the prospects themselves.","type":"object","properties":{"lists":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectList"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectList, keyed by name."}},"columns":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectColumn"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectColumn, keyed by name."}},"enclosures":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Enclosure"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Enclosure, keyed by name."}}},"required":["lists","columns"]},"dispatches":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Dispatch"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Dispatch, keyed by name."}}},"additionalProperties":{}},"data":{"type":"object","properties":{"context":{"type":"object","properties":{"positioning":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"offerings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Offering"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Offering, keyed by name."}},"competitors":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Competitor"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Competitor, keyed by name."}},"brandProfiles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"BrandProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One BrandProfile, keyed by name."}},"sourceRoles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SourceRole"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SourceRole, keyed by name."}},"memory":{"description":"Custom memory sections, by slug, with their entries.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"MemorySection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One MemorySection, keyed by name."}}},"required":["positioning","offerings","competitors"]},"collections":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Collection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Collection, keyed by name."}},"templates":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Template"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Template, keyed by name."}},"compositions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Composition"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Composition, keyed by name."}},"sequences":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Sequence"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Sequence, keyed by name."}},"landings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Landing"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Landing, keyed by name."}},"canvasTypes":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"CanvasType"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One CanvasType, keyed by name."}},"profiles":{"description":"Social publishing personas.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SocialProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SocialProfile, keyed by name."}}},"additionalProperties":{}},"tools":{"type":"object","properties":{"measure":{"type":"object","properties":{"rankingTargets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"RankingTarget"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One RankingTarget, keyed by name."}},"topics":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"TrackedTopic"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One TrackedTopic, keyed by name."}},"keywordExclusions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"KeywordExclusion"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One KeywordExclusion, keyed by name."}},"native":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"cap":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["rankingTargets","topics","keywordExclusions","native"]}},"additionalProperties":{}}},"required":["primitives","data","tools"]}},"required":["apiVersion","kind","metadata","spec"],"description":"The desired `Organization` bundle — e.g. an edited export_org_config result, or only some of its sections."},"dry_run":{"description":"true = return what would be applied and write nothing. Run this first.","type":"boolean"},"prune":{"description":"true = also DELETE records in the org that the bundle lacks (in the sections it carries). Default false: apply only adds and updates.","type":"boolean"},"allow":{"description":"Gated changes to allow (org admins only): spends = create a phantom (provisions a site, uses a phantom credit); sends = turn a sequence on; destroys = delete a phantom, collection or canvas type, archive a sequence, retire a mailbox (deletes also need prune; a sequence's status archived does not). Run dry_run with the same allow first, then pass its `confirm`.","type":"array","items":{"type":"string","enum":["spends","sends","destroys","access"]}},"confirm":{"description":"The `confirm` hash a dry run returned. Gated changes apply only if the plan still hashes to it.","type":"string"}},"required":["config"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"result":{"type":"object","properties":{"plan":{"type":"object","properties":{"org":{"type":"string"},"sections":{"type":"array","items":{"type":"string"},"description":"The sections compared — those present in the bundle."},"changes":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"],"description":"`remove`: in the org but not the bundle — apply leaves it unless pruning."},"section":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Leaf changes, live → bundle (`change` only)."}},"required":["op","section","kind","name","fields"]}},"summary":{"type":"object","properties":{"add":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["add","change","remove","unchanged"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["org","sections","changes","summary","warnings"],"description":"What applying the bundle would change."},"dryRun":{"type":"boolean"},"prune":{"type":"boolean"},"applied":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"}},"required":["op","section","name"]},"description":"Written — or, on a dry run, what would be written."},"skipped":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"reason":{"type":"string"}},"required":["op","section","name","reason"]}},"failed":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"error":{"type":"string"}},"required":["op","section","name","error"]}},"gated":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"gate":{"type":"string","enum":["spends","sends","destroys","access"]}},"required":["op","section","name","gate"]},"description":"Changes held behind a gate: applied only when `allow` names the gate and `confirm` matches."},"confirm":{"description":"The hash of the gated changes, to pass back as `confirm`.","type":["string","null"]}},"required":["plan","dryRun","prune","applied","skipped","failed","gated","confirm"],"description":"What the apply did (or, on a dry run, would do)."}},"required":["result"]}},{"name":"get_org_config_source","description":"The GitHub repo this org keeps its config in (GitOps), if any: repo, branch, path, mode (`plan` posts each new commit's plan to Slack; `apply` applies it), and the last sync — the commit seen, the last commit applied cleanly, its result or error. Null when no repo is connected. Docs: docs/architecture/org-config.md. Requires capability org_config:read.","capability":"org_config:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"source":{"anyOf":[{"type":"object","properties":{"repo":{"type":"string","description":"`owner/name`."},"branch":{"type":"string"},"path":{"type":"string"},"mode":{"type":"string","enum":["plan","apply"]},"prune":{"type":"boolean"},"last_seen_sha":{"description":"The branch head last planned or applied.","type":["string","null"]},"last_applied_sha":{"description":"The last commit applied with no failures.","type":["string","null"]},"last_synced_at":{"type":["string","null"]},"last_result":{},"last_error":{"type":["string","null"]},"drift_checked_at":{"type":["string","null"]}},"required":["repo","branch","path","mode","prune","last_seen_sha","last_applied_sha","last_synced_at","last_result","last_error","drift_checked_at"],"description":"The org's config repo and its last sync."},{"type":"null"}]}},"required":["source"]}},{"name":"connect_org_config_source","description":"Keep this org's config in a GitHub repo (GitOps). Point it at `repo` (`owner/name`), `branch` (default main) and `path` — a bundle file (default `letterstory/org.json`, an export_org_config result) or an `org export --split` directory. The org's GitHub connection reads it, and the bundle at the branch head is validated now. Every ~15 minutes a new commit is then planned against the live org and posted to Slack (`mode: plan`, the default) or applied as the key's creator (`mode: apply`; deletes only with `prune: true`), and once a day apply mode posts drift — live changes made outside git. Replaces any earlier connection. Org admins only. Docs: docs/architecture/org-config.md. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"repo":{"type":"string","pattern":"^[\\w.-]+\\/[\\w.-]+$","description":"The GitHub repo holding the bundle, `owner/name`. Read with the org's GitHub connection."},"branch":{"description":"The branch to follow (default `main`).","type":"string","minLength":1},"path":{"description":"A bundle file (`letterstory/org.json`, the default) or an `org export --split` directory.","type":"string","minLength":1},"mode":{"description":"`plan` (default): each new commit is planned and the plan posted to Slack, nothing applied. `apply`: each new commit is applied, as you.","type":"string","enum":["plan","apply"]},"prune":{"description":"Apply mode only: also DELETE records the committed bundle lacks. Default false.","type":"boolean"}},"required":["repo"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"source":{"type":"object","properties":{"repo":{"type":"string","description":"`owner/name`."},"branch":{"type":"string"},"path":{"type":"string"},"mode":{"type":"string","enum":["plan","apply"]},"prune":{"type":"boolean"},"last_seen_sha":{"description":"The branch head last planned or applied.","type":["string","null"]},"last_applied_sha":{"description":"The last commit applied with no failures.","type":["string","null"]},"last_synced_at":{"type":["string","null"]},"last_result":{},"last_error":{"type":["string","null"]},"drift_checked_at":{"type":["string","null"]}},"required":["repo","branch","path","mode","prune","last_seen_sha","last_applied_sha","last_synced_at","last_result","last_error","drift_checked_at"],"description":"The org's config repo and its last sync."},"head":{"type":"string","description":"The branch head read at connect time; the next sync plans or applies it."},"org":{"type":"string","description":"The org slug the committed bundle names."}},"required":["source","head","org"]}},{"name":"sync_org_config_source","description":"Sync the org's config repo now instead of waiting for the ~15-minute poll: plan (or, in apply mode, apply) the branch head if it moved since the last sync — `force: true` re-runs it even if not. Returns the plan or the apply result, posted to Slack as well. In apply mode only an org admin may sync by hand. Docs: docs/architecture/org-config.md. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"force":{"description":"Re-run the branch head even if it was already synced.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"outcome":{"type":"object","properties":{"status":{"type":"string","enum":["unchanged","planned","applied","error"]},"sha":{"type":["string","null"]},"plan":{"type":"object","properties":{"org":{"type":"string"},"sections":{"type":"array","items":{"type":"string"},"description":"The sections compared — those present in the bundle."},"changes":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"],"description":"`remove`: in the org but not the bundle — apply leaves it unless pruning."},"section":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Leaf changes, live → bundle (`change` only)."}},"required":["op","section","kind","name","fields"]}},"summary":{"type":"object","properties":{"add":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["add","change","remove","unchanged"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["org","sections","changes","summary","warnings"],"description":"What applying the bundle would change."},"result":{"type":"object","properties":{"plan":{"type":"object","properties":{"org":{"type":"string"},"sections":{"type":"array","items":{"type":"string"},"description":"The sections compared — those present in the bundle."},"changes":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"],"description":"`remove`: in the org but not the bundle — apply leaves it unless pruning."},"section":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Leaf changes, live → bundle (`change` only)."}},"required":["op","section","kind","name","fields"]}},"summary":{"type":"object","properties":{"add":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["add","change","remove","unchanged"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["org","sections","changes","summary","warnings"],"description":"What applying the bundle would change."},"dryRun":{"type":"boolean"},"prune":{"type":"boolean"},"applied":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"}},"required":["op","section","name"]},"description":"Written — or, on a dry run, what would be written."},"skipped":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"reason":{"type":"string"}},"required":["op","section","name","reason"]}},"failed":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"error":{"type":"string"}},"required":["op","section","name","error"]}},"gated":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"gate":{"type":"string","enum":["spends","sends","destroys","access"]}},"required":["op","section","name","gate"]},"description":"Changes held behind a gate: applied only when `allow` names the gate and `confirm` matches."},"confirm":{"description":"The hash of the gated changes, to pass back as `confirm`.","type":["string","null"]}},"required":["plan","dryRun","prune","applied","skipped","failed","gated","confirm"],"description":"What the apply did (or, on a dry run, would do)."},"error":{"type":"string"}},"required":["status","sha"],"description":"What the sync did with the branch head."}},"required":["outcome"]}},{"name":"disconnect_org_config_source","description":"Stop keeping this org's config in git: forget the connected repo. Nothing in the org or the repo changes. Org admins only. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean"}},"required":["disconnected"]}},{"name":"list_compositions","description":"List the org's primitive compositions — named bundles create_phantom's `composition` field can name by key. Requires capability composition:read.","capability":"composition:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"compositions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The stable handle — what create_phantom's composition.key names."},"name":{"type":"string"},"description":{"type":["string","null"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider","mode","config"]},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","key","name","description","content","phantom","seer","created_at","updated_at"]}}},"required":["compositions"]}},{"name":"get_composition","description":"Fetch one primitive composition: its content section (kernel, shred, template), phantom defaults, and optional seer spec. Requires capability composition:read.","capability":"composition:read","inputSchema":{"type":"object","properties":{"composition_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["composition_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"composition":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The stable handle — what create_phantom's composition.key names."},"name":{"type":"string"},"description":{"type":["string","null"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider","mode","config"]},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","key","name","description","content","phantom","seer","created_at","updated_at"]}},"required":["composition"]}},{"name":"resolve_composition","description":"Turn a composition into the sections create_phantom's `composition` field would merge in — its content (kernel, shred, shredder endpoint, content template), phantom defaults (theme), and seer spec, if any. Read-only: costs nothing, changes nothing. Useful to preview a bundle, or to build the same sections yourself for a door that doesn't take `composition` directly yet. Requires capability composition:read.","capability":"composition:read","inputSchema":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":64}},"required":["key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"key":{"type":"string"},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}},"description":"What create_phantom's config.content would merge under, unless overridden field by field."},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}},"description":"What create_phantom's theme would merge under."},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider","mode","config"]},{"type":"null"}],"description":"The seer create_phantom would stand up, unless skip_seer is set. null = none."}},"required":["key","content","phantom","seer"]}},{"name":"create_composition","description":"Create a primitive composition — a named bundle of primitive references. `content` is a collection's own content section (kernel_family, shred, shredder_endpoint_id, template_key, mention_degree, cover, visuals, verify, research_depth). `phantom` seeds create_phantom's theme. The org-unique key is generated server-side. Requires capability composition:write.","capability":"composition:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"content":{"description":"Omitted = an empty content section (no defaults).","type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"description":"Omitted = no phantom defaults.","type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider"]},{"type":"null"}]}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"composition":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The stable handle — what create_phantom's composition.key names."},"name":{"type":"string"},"description":{"type":["string","null"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider","mode","config"]},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","key","name","description","content","phantom","seer","created_at","updated_at"]}},"required":["composition"]}},{"name":"update_composition","description":"Replace fields on a composition (name, description, content, phantom, seer). Its key never changes. Requires capability composition:write.","capability":"composition:write","inputSchema":{"type":"object","properties":{"composition_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string","minLength":1,"maxLength":200},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider"]},{"type":"null"}]}},"required":["composition_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"composition":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The stable handle — what create_phantom's composition.key names."},"name":{"type":"string"},"description":{"type":["string","null"]},"content":{"type":"object","properties":{"research_depth":{"type":"string","enum":["barebones","fleshed_out"]},"kernel_family":{"description":"Writing-kernel family (list_kernels). null = the org's default.","anyOf":[{"type":"string","minLength":1,"maxLength":80},{"type":"null"}]},"verify":{"type":"boolean"},"shred":{"type":"boolean"},"shredder_endpoint_id":{"description":"Which shredder endpoint the in-loop shred is attributed to. null clears it back to the org's default, provisioned on first use. Setting this does not itself turn shredding on — see shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"visuals":{"type":"boolean"},"cover":{"description":"Which of the org's canvas kinds mints covers; 'none' clears it.","type":"string","enum":["editorial_image","full_ai","line_art","none"]},"mention_degree":{"type":"string","enum":["off","subtle","woven"]},"mention_choice":{"description":"How the planner picks which of this collection's offerings (set_collection_offerings) a topic's post promotes. A partial override of the preset — omitted keys keep their current value; null resets everything to the preset. Read the resolved values back from get_collection_config.","anyOf":[{"type":"object","properties":{"field_weights":{"description":"Points per topic word matched in an offering's name / priority topics / identity","type":"object","properties":{"name":{"type":"number","minimum":0,"maximum":10},"priority_topics":{"type":"number","minimum":0,"maximum":10},"identity":{"type":"number","minimum":0,"maximum":10}}},"name_phrase_bonus":{"description":"Extra points when the topic names an offering (or alias) verbatim","type":"number","minimum":0,"maximum":20},"primary_bonus":{"description":"Head start for the collection's PRIMARY offering — another must out-fit it by more than this to take a topic. 0 = pure fit","type":"number","minimum":0,"maximum":20}}},{"type":"null"}]},"template_key":{"description":"Content template binding; null clears.","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"auto_start":{"description":"Start writing as soon as a post is queued from Curate.","type":"boolean"},"research":{"description":"Run the research step before the kernel writes.","type":"boolean"},"direction_check":{"description":"Pause after research so a person picks the article's direction.","type":"boolean"},"write":{"description":"Run the kernel's writing step.","type":"boolean"},"words":{"description":"Target length in words of prose; null = the measured default.","anyOf":[{"type":"integer","minimum":200,"maximum":6000},{"type":"null"}]},"tables":{"description":"Comparison tables in posts.","type":"boolean"},"diagrams":{"description":"Free-form diagrams in posts.","type":"boolean"},"external_links":{"description":"Add external sources to posts.","type":"boolean"},"metadata":{"description":"Generate JSON-LD, meta tags and llms.txt for each post.","type":"object","properties":{"enabled":{"type":"boolean"},"json_ld":{"type":"boolean"},"meta_tags":{"type":"boolean"},"llms_txt":{"type":"boolean"}}},"relink":{"description":"Keep internal links in published posts fresh.","type":"boolean"},"additional_sitemaps":{"description":"Other sites' sitemaps the internal-links pass may also link into.","maxItems":20,"type":"array","items":{"type":"string","maxLength":2000,"format":"uri"}},"preferred_sources":{"description":"Pages and sites research reads first.","maxItems":20,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"A site (https://zip.security). A URL with a path is read as its site."},"note":{"description":"What the page holds and when it is worth citing. For people; never sent to the model.","anyOf":[{"type":"string","maxLength":300},{"type":"null"}]}},"required":["url"]}},"client_one_in":{"description":"At most one in this many published posts cites the client's own site; null = no ceiling.","anyOf":[{"type":"integer","minimum":2,"maximum":50},{"type":"null"}]},"voice":{"description":"The editorial person posts are written in, and the byline.","type":"object","properties":{"person":{"type":"string","enum":["third","first_plural","first_singular"]},"author_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"allow_experience_claims":{"type":"boolean"}}},"mention_rate":{"description":"Independent sites: the share of topics (0-100%) that name the client; null = every topic.","anyOf":[{"type":"integer","minimum":0,"maximum":100},{"type":"null"}]},"mention_landings":{"description":"Weave relevant landing-page links into posts.","type":"boolean"},"styles":{"description":"Canvas types (by name, data.canvasTypes) inline visuals are drawn with; null = the org default.","type":"object","properties":{"diagram":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"table":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"venn":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}}}}},"phantom":{"type":"object","properties":{"theme":{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]}}},"seer":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"provider":{"type":"string","enum":["github_pr","github_release","notion_activity","spec_change","news_trend","regulation_change","reddit_top"]},"mode":{"default":"suggest","type":"string","enum":["suggest","auto"]},"poll_interval_minutes":{"type":"integer","minimum":5,"maximum":10080},"config":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name","provider","mode","config"]},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","key","name","description","content","phantom","seer","created_at","updated_at"]}},"required":["composition"]}},{"name":"delete_composition","description":"Delete a composition. Non-destructive to anything already built from it: a phantom created from it keeps what was resolved at creation time — its `resolved_from_composition` becomes a name pointing at nothing, not a broken reference. Requires capability composition:write.","capability":"composition:write","inputSchema":{"type":"object","properties":{"composition_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["composition_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}},{"name":"plan_phantom","description":"Letter's launch recipe, from a CLIENT domain: reads the homepage, infers positioning, proposes a client-free masthead and description, decides cadence/cover/seed defaults, and — with accept:true — starts a durable launch job that provisions the site (create_phantom), configures it, and seeds the first post(s) through run_collection (origin internal: the launch is never metered against cadence). accept:false (default) is a DRY RUN returning the plan; hand it back verbatim as `plan` with accept:true to launch exactly what you reviewed. Override any decided value in `overrides` (name pins the masthead — it seeds the URL). For distinct editorial angles, priced domains and sibling publications first, run propose_phantom_plan then propose_phantom_mastheads. Requires capability phantom:write.","capability":"phantom:write","inputSchema":{"type":"object","properties":{"domain":{"description":"The client company's bare website domain, e.g. acme.com (no scheme, no path). Its homepage is read to infer the positioning that seeds the plan (masthead name, description, strategy).","type":"string"},"overrides":{"description":"Caller-pinned choices; anything omitted is decided for this site. Run propose_phantom_plan, then propose_phantom_mastheads, and pass the chosen plan's direction + pillars and the chosen masthead + domain through here.","type":"object","properties":{"name":{"description":"Pin the publication's masthead name instead of the inferred one. Committed at create (it seeds the URL) and rejected if it contains the client's company name. REQUIRED when no strategy can be inferred (unreadable homepage, or no company domain on file). Pick one from propose_phantom_mastheads.","anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]},"direction":{"description":"The publication's editorial direction in plain language, e.g. 'where bank regulators are heading on agentic AI, for compliance officers'. This is the highest-leverage override: it is pinned as the planner's STEERING PROMPT, so it shapes the masthead, the topic map, the seeded headlines, and every post the planner writes afterwards — not just the first batch. Use it when the client already has a phantom and this one must cover a different beat. Take it from the chosen propose_phantom_plan topic plan's `direction`.","anyOf":[{"type":"string","maxLength":600},{"type":"null"}]},"pillars":{"description":"The approved content areas — pass the chosen propose_phantom_plan topic plan's `pillars` through VERBATIM. They become the topic map's pillars exactly as given (generation is confined to them), which is what makes an approved plan binding instead of a preview to correct afterwards. Omit to let the topic map choose its own.","anyOf":[{"maxItems":8,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The content area, as a short noun phrase."},"description":{"type":"string","maxLength":400,"description":"One or two sentences on what the area covers."}},"required":["name","description"]}},{"type":"null"}]},"site_domain":{"description":"A custom domain for the phantom, bare (no scheme) — `www.` and a scheme are stripped. Nothing is bought unless domain_mode is 'buy_now'. Pick one from the priced `domains` propose_phantom_mastheads returns for the chosen masthead, or leave it out with domain_mode 'buy_now' to buy one named after the masthead.","type":["string","null"]},"domain_mode":{"description":"What to do about site_domain. 'subdomain' (DEFAULT) launches on the instant subdomain and ignores it; 'buy_now' REGISTERS it during provisioning and SPENDS money (capped at 2 domains/org and $30/domain — price it with check_domain first), and without a site_domain registers the first buyable domain named after the masthead (the subdomain when none is, or the org is at its cap); 'connect_later' records the intent and buys nothing.","type":"string","enum":["subdomain","buy_now","connect_later"]},"cadence_per_week":{"description":"Planner publishing cadence (posts/week). Absent = judged per site from how much ground its subject has (6 when that cannot be judged). Pin it when sites must publish at the same pace. 7 is stored as 6.","type":"integer","minimum":1,"maximum":7},"cover_canvas":{"description":"Which cover style the collection defaults to. Default editorial_image (one house style).","type":"string","enum":["editorial_image","full_ai","line_art"]},"theme":{"description":"Pin the blog's visual theme instead of the auto-authored one.","anyOf":[{"type":"string","enum":["sleek","classic","minimal","magazine","midnight","gazette","dispatch","metro","review","signal","current","ledger","atelier","noir","chronicle","verdure","couture","atlas","missive","commit","apex","mint","bloom","cinder","hearth","harvest","terra","verve","estate","counsel","clinic","nook","lumen","cellar","horizon","derma","meridian","keystone","vantage","cobalt","sterling","momentum","forum","cadence","evergreen","beacon","kernel","graphite","cortex","cipher","pulse","apiwire","bourse","folio","kiosk","vitrine","commons","blitz","quarto","stark","flux","relay","riot","circuit","canvas","codex"]},{"type":"null"}]},"posts_to_seed":{"description":"How many first posts to seed from OPEN topic-map spokes once the site is configured. Default 1; 0 seeds none. The strongest spokes are picked and spread across pillars, not the first N.","type":"integer","minimum":0,"maximum":10},"is_test":{"description":"Mark the provisioned site as a throwaway/test site, kept off the fleet board and every fleet tab, and never billed. Staff only: defaults to true for a staff caller; a customer always launches a real site, whatever this says.","type":"boolean"},"disclosure":{"description":"Sponsor disclosure ('safe mode'): the site says the client sponsors it, beside every post (never in the body). Absent/null = off, the default; decided per client. Applied when the site is created; change it later with set_collection_config's disclosure section.","anyOf":[{"type":"object","properties":{"surfaces":{"default":["article_header","article_footer","structured_data","sponsored_links","meta_description","site_statement","site_byline"],"description":"Where the site discloses its sponsor. Default (safe mode): every surface except the cover ones — the label at the top of each article and the line after it, schema.org sponsor, rel=sponsored on links to the client (already nofollow), the label in meta and Open Graph descriptions, the statement on every page (footer, About, llms.txt, RSS), and bylines credited to the site's editors instead of named writers. On the cover, add cover_badge (a badge the site sets over it) or cover_baked (the label drawn into the image, so it travels into link previews) — not both. See set_collection_config's disclosure.surfaces.","minItems":1,"maxItems":9,"type":"array","items":{"type":"string","enum":["article_header","article_footer","cover_badge","cover_baked","structured_data","sponsored_links","meta_description","site_statement","site_byline"]}},"sponsor_label":{"description":"The sponsor name to show instead of the client's company name. Required on internal and phantom_farm orgs.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"note":{"description":"Why: the client's words from the call, recorded on the collection's event log.","anyOf":[{"type":"string","maxLength":600},{"type":"null"}]}}},{"type":"null"}]},"seed_topic_ids":{"description":"Pin exactly which planner_topics spokes to seed, in order, instead of letting the strongest be picked. Ids no longer open are skipped.","maxItems":10,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"customer_ref":{"description":"Attribution for platform integrators launching phantoms on behalf of THEIR customers: the owning customer's id or email in YOUR system. Opaque free text, stamped on the deployment and returned by get_deployment/list_deployments so you can map deployments back to your users.","type":"string","minLength":1,"maxLength":200},"notify_staff":{"description":"Whether this launch posts started/completed/failed to the Letter staff onboarding channel. Default true. Pass false when your own customers launch sites themselves (a self-serve product), so staff aren't pinged for sites they don't run.","type":"boolean"}}},"accept":{"default":false,"description":"false (default) = DRY RUN: compute and return the plan, persist NOTHING. true = APPLY: start the one-shot onboarding job and return its job_id (poll get_phantom_onboard_status).","type":"boolean"},"plan":{"description":"The plan you already reviewed, passed back VERBATIM from this tool's own dry run (`plan` in the accept:false response). Given this, the onboarding provisions exactly what you reviewed and skips re-planning — no second homepage read, no second masthead proposal. Omit to let the job plan for itself. Ignored when accept is false.","type":"object","properties":{"domain":{"type":"string","description":"The client domain the plan was built from."},"client_label":{"type":"string","description":"Display label for the client — the domain's registrable part."},"name":{"description":"The masthead: the caller's pin, else an inferred client-free name; null when neither. Committed at create — it seeds the URL.","type":["string","null"]},"description":{"type":"string","description":"Subheadline built from the publication's own subject, never the client's."},"direction":{"description":"The editorial direction the plan was steered with, if any.","type":["string","null"]},"strategy":{"anyOf":[{"type":"object","properties":{"description":{"type":"string"},"stances":{"type":"array","items":{"type":"string"}},"priority_topics":{"type":"array","items":{"type":"string"}}},"required":["description","stances","priority_topics"]},{"type":"null"}],"description":"The inferred positioning, or null when the homepage couldn't be read."},"defaults":{"type":"object","properties":{"cadence_per_week":{"type":"number"},"cover_canvas":{"type":"string","enum":["editorial_image","full_ai","line_art"]},"posts_to_seed":{"type":"number"}},"required":["cadence_per_week","cover_canvas","posts_to_seed"]},"domain_plan":{"type":"object","properties":{"site_domain":{"type":["string","null"]},"mode":{"type":"string","enum":["subdomain","buy_now","connect_later"]}},"required":["site_domain","mode"]},"theme":{"type":["string","null"]},"note":{"type":"string","description":"Human-readable summary, including any soft-fail caveat."}},"required":["domain","client_label","name","description","direction","strategy","defaults","domain_plan","theme","note"]}},"required":["domain"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"accepted":{"type":"boolean","const":false,"description":"Dry run — nothing was persisted."},"domain":{"type":"string","description":"The client domain the plan was built from (as passed in)."},"client_label":{"type":"string","description":"Display label for the client — the domain's registrable part."},"proposed_name":{"description":"The masthead/publication name that will be committed at create — an override if given, else inferred (2-4 words, never containing the client label). null when no strategy could be inferred and no overrides.name was given; pass overrides.name to apply.","type":["string","null"]},"description":{"type":"string","description":"Suggested blog description/tagline, built from the publication's own subject (never the client's)."},"strategy":{"anyOf":[{"type":"object","properties":{"description":{"type":"string","description":"Inferred manifesto: who the client is and what their content should do."},"stances":{"type":"array","items":{"type":"string"},"description":"Inferred opinionated positions the client holds."},"priority_topics":{"type":"array","items":{"type":"string"},"description":"Inferred topic areas the content program should own."}},"required":["description","stances","priority_topics"]},{"type":"null"}],"description":"The inferred positioning, or null when the homepage couldn't be read or the model soft-failed."},"defaults":{"type":"object","properties":{"cadence_per_week":{"type":"number","description":"Planner cadence (posts/week) the apply will use."},"cover_canvas":{"type":"string","description":"Cover style the apply will use (editorial_image by default)."},"posts_to_seed":{"type":"number","description":"Number of first posts the apply will seed."}},"required":["cadence_per_week","cover_canvas","posts_to_seed"],"description":"The starting values the apply will use; override any of them in overrides when applying."},"direction":{"description":"The editorial direction this publication is steered with — it becomes the planner's steering prompt, so it shapes the topic map and every post, not just the masthead. null when none was given.","type":["string","null"]},"domain_plan":{"type":"object","properties":{"site_domain":{"description":"The custom domain to attach, when one was chosen. Null with buy_now: one named after the masthead is bought.","type":["string","null"]},"mode":{"type":"string","description":"One of: subdomain (launch on the instant subdomain, the default) | buy_now (register site_domain during provisioning, or with none the first buyable domain named after the masthead) | connect_later (record the intent, buy nothing)."}},"required":["site_domain","mode"],"description":"What the apply will do about a custom domain."},"theme":{"description":"The pinned theme, or null to let the site author its own.","type":["string","null"]},"note":{"type":"string","description":"Human-readable summary, including any soft-fail caveat (e.g. homepage unreadable)."},"plan":{"type":"object","properties":{"domain":{"type":"string","description":"The client domain the plan was built from."},"client_label":{"type":"string","description":"Display label for the client — the domain's registrable part."},"name":{"description":"The masthead: the caller's pin, else an inferred client-free name; null when neither. Committed at create — it seeds the URL.","type":["string","null"]},"description":{"type":"string","description":"Subheadline built from the publication's own subject, never the client's."},"direction":{"description":"The editorial direction the plan was steered with, if any.","type":["string","null"]},"strategy":{"anyOf":[{"type":"object","properties":{"description":{"type":"string"},"stances":{"type":"array","items":{"type":"string"}},"priority_topics":{"type":"array","items":{"type":"string"}}},"required":["description","stances","priority_topics"]},{"type":"null"}],"description":"The inferred positioning, or null when the homepage couldn't be read."},"defaults":{"type":"object","properties":{"cadence_per_week":{"type":"number"},"cover_canvas":{"type":"string","enum":["editorial_image","full_ai","line_art"]},"posts_to_seed":{"type":"number"}},"required":["cadence_per_week","cover_canvas","posts_to_seed"]},"domain_plan":{"type":"object","properties":{"site_domain":{"type":["string","null"]},"mode":{"type":"string","enum":["subdomain","buy_now","connect_later"]}},"required":["site_domain","mode"]},"theme":{"type":["string","null"]},"note":{"type":"string","description":"Human-readable summary, including any soft-fail caveat."}},"required":["domain","client_label","name","description","direction","strategy","defaults","domain_plan","theme","note"],"description":"The reviewed plan as ONE object. Pass it back VERBATIM as `plan` with accept:true and the onboarding provisions exactly this, instead of re-planning from the homepage and possibly proposing a different masthead."}},"required":["accepted","domain","client_label","proposed_name","description","strategy","defaults","direction","domain_plan","theme","note","plan"]},{"type":"object","properties":{"accepted":{"type":"boolean","const":true,"description":"Applied — a durable onboarding job was started."},"job_id":{"type":"string","description":"Poll target for get_phantom_onboard_status."},"status":{"type":"string","const":"running"},"stage":{"type":"string","const":"planning"}},"required":["accepted","job_id","status","stage"]}],"description":"Either the dry-run plan (accept:false) or the started job (accept:true), discriminated by `accepted`.","type":"object"}},{"name":"propose_phantom_plan","description":"Step ONE of Letter's launch recipe, before plan_phantom: pick the beat. Reads the client's homepage, infers positioning, and returns distinct editorial angles (topicPlans — each a direction plus the content areas that become the topic map's pillars VERBATIM), the single fallback plan with its defaults, the client's existing publications (siblings) with an overlap warning, and the org's custom-domain headroom. Persists nothing and spends nothing. Pass `direction` to get variations on a beat you already have in mind. Next: propose_phantom_mastheads, then plan_phantom with the chosen direction and pillars in `overrides`. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"client_domain":{"description":"The client company's bare website domain, e.g. acme.com — the same value onboard_phantom takes as `domain`. Its homepage is read to infer the positioning everything else is proposed from. Optional when the org already has a company domain on file.","type":["string","null"]},"direction":{"description":"The beat you already have in mind, in plain language. When given, the proposed topic plans are variations ON it (audience, altitude, pillar breakdown) rather than alternatives to it.","anyOf":[{"type":"string","maxLength":600},{"type":"null"}]},"name":{"description":"Pin the masthead instead of proposing one; the plan is still computed around it.","anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"clientName":{"type":"string","description":"The client this would sit under — the org's name."},"clientDomain":{"description":"The client's company domain the positioning was inferred from; null when there is none on file and the direction steered the plan alone.","type":["string","null"]},"existingOrgId":{"description":"The org the site would be created in (the API key's org).","type":["string","null"]},"plan":{"type":"object","properties":{"domain":{"type":"string","description":"The client domain the plan was built from."},"client_label":{"type":"string","description":"Display label for the client — the domain's registrable part."},"name":{"description":"The masthead: the caller's pin, else an inferred client-free name; null when neither. Committed at create — it seeds the URL.","type":["string","null"]},"description":{"type":"string","description":"Subheadline built from the publication's own subject, never the client's."},"direction":{"description":"The editorial direction the plan was steered with, if any.","type":["string","null"]},"strategy":{"anyOf":[{"type":"object","properties":{"description":{"type":"string"},"stances":{"type":"array","items":{"type":"string"}},"priority_topics":{"type":"array","items":{"type":"string"}}},"required":["description","stances","priority_topics"]},{"type":"null"}],"description":"The inferred positioning, or null when the homepage couldn't be read."},"defaults":{"type":"object","properties":{"cadence_per_week":{"type":"number"},"cover_canvas":{"type":"string","enum":["editorial_image","full_ai","line_art"]},"posts_to_seed":{"type":"number"}},"required":["cadence_per_week","cover_canvas","posts_to_seed"]},"domain_plan":{"type":"object","properties":{"site_domain":{"type":["string","null"]},"mode":{"type":"string","enum":["subdomain","buy_now","connect_later"]}},"required":["site_domain","mode"]},"theme":{"type":["string","null"]},"note":{"type":"string","description":"Human-readable summary, including any soft-fail caveat."}},"required":["domain","client_label","name","description","direction","strategy","defaults","domain_plan","theme","note"],"description":"The single inferred plan — the fallback when topicPlans is empty, and the source of the defaults."},"siblings":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"direction":{"type":["string","null"]}},"required":["name","description","direction"]},"description":"The client's existing publications, so a second site can be steered off the first."},"overlapWarning":{"description":"A nudge when the proposal looks like a beat the client already covers. Never blocks.","type":["string","null"]},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"available":{"type":"boolean","description":"Whether the domain is available to register."},"purchasable":{"type":"boolean","description":"available AND within Letter's per-domain price cap."},"purchasePrice":{"description":"First-year purchase price (USD), null if unpriced.","type":["number","null"]},"renewalPrice":{"description":"Yearly renewal price (USD), null if unpriced.","type":["number","null"]}},"required":["domain","available","purchasable","purchasePrice","renewalPrice"]},"description":"Priced domains for the FALLBACK masthead only — empty when topicPlans were proposed."},"domainHeadroom":{"description":"How many more custom domains this org may attach; null = unlimited.","type":["number","null"]},"topicPlans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within one proposal — pass the whole plan back to propose_phantom_mastheads."},"title":{"type":"string","description":"2-5 words naming the ANGLE (not the publication)."},"direction":{"type":"string","description":"The beat, as an instruction to a writer. Pass it as onboard_phantom overrides.direction."},"pillars":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The content area. Becomes a topic-map pillar VERBATIM when passed to onboard_phantom."},"description":{"type":"string"}},"required":["name","description"]},"description":"Pass these VERBATIM as onboard_phantom overrides.pillars."},"rationale":{"type":"string"}},"required":["id","title","direction","pillars","rationale"]},"description":"Distinct editorial angles to choose between — the FIRST decision. Empty when the model is unavailable; then `plan` is the answer."}},"required":["clientName","clientDomain","existingOrgId","plan","siblings","overlapWarning","domains","domainHeadroom","topicPlans"]}},{"name":"propose_phantom_mastheads","description":"Step TWO of the launch recipe: names for ONE chosen plan — candidate mastheads, each with a subheadline and its own priced, buyable domains. Persists nothing and spends nothing. Pass the chosen masthead as plan_phantom overrides.name and a domain as overrides.site_domain. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"client_domain":{"description":"The client company's bare website domain — the same value onboard_phantom takes as `domain`. Names are checked so they never carry the client's own name. Null/absent for a client with no domain on file, where there is no company name for a masthead to collide with.","type":["string","null"]},"plan":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40,"description":"The plan's id as returned by propose_phantom_plan (e.g. plan-2)."},"title":{"type":"string","minLength":1,"maxLength":120,"description":"2-5 words naming the angle."},"direction":{"type":"string","minLength":1,"maxLength":600,"description":"The beat as an instruction to a writer; becomes the planner's steering prompt."},"pillars":{"minItems":1,"maxItems":8,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The content area, as a short noun phrase."},"description":{"type":"string","maxLength":400,"description":"One or two sentences on what the area covers."}},"required":["name","description"]},"description":"The content areas the angle will own."},"rationale":{"default":"","description":"Why this angle, for the operator.","type":"string","maxLength":400}},"required":["id","title","direction","pillars"],"description":"The chosen topic plan, passed back verbatim from propose_phantom_plan's `topicPlans`."}},"required":["plan"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"options":{"type":"array","items":{"type":"object","properties":{"masthead":{"type":"string","description":"Candidate publication name; pass as onboard_phantom overrides.name."},"description":{"type":"string","description":"Subheadline that ships with this masthead."},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"available":{"type":"boolean","description":"Whether the domain is available to register."},"purchasable":{"type":"boolean","description":"available AND within Letter's per-domain price cap."},"purchasePrice":{"description":"First-year purchase price (USD), null if unpriced.","type":["number","null"]},"renewalPrice":{"description":"Yearly renewal price (USD), null if unpriced.","type":["number","null"]}},"required":["domain","available","purchasable","purchasePrice","renewalPrice"]},"description":"Priced candidates for THIS masthead; pass one as onboard_phantom overrides.site_domain."}},"required":["masthead","description","domains"]},"description":"One entry per candidate masthead, best first. Empty when the model is unavailable — keep the plan's own `name`."}},"required":["options"]}},{"name":"get_phantom_launch_status","description":"Poll a launch started by plan_phantom(accept:true): status (running|done|failed), stage (planning → provisioning → configuring → seeding → done), the phantom and collection ids once created, the configuration job, the seeded run ids (poll each with get_collection_run), the decided plan, and any error. Polling nudges the launch forward. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The launch job id returned by plan_phantom with accept:true."}},"required":["job_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","description":"One of: running | done | failed."},"stage":{"type":"string","description":"Stage the job has reached. One of: planning | provisioning | configuring | seeding | done | failed."},"domain":{"type":"string","description":"The client domain the onboarding was built from."},"deployment_id":{"description":"The phantom blog created (once provisioning starts).","type":["string","null"]},"collection_id":{"description":"The blog's backing collection.","type":["string","null"]},"config_job_id":{"description":"The phantom_config_jobs id doing the configuration (once started).","type":["string","null"]},"seed_job_ids":{"type":"array","items":{"type":"string"},"description":"The collection run ids seeded for the first posts (poll each with get_collection_run). Named seed_job_ids for the wire's sake: the seeding stage opened phantom_orchestration_jobs before PR #2158 folded that table into collection_runs."},"plan":{"anyOf":[{},{"type":"null"}],"description":"The plan the planning stage decided (name, description, direction, strategy, defaults)."},"direction":{"description":"The editorial direction the onboarding was steered with, if any.","type":["string","null"]},"site_domain":{"description":"The custom domain chosen for this site, if any.","type":["string","null"]},"domain_mode":{"type":"string","description":"One of: subdomain | buy_now | connect_later."},"is_test":{"type":"boolean","description":"Whether the provisioned site is marked as a throwaway/test site."},"error":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["job_id","status","stage","domain","deployment_id","collection_id","config_job_id","seed_job_ids","plan","direction","site_domain","domain_mode","is_test","error","created_at","updated_at"]}},{"name":"get_recent_activity","description":"The newest AI-agent/crawler requests and confirmed human visits on the caller's PHANTOM sites (not the org's own site: that is Visitors), newest first: the dashboard's 'what's hitting my fleet right now' feed. Reads at most the newest 60 request rows and 40 visit rows from the last 7 days, so it is a sample, not a count: for totals use get_phantom_agent_access or get_page_access. Pass phantom_id to scope to one site; omit it for the whole org fleet. Requires capability phantom_access:read.","capability":"phantom_access:read","inputSchema":{"type":"object","properties":{"phantom_id":{"description":"Optional phantom UUID to scope the feed to a single site; omit to see the caller's whole fleet.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"Max rows to return, newest first (default 40).","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"recentActivity":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"access"},"lastSeenAt":{"type":"string","description":"ISO timestamp this (site, path, who) was last hit."},"blogId":{"type":"string"},"siteName":{"type":"string"},"siteDomain":{"type":["string","null"]},"path":{"type":"string"},"requesterClass":{"type":"string","enum":["ai_agent","search_crawler","other_bot","browser"]},"agent":{"type":"string","description":"User-agent string, or empty when not captured."},"purpose":{"type":"string","description":"'training' | 'index' | 'live' | '' — only meaningful for ai_agent."},"verified":{"type":"boolean","description":"Whether the requester's claimed identity was cryptographically verified."},"requestsToday":{"type":"number","description":"The day's running count for this (site, path, who) bucket."}},"required":["kind","lastSeenAt","blogId","siteName","siteDomain","path","requesterClass","agent","purpose","verified","requestsToday"]},{"type":"object","properties":{"kind":{"type":"string","const":"visit"},"lastSeenAt":{"type":"string","description":"ISO timestamp of the last confirmed browser render."},"blogId":{"type":"string"},"siteName":{"type":"string"},"siteDomain":{"type":["string","null"]},"path":{"type":"string"},"source":{"type":"string","description":"'' for an ordinary visit; an AI product when the reader arrived from one."},"visitsToday":{"type":"number"}},"required":["kind","lastSeenAt","blogId","siteName","siteDomain","path","source","visitsToday"]}]},"description":"Newest-first feed of AI-agent/crawler access and confirmed visits across the scoped deployment(s), last 7 days."}},"required":["recentActivity"]}},{"name":"get_phantom_visitors","description":"People identified on the caller's PHANTOM sites only (the Sites page's Visitors tab): name, title, company, work email, LinkedIn, which site, how often and how recently, with a hot-lead flag. Not the org's own site: who visited that is Visitors (visitors_get_people). Opt-in per phantom: set_phantom_config `maverick_enabled: true` registers the site's domain automatically. A site whose registration hasn't landed reports tracking 'pending_registration' and identifies nobody; sending maverick_enabled: true again retries. Pass phantom_id to scope to one site; omit it for every opted-in phantom. Requires capability phantom_access:read.","capability":"phantom_access:read","inputSchema":{"type":"object","properties":{"phantom_id":{"description":"Optional phantom UUID to scope to a single site; omit for every opted-in site of the caller's.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"Max visitors to return, newest first (default 50).","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"configured":{"type":"boolean","description":"Whether visitor identification is configured on this environment."},"error":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["forbidden","failed"]},"message":{"type":"string"}},"required":["reason","message"]},{"type":"null"}],"description":"The pull's failure when visitor identification couldn't be read ('forbidden' = key rejected); null on success."},"sites":{"type":"array","items":{"type":"object","properties":{"phantom_id":{"type":"string"},"name":{"type":"string"},"domain":{"description":"The public host the pixel runs on.","type":["string","null"]},"tracking":{"type":"string","enum":["active","pending_registration"],"description":"pending_registration = opted in but the site's identification registration hasn't landed (it's automatic on enable; re-enabling retries). Nothing is identified until it does."}},"required":["phantom_id","name","domain","tracking"]},"description":"The caller's opted-in sites (set_phantom_config maverick_enabled) and each one's state."},"visitors":{"type":"array","items":{"type":"object","properties":{"person_id":{"type":"string","description":"The identified person's id, stable across visits."},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"company":{"type":["string","null"]},"company_domain":{"type":["string","null"]},"email":{"description":"Work address when known, else their personal one.","type":["string","null"]},"linkedin_url":{"type":["string","null"]},"location":{"type":["string","null"]},"seniority":{"type":["string","null"]},"job_function":{"type":["string","null"]},"traffic_type":{"type":["string","null"]},"visit_count":{"type":"number"},"first_seen":{"type":"string"},"last_seen":{"type":"string"},"is_hot_lead":{"type":"boolean","description":"ICP match flag."},"phantom_id":{"type":"string","description":"The caller's site this person was identified on."},"site_name":{"type":"string"},"site_domain":{"type":["string","null"]}},"required":["person_id","name","title","company","company_domain","email","linkedin_url","location","seniority","job_function","traffic_type","visit_count","first_seen","last_seen","is_hot_lead","phantom_id","site_name","site_domain"]},"description":"Identified visitors across the scoped site(s), newest last-seen first."}},"required":["configured","error","sites","visitors"]}},{"name":"launch_mailers","description":"The one-shot Mailers launch: buys or REUSES a domain (a domain that already backs a phantom site is reused with NO re-purchase), provisions a self-hosted mailserver on it, creates the requested mailers, and enrolls them in warmup (warmup defaults ON; a warming mailer becomes 'ready' once the warmup engine reports it warm: at least 14 days and a warmup score of 85). Mail is only available on domains registered through Letter (we manage their DNS). SPENDS money if the domain is net-new (auto-renewing): on the org's own Vercel key and team when one is set (set_vercel_key; that purchase also needs set_registrant_contact), on Letter's account otherwise, up to 2 domains ($30 in total) per org, after which the org must set its own key. Price it first with quote_mail_domain. Compliance: consent_attested and mail_from_postal_address are optional (consent is normally captured during onboarding); the CAN-SPAM footer address falls back to the org's mailing address (set_mail_postal_address), and with none on file anywhere emails go out with no postal footer line. Runs in the background; poll get_mailer_domain_status until the mailers are warming or ready. Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Bare apex domain to set up email on, e.g. myco.com (no scheme). If we don't own it yet it's registered under our account; if a phantom site already uses it, the SAME shared domain is reused (no re-purchase). Mail is only available on domains registered through Letter (we manage their DNS)."},"mailers":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"local_part":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9._-]+$","description":"The part before @, e.g. 'hello' or 'sara.jones'."},"display_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"purpose":{"description":"Defaults to cold_email.","type":"string","enum":["cold_email","newsletter","system"]}},"required":["local_part"]},"description":"Mailers to create on the domain (addresses are name@domain)."},"customer_ref":{"description":"Developer-console customer attribution (opaque).","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"forward_to":{"description":"Forward replies to these mailers' sequences to this inbox, within about 5 minutes (warmup mail and other mail stay in the mailbox). Omit for no forwarding.","anyOf":[{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"warmup":{"description":"Enroll every created mailer in warmup automatically (the one-shot default; omitted = true). A warming mailer becomes 'ready' once the warmup engine reports it warm: at least 14 days and a warmup score of 85. Pass false to create the mailers 'active' and start warmup later with start_mailer_warmup.","type":"boolean"},"consent_attested":{"description":"Optional attestation that you have consent to send from these addresses (consent is normally captured during onboarding); true stamps it on the domain config.","type":"boolean"},"mail_from_postal_address":{"description":"Optional physical mailing address printed in every message (CAN-SPAM). Omitted = footers fall back to the org's mailing address (set_mail_postal_address); with none on file anywhere, emails go out with no postal footer line.","type":"string","minLength":6,"maxLength":300}},"required":["domain","mailers"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string","description":"mailer_onboard_jobs UUID for this provisioning run (informational)."},"owned_domain_id":{"type":"string","description":"The shared owned_domains UUID backing this mail setup; poll it with get_mailer_domain_status."},"domain":{"type":"string","description":"The apex domain mail was set up on."},"mail_setup":{"type":"string","const":"started"}},"required":["job_id","owned_domain_id","domain","mail_setup"],"description":"Email provisioning was enqueued; poll get_mailer_domain_status until the mailers are active."}},{"name":"quote_mail_domain","description":"Price a domain for launch_mailers BEFORE anything is bought (read-only, no charge): reuse of a domain the org already owns, the registration price and renewal (on the org's own Vercel key and team when one is set), or why neither works with up to three registrable alternatives, plus the org's remaining domain registrations. Letter registers at most 2 domains ($30 in total) per org on its own account; past that the quote is needs_own_vercel_key and the org registers on its own Vercel key (set_vercel_key). Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Bare apex domain to price for a Mailers launch, e.g. myco.com (no scheme)."}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"quote":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"owned"},"domain":{"type":"string"}},"required":["kind","domain"],"description":"Already ours: launch reuses it, no charge."},{"type":"object","properties":{"kind":{"type":"string","const":"available"},"domain":{"type":"string"},"price_usd":{"type":"number","description":"First-year price launch_mailers would pay."},"renewal_usd":{"type":["number","null"]},"years":{"type":"number"}},"required":["kind","domain","price_usd","renewal_usd","years"],"description":"Registrable: launch_mailers buys it (on the org's own Vercel key when one is set)."},{"type":"object","properties":{"kind":{"type":"string","const":"unavailable"},"domain":{"type":"string"},"suggestions":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"price_usd":{"type":"number"}},"required":["domain","price_usd"]},"description":"Up to three registrable nearby names."}},"required":["kind","domain","suggestions"],"description":"Taken (or owned by another org)."},{"type":"object","properties":{"kind":{"type":"string","const":"external"},"domain":{"type":"string"}},"required":["kind","domain"],"description":"Ours, but its DNS isn't managed by us: mail can't be launched on it."},{"type":"object","properties":{"kind":{"type":"string","const":"not_offered"},"domain":{"type":"string"}},"required":["kind","domain"],"description":"Free, but its TLD or price isn't offered (the cap is under $30)."},{"type":"object","properties":{"kind":{"type":"string","const":"needs_own_vercel_key"},"domain":{"type":"string"},"price_usd":{"type":"number"},"platform_cap":{"type":"number","description":"Domains Letter registers on its own account per org."},"platform_budget_usd":{"type":"number","description":"First-year USD Letter spends on them per org, all together."}},"required":["kind","domain","price_usd","platform_cap","platform_budget_usd"],"description":"Registrable, but past what Letter registers on its own account: set_vercel_key, then quote again."}],"description":"What launching mailers on this domain would mean. Read-only: nothing is bought."},"domains_left":{"description":"Domains Letter will still register on its own account for this org; null = no cap (the org's own Vercel key, or a developer org).","type":["number","null"]},"domains_cap":{"type":["number","null"]}},"required":["quote","domains_left","domains_cap"],"description":"Quote for a Mailers launch domain."}},{"name":"list_mail_domains","description":"Every domain the org owns with its mail status, its latest setup job (stage, status, error) and the mailers on it (status, warmup start and health, daily send limit) — the Mailers page in one call. Use an owned_domain_id with get_mailer_domain_status. Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domains":{"type":"array","items":{"type":"object","properties":{"owned_domain_id":{"type":"string","description":"Pass to get_mailer_domain_status."},"domain":{"type":"string"},"source":{"type":"string","description":"purchased / connected / adopted."},"dns_managed":{"type":"boolean"},"backs_phantom":{"type":"boolean"},"backs_mail":{"type":"boolean"},"mail_status":{"description":"The domain's mail setup status; null = no mail yet.","type":["string","null"]},"sending_subdomain":{"type":["string","null"]},"job":{"anyOf":[{"type":"object","properties":{"stage":{"type":"string"},"status":{"type":"string"},"error":{"type":["string","null"]}},"required":["stage","status","error"]},{"type":"null"}],"description":"The latest setup job; when it failed, retry_mailer_setup resumes it."},"mailers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"status":{"type":"string"},"purpose":{"type":"string"},"forward_to":{"type":["string","null"]},"daily_send_limit":{"type":"number"},"warmup_started_at":{"type":["string","null"]},"warmup_health_score":{"type":["number","null"]},"warmup_error":{"description":"Why warmup stopped on a warming mailer (it lost access and couldn't reconnect); null when fine.","type":["string","null"]}},"required":["id","address","status","purpose","forward_to","daily_send_limit","warmup_started_at","warmup_health_score","warmup_error"]}}},"required":["owned_domain_id","domain","source","dns_managed","backs_phantom","backs_mail","mail_status","sending_subdomain","job","mailers"]},"description":"Every domain the org owns, with its mail status and mailers."},"registration_configured":{"type":"boolean","description":"Whether a net-new domain can be registered (on the org's own Vercel key or the platform's)."}},"required":["domains","registration_configured"]}},{"name":"move_mail_domain","description":"Move a mail domain to another organization (e.g. launched in the wrong one): the domain with its mail config, setup jobs (a running setup keeps going) and mailers (warmup carries on). By default the org's own Vercel key, team and registrant move too: copied when the other org has none, removed here once nothing else here was bought on it. The API key's creator must be an admin of both orgs. Refused when the domain also serves a site here, or its mailers are attached to sequences here. Send history stays here. Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"owned_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The domain to move (list_mail_domains)."},"to_org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The organization to move it to. The API key's creator must be an admin of both."},"move_vercel_key":{"description":"Also move this org's own Vercel key (token, team, registrant): copied to the other org when it has none, and removed here once no other domain here was bought on it. Default true.","type":"boolean"}},"required":["owned_domain_id","to_org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"owned_domain_id":{"type":"string"},"domain":{"type":"string"},"from_org_id":{"type":"string"},"to_org_id":{"type":"string"},"mailers_moved":{"type":"number","description":"Mailers that moved with the domain (warmup carries on)."},"vercel_key":{"type":"string","enum":["copied","already_set","not_moved"],"description":"The org's own Vercel key: copied to the other org, already set there, or not involved."},"vercel_key_removed_from_source":{"type":"boolean","description":"True when the key left this org (nothing still here was bought on it)."}},"required":["owned_domain_id","domain","from_org_id","to_org_id","mailers_moved","vercel_key","vercel_key_removed_from_source"],"description":"The domain, its mail config, setup jobs and mailers now belong to to_org_id."}},{"name":"set_vercel_key","description":"Bring your own Vercel account: save the org's Vercel API token (and optionally the team to act in) so Mailers domains are bought, billed and managed THERE instead of on Letter's account. The token is verified live against Vercel (and against the team, when given), sealed at rest and never returned. Replaces any previous key. Purchases on it also need set_registrant_contact. Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"token":{"type":"string","minLength":8,"maxLength":200,"description":"A Vercel API token (vercel.com, Account Settings, Tokens). Verified live, sealed, never returned."},"team_id":{"description":"The Vercel team (org) to buy and manage domains in, e.g. team_AbC123. Omit or null to act in the token's personal account. The token must have access to this team.","anyOf":[{"type":"string","pattern":"^team_[A-Za-z0-9]{6,64}$"},{"type":"null"}]}},"required":["token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"vercel_key_configured":{"type":"boolean","const":true},"team_id":{"description":"The Vercel team purchases happen in; null = the token's personal account.","type":["string","null"]}},"required":["vercel_key_configured","team_id"]}},{"name":"set_registrant_contact","description":"Set the WHOIS registrant for domains bought on the org's own Vercel key (the org pays, so it is the legal owner). Required before launch_mailers can buy on that key; save the key first with set_vercel_key. Phone and country take any common format ((415) 555-1234, +44 20 7946 0958; US, USA, United States) and are saved in the registrar's form (+1.4155551234, US). Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"registrant_contact":{"description":"The WHOIS registrant for domains bought on the org's own Vercel key (the org pays, so it owns them). Requires the key to be saved first.","type":"object","properties":{"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"phone":{"type":"string","minLength":1,"maxLength":40,"description":"Any common format: +1.4155551234, +1 415 555 1234, (415) 555-1234. A number without a country code is read in the contact's country."},"address1":{"type":"string","minLength":1,"maxLength":200},"address2":{"type":"string","maxLength":200},"city":{"type":"string","minLength":1,"maxLength":100},"state":{"type":"string","minLength":1,"maxLength":100},"zip":{"type":"string","minLength":1,"maxLength":20},"country":{"description":"A 2-letter code (US) or the country's name (United States, USA, United Kingdom).","type":"string","minLength":1,"maxLength":60},"companyName":{"type":"string","maxLength":200}},"required":["firstName","lastName","email","phone","address1","city","state","zip","country"]}},"required":["registrant_contact"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"registrant_contact_configured":{"type":"boolean","const":true},"registrant_contact":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"address1":{"type":"string"},"address2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"},"country":{"type":"string"},"companyName":{"type":"string"}},"required":["firstName","lastName","email","phone","address1","city","state","zip","country"],"description":"The contact as saved (phone and country normalized)."}},"required":["registrant_contact_configured","registrant_contact"]}},{"name":"remove_vercel_key","description":"Remove the org's own Vercel key (and its registrant contact): future domain purchases return to Letter's account. Domains already bought on the key keep needing it for DNS changes, so re-add it before relaunching mail on them. Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"vercel_key_configured":{"type":"boolean","const":false},"registrant_contact_configured":{"type":"boolean","const":false,"description":"The registrant contact is removed with the key."}},"required":["vercel_key_configured","registrant_contact_configured"]}},{"name":"retry_mailer_setup","description":"Resume a domain's failed mail setup from the step it stopped at: the same job, the same mailer names, no new charge. Use it after the mail provider fixes whatever stalled (e.g. a mailbox stuck in creation); mailboxes that finished in the meantime are adopted, not re-created. Only the domain's latest setup, and only when it failed. Poll get_mailer_domain_status afterwards. Requires capability mailer:provision.","capability":"mailer:provision","inputSchema":{"type":"object","properties":{"owned_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"owned_domains UUID whose latest mail setup failed."}},"required":["owned_domain_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string","description":"The mailer_onboard_jobs UUID that was resumed (the same job, not a new one)."},"owned_domain_id":{"type":"string"},"domain":{"type":"string"},"stage":{"type":"string","description":"The step the job resumes from."},"mail_setup":{"type":"string","const":"resumed"}},"required":["job_id","owned_domain_id","domain","stage","mail_setup"],"description":"The failed setup is running again from where it stopped; poll get_mailer_domain_status."}},{"name":"get_mailer_domain_status","description":"Poll the mail status of a domain you own: whether it also backs a phantom site, the provisioning stage, DNS/verification state, DMARC policy, and the mailers on it (with each mailer's status and daily send limit). Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"owned_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"owned_domains UUID returned by launch_mailers."}},"required":["owned_domain_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"owned_domain_id":{"type":"string"},"domain":{"type":"string"},"backs_phantom":{"type":"boolean","description":"Whether this shared domain also backs a phantom site."},"backs_mail":{"type":"boolean"},"mail_status":{"description":"provisioning | dns_pending | verifying | active | failed | suspended","type":["string","null"]},"sending_subdomain":{"type":["string","null"]},"dmarc_policy":{"type":["string","null"]},"job":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"stage":{"type":"string"},"status":{"type":"string"},"error":{"type":["string","null"]}},"required":["id","stage","status","error"]},{"type":"null"}]},"mailers":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"status":{"type":"string"},"purpose":{"type":"string"},"daily_send_limit":{"type":"number","description":"Manual daily limit; 0 means the mailer follows the sending ramp."},"ready_at":{"description":"When the mailer became ready: the start of its sending ramp.","type":["string","null"]},"effective_daily_limit":{"type":"number","description":"Emails it may send today: the manual limit, else the ramp's."}},"required":["address","status","purpose","daily_send_limit","ready_at","effective_daily_limit"]}}},"required":["owned_domain_id","domain","backs_phantom","backs_mail","mail_status","sending_subdomain","dmarc_policy","job","mailers"]}},{"name":"list_mailers","description":"List every mailer (sending mailbox) in the org: those on Letter-registered domains and connected Google mailboxes (also in get_mail_connections), optionally filtered to one apex domain. Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"domain":{"description":"Filter to one apex domain (optional).","type":"string","maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"status":{"type":"string"},"purpose":{"type":"string"},"daily_send_limit":{"type":"number","description":"Manual daily limit; 0 means the mailer follows the sending ramp."},"ready_at":{"description":"When the mailer became ready: the start of its sending ramp.","type":["string","null"]},"effective_daily_limit":{"type":"number","description":"Emails it may send today: the manual limit, else the ramp's."},"warmup_error":{"description":"Why warmup stopped on a warming mailer (it lost access and couldn't reconnect); null when fine.","type":["string","null"]},"created_at":{"type":"string"}},"required":["id","address","status","purpose","daily_send_limit","ready_at","effective_daily_limit","warmup_error","created_at"]}}},"required":["mailers"]}},{"name":"list_mailer_messages","description":"Explore a mailer's recent mail: the messages it has sent and received (envelope only: from, to, subject, date, seen). Read live from the mailbox (IMAP, or the Gmail API for Google-connected mailboxes). Requires the mailer credential to be retrievable (MAIL_SECRET_KEY set when it was created). Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"folder":{"description":"Which mail to list. Defaults to all.","type":"string","enum":["all","inbox","sent"]},"limit":{"description":"Max messages (default 25, max 100).","type":"integer","minimum":1,"maximum":100}},"required":["mailer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"address":{"type":"string"},"messages":{"type":"array","items":{"type":"object","properties":{"uid":{"type":["number","string"]},"folder":{"type":"string","enum":["inbox","sent"]},"direction":{"type":"string","enum":["received","sent"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"subject":{"type":["string","null"]},"date":{"type":["string","null"]},"seen":{"type":"boolean"}},"required":["uid","folder","direction","from","to","subject","date","seen"]}}},"required":["address","messages"]}},{"name":"get_mailer_message","description":"Read one message in full: its plain-text body (up to 64 KB) plus from, to, subject and date. Use the uid and folder from list_mailer_messages to open a reply or a bounce. HTML-only messages return an empty text. Read live from the mailbox (IMAP, or the Gmail API for Google-connected mailboxes); requires the mailer credential to be retrievable (MAIL_SECRET_KEY set when it was created). Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"uid":{"anyOf":[{"type":"integer","minimum":1,"maximum":9007199254740991},{"type":"string","pattern":"^[0-9a-f]{1,32}$"}],"description":"Message uid (from list_mailer_messages): an IMAP UID number, or a Gmail message id string for Google-connected mailboxes."},"folder":{"type":"string","enum":["inbox","sent"],"description":"The folder the uid came from (the 'folder' field of list_mailer_messages)."}},"required":["mailer_id","uid","folder"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"address":{"type":"string"},"message":{"type":"object","properties":{"uid":{"type":["number","string"]},"folder":{"type":"string","enum":["inbox","sent"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"subject":{"type":["string","null"]},"date":{"type":["string","null"]},"text":{"type":"string","description":"Plain-text body; empty for an HTML-only message."},"truncated":{"type":"boolean","description":"True when the body was cut at 64 KB."}},"required":["uid","folder","from","to","subject","date","text","truncated"]}},"required":["address","message"]}},{"name":"add_email_suppression","description":"Add an address to the org's do-not-send (suppression) list. Suppressed addresses are never provisioned to or mailed. The compliance backbone for opt-outs, bounces, and complaints. Idempotent. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"address":{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Address to add to the org's do-not-send (suppression) list."},"reason":{"description":"Defaults to manual.","type":"string","enum":["unsubscribe","bounce","complaint","manual"]},"note":{"type":"string","maxLength":300}},"required":["address"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suppressed":{"type":"boolean","const":true},"address":{"type":"string"}},"required":["suppressed","address"]}},{"name":"list_email_suppressions","description":"List the org's do-not-send (suppression) list, newest first: address, reason (unsubscribe, bounce, complaint, manual), source and note. Optionally narrowed to one reason. Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"reason":{"description":"Only entries with this reason.","type":"string","enum":["unsubscribe","bounce","complaint","manual"]},"limit":{"description":"Max entries, newest first (default 100, max 500).","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suppressions":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"reason":{"type":"string","enum":["unsubscribe","bounce","complaint","manual"]},"source":{"type":["string","null"]},"note":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["address","reason","source","note","created_at"]}}},"required":["suppressions"]}},{"name":"remove_email_suppression","description":"Take an address off the do-not-send list. Only entries with reason 'manual' can be removed; unsubscribes, complaints and bounces stay on the list (they are legal opt-outs and reputation protection) and the call is refused with a 409. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"address":{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Address to take off the do-not-send list. Only 'manual' entries can be removed."}},"required":["address"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"address":{"type":"string"}},"required":["removed","address"]}},{"name":"set_mailer_forwarding","description":"Set (or clear, with forward_to=null) where a mailer's replies are forwarded. Every 5 minutes we read the mailbox and forward only replies to our sequences (from someone the mailer emailed in a sequence, or answering one of its sequence sends), sent from the mailer with Reply-To set to the original sender. Warmup mail and everything else stay in the mailbox. See list_mailer_forwards for each decision. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"forward_to":{"anyOf":[{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}],"description":"Inbox that receives the mailer's replies to sequences. Pass null to clear forwarding."}},"required":["mailer_id","forward_to"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"forward_to":{"type":["string","null"]}},"required":["mailer_id","forward_to"]}},{"name":"list_mailer_forwards","description":"What reply forwarding decided for a mailer, newest first: each inbound message forwarded or held, and why (reason: sequence_recipient, sequence_thread, no_sequence_match, bounce_notice, own_mail), plus last-7-day counts by outcome. A failed forward carries the send error; pending means the send was started but never confirmed. Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"outcome":{"description":"Only decisions with this outcome. Defaults to all. pending: claimed for forwarding but the send was never confirmed.","type":"string","enum":["pending","forwarded","held","failed"]},"limit":{"description":"Max decisions (default 25, max 100).","type":"integer","minimum":1,"maximum":100}},"required":["mailer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"forward_to":{"type":["string","null"]},"last_7_days":{"type":"object","properties":{"forwarded":{"type":"number"},"held":{"type":"number"},"failed":{"type":"number"},"pending":{"type":"number"}},"required":["forwarded","held","failed","pending"]},"forwards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message_key":{"type":"string"},"from_address":{"type":["string","null"]},"subject":{"type":["string","null"]},"received_at":{"type":["string","null"]},"forward_to":{"type":"string"},"outcome":{"type":"string","enum":["pending","forwarded","held","failed"]},"reason":{"type":"string","enum":["sequence_recipient","sequence_thread","no_sequence_match","bounce_notice","own_mail"]},"error":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","message_key","from_address","subject","received_at","forward_to","outcome","reason","error","created_at"]}}},"required":["mailer_id","forward_to","last_7_days","forwards"]}},{"name":"start_mailer_warmup","description":"Start warming a mailer so it can safely send cold email. The mailer is enrolled in the warmup engine and stays 'warming' until the engine reports it warm (at least 14 days and a warmup score of 85) before it becomes 'ready' (available for sending; days_left counts down the 14-day minimum, after which the score decides). Requires the mailer password to be retrievable (MAIL_SECRET_KEY set when it was created). Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."}},"required":["mailer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"status":{"type":"string","description":"The mailbox's new status: 'warming', 'ready', or 'paused'."},"warmup_started_at":{"type":["string","null"]},"days_left":{"description":"Whole days of warming remaining before the mailbox is available.","type":"number"}},"required":["mailer_id","status"]}},{"name":"pause_mailer_warmup","description":"Pause warming a mailer. Restart it later with start_mailer_warmup. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."}},"required":["mailer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"status":{"type":"string","description":"The mailbox's new status: 'warming', 'ready', or 'paused'."},"warmup_started_at":{"type":["string","null"]},"days_left":{"description":"Whole days of warming remaining before the mailbox is available.","type":"number"}},"required":["mailer_id","status"]}},{"name":"set_mailer_daily_send_limit","description":"Set how many emails a mailer may send per day (1 to 500), overriding the automatic sending ramp (20 a day once ready, +5 a day, up to 50); 0 returns it to the ramp. Refused with a 409 while the mailer is provisioning or warming: it sends nothing until it is ready. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"daily_send_limit":{"type":"integer","minimum":0,"maximum":500,"description":"Emails per day this mailer may send. 0 = automatic: the sending ramp (20 a day once ready, +5 a day, up to 50)."}},"required":["mailer_id","daily_send_limit"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"daily_send_limit":{"type":"number"}},"required":["mailer_id","daily_send_limit"]}},{"name":"retire_mailer","description":"Take a mailer out of service: stop its warmup and mark it 'disabled' (sequences stop picking it; send history is kept). With release_mailbox=true it also deletes the hosted mailbox at the provider, which stops its per-mailbox billing and cannot be undone. If the release fails the mailer stays disabled and the call can be retried. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mailer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Mailbox UUID (from list_mailers)."},"release_mailbox":{"description":"Also delete the hosted mailbox at the provider, which stops its per-mailbox billing. Not reversible. Omitted = false: the mailer is only disabled.","type":"boolean"}},"required":["mailer_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mailer_id":{"type":"string"},"status":{"type":"string","const":"disabled"},"mailbox_released":{"type":"boolean","description":"True when the hosted mailbox was deleted at the provider."}},"required":["mailer_id","status","mailbox_released"]}},{"name":"list_sequences","description":"List the org's email sequences (archived ones excluded), newest first: each one's flow, status, attached mailers, enrollment counts by status, total sends, and the per-step funnel (queued / sent / failed per block). Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sequences":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","active","paused","archived"],"description":"draft = not sending; active = sending; paused = stop sending (resume with active); archived = retired."},"flow":{"type":"object","properties":{"version":{"type":"number","const":1},"entry":{"type":["string","null"]},"blocks":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"email"},"subject":{"type":"string","maxLength":200,"description":"Blank on a follow-up threads it as a reply (Re: ...)."},"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Email body. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}}."},"delay_hours":{"type":"number","minimum":0,"maximum":720,"description":"Hours to wait before THIS email sends (from enrollment or the previous send)."},"media":{"description":"When a picture this email carries ({{custom:<picture column>}}) isn't ready for someone. Omitted = wait 1 hour, then send without it.","type":"object","properties":{"max_wait_hours":{"type":"integer","minimum":0,"maximum":168,"description":"How long to wait for a person's picture"},"then":{"type":"string","enum":["send_without","stop","review"],"description":"If it never comes: send without it, stop this person, or send the email to review"}},"required":["max_wait_hours","then"]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"The block that follows, or null to end this path."}},"required":["id","kind","subject","body","delay_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"ab_test"},"split_a":{"type":"integer","minimum":10,"maximum":90,"description":"Percent of recipients routed to arm A."},"a":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm A."},"b":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm B."}},"required":["id","kind","split_a","a","b"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"review"},"auto_send_after_hours":{"anyOf":[{"type":"integer","minimum":1,"maximum":720},{"type":"null"}],"description":"Hours a queued email waits for review before it auto-sends; null waits forever."},"dispatch_id":{"description":"The dispatch this stage queues to (list_dispatches); null or absent = the org's default dispatch.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","auto_send_after_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"cake"},"image_field":{"type":"string","minLength":1,"maxLength":80,"description":"The prospect field holding the image's public https URL (PNG/JPEG/WebP, max 10MB): `custom:<column key>` for a custom column, or a built-in text field."},"address_field":{"description":"The prospect field holding the delivery street address; blank looks it up from the prospect's website.","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"cake_size":{"description":"Daymaker cake size; blank = Daymaker's default.","anyOf":[{"type":"string","enum":["6in","8in","10in","12in","half_sheet","sheet"]},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","image_field","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"frontrunner"},"agent_id":{"type":"string","minLength":1,"maxLength":120,"description":"The Frontrunner agent (app.usefr.com, `fr agent list`) that runs the LinkedIn automation."},"brief":{"type":"string","minLength":1,"maxLength":2000,"description":"What the agent should do for this recipient, e.g. connect and send a note. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}} {{linkedin_url}} {{title}} {{email}}. The recipient's details are appended either way."},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","agent_id","brief","next"]}]}},"settings":{"type":"object","properties":{"weekdays_only":{"description":"Send only on the recipient's local Monday to Friday.","type":"boolean"}}}},"required":["version","entry","blocks"]},"default_lede":{"type":["string","null"]},"stop_on_reply":{"type":"boolean"},"review_gate_hours":{"type":["number","null"]},"opt_out_footer":{"type":"boolean","description":"Whether every email appends the opt-in \"reply and say so\" opt-out line (off by default)."},"postal_address_footer":{"type":"boolean","description":"Whether every email's footer prints the org's CAN-SPAM postal address (on by default)."},"unsubscribe_links":{"type":"boolean","description":"Whether emails carry the one-click unsubscribe footer link and List-Unsubscribe headers (on by default)."},"detect_reply_opt_outs":{"type":"boolean","description":"Whether \"unsubscribe\"-style replies suppress the address org-wide (on by default)."},"test_mode":{"type":"boolean","description":"Whether the sequence is in test mode: it walks as live but no email is actually sent (off by default)."},"send_window_start_minute":{"type":"number","description":"Minute of day the send window opens (default 540 = 9:00). Equal to the end turns the window off; above the end wraps midnight."},"send_window_end_minute":{"type":"number","description":"Minute of day the send window closes (default 1020 = 17:00)."},"send_window_timezone":{"type":"string","description":"The sender's IANA timezone — the window's fallback when a recipient's timezone can't be inferred from their location."},"enroll_on_cake":{"description":"Daymaker cake stage that enrolls the cake's recipient (paid / scheduled / out_for_delivery / delivered); null = never.","type":["string","null"]},"enroll_on_cake_list_id":{"description":"Prospect list the cake trigger is scoped to; null = any Prospect in the org.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"enrollment_counts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Enrollments by status."},"sends_count":{"type":"number","description":"Emails sent, including test-mode sends."},"test_sends_count":{"type":"number","description":"Of sends_count, how many were test-mode sends (nothing was emailed)."},"bounced_count":{"type":"number","description":"Sent emails that later hard-bounced."},"next_send_at":{"description":"When the soonest active recipient's next email is due; null when nobody is waiting.","type":["string","null"]},"step_stats":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"queued":{"type":"number","description":"Active recipients waiting AT this block."},"sent":{"type":"number","description":"Emails this block has sent."},"failed":{"type":"number","description":"Recipients whose sequence died at this block."}},"required":["queued","sent","failed"]},"description":"Per-block funnel, keyed by block id."},"mailers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"status":{"type":"string"}},"required":["id","address","status"]},"description":"The mailers this sequence sends from."},"campaign_build_id":{"description":"The campaign build that made it from one prompt (get_campaign_build); null when none did.","type":["string","null"]}},"required":["id","name","status","flow","default_lede","stop_on_reply","review_gate_hours","opt_out_footer","postal_address_footer","unsubscribe_links","detect_reply_opt_outs","test_mode","send_window_start_minute","send_window_end_minute","send_window_timezone","enroll_on_cake","enroll_on_cake_list_id","created_at","updated_at","enrollment_counts","sends_count","test_sends_count","bounced_count","next_send_at","step_stats","mailers"]}}},"required":["sequences"]}},{"name":"get_sequence","description":"One sequence: its flow and settings, the latest 200 enrollments (who is in it, where each recipient is, when their next email is due, any error), and per-block outcomes including who replied at each step. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{"sequence_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["sequence_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sequence":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","active","paused","archived"],"description":"draft = not sending; active = sending; paused = stop sending (resume with active); archived = retired."},"flow":{"type":"object","properties":{"version":{"type":"number","const":1},"entry":{"type":["string","null"]},"blocks":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"email"},"subject":{"type":"string","maxLength":200,"description":"Blank on a follow-up threads it as a reply (Re: ...)."},"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Email body. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}}."},"delay_hours":{"type":"number","minimum":0,"maximum":720,"description":"Hours to wait before THIS email sends (from enrollment or the previous send)."},"media":{"description":"When a picture this email carries ({{custom:<picture column>}}) isn't ready for someone. Omitted = wait 1 hour, then send without it.","type":"object","properties":{"max_wait_hours":{"type":"integer","minimum":0,"maximum":168,"description":"How long to wait for a person's picture"},"then":{"type":"string","enum":["send_without","stop","review"],"description":"If it never comes: send without it, stop this person, or send the email to review"}},"required":["max_wait_hours","then"]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"The block that follows, or null to end this path."}},"required":["id","kind","subject","body","delay_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"ab_test"},"split_a":{"type":"integer","minimum":10,"maximum":90,"description":"Percent of recipients routed to arm A."},"a":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm A."},"b":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm B."}},"required":["id","kind","split_a","a","b"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"review"},"auto_send_after_hours":{"anyOf":[{"type":"integer","minimum":1,"maximum":720},{"type":"null"}],"description":"Hours a queued email waits for review before it auto-sends; null waits forever."},"dispatch_id":{"description":"The dispatch this stage queues to (list_dispatches); null or absent = the org's default dispatch.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","auto_send_after_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"cake"},"image_field":{"type":"string","minLength":1,"maxLength":80,"description":"The prospect field holding the image's public https URL (PNG/JPEG/WebP, max 10MB): `custom:<column key>` for a custom column, or a built-in text field."},"address_field":{"description":"The prospect field holding the delivery street address; blank looks it up from the prospect's website.","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"cake_size":{"description":"Daymaker cake size; blank = Daymaker's default.","anyOf":[{"type":"string","enum":["6in","8in","10in","12in","half_sheet","sheet"]},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","image_field","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"frontrunner"},"agent_id":{"type":"string","minLength":1,"maxLength":120,"description":"The Frontrunner agent (app.usefr.com, `fr agent list`) that runs the LinkedIn automation."},"brief":{"type":"string","minLength":1,"maxLength":2000,"description":"What the agent should do for this recipient, e.g. connect and send a note. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}} {{linkedin_url}} {{title}} {{email}}. The recipient's details are appended either way."},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","agent_id","brief","next"]}]}},"settings":{"type":"object","properties":{"weekdays_only":{"description":"Send only on the recipient's local Monday to Friday.","type":"boolean"}}}},"required":["version","entry","blocks"]},"default_lede":{"type":["string","null"]},"stop_on_reply":{"type":"boolean"},"review_gate_hours":{"type":["number","null"]},"opt_out_footer":{"type":"boolean","description":"Whether every email appends the opt-in \"reply and say so\" opt-out line (off by default)."},"postal_address_footer":{"type":"boolean","description":"Whether every email's footer prints the org's CAN-SPAM postal address (on by default)."},"unsubscribe_links":{"type":"boolean","description":"Whether emails carry the one-click unsubscribe footer link and List-Unsubscribe headers (on by default)."},"detect_reply_opt_outs":{"type":"boolean","description":"Whether \"unsubscribe\"-style replies suppress the address org-wide (on by default)."},"test_mode":{"type":"boolean","description":"Whether the sequence is in test mode: it walks as live but no email is actually sent (off by default)."},"send_window_start_minute":{"type":"number","description":"Minute of day the send window opens (default 540 = 9:00). Equal to the end turns the window off; above the end wraps midnight."},"send_window_end_minute":{"type":"number","description":"Minute of day the send window closes (default 1020 = 17:00)."},"send_window_timezone":{"type":"string","description":"The sender's IANA timezone — the window's fallback when a recipient's timezone can't be inferred from their location."},"enroll_on_cake":{"description":"Daymaker cake stage that enrolls the cake's recipient (paid / scheduled / out_for_delivery / delivered); null = never.","type":["string","null"]},"enroll_on_cake_list_id":{"description":"Prospect list the cake trigger is scoped to; null = any Prospect in the org.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","status","flow","default_lede","stop_on_reply","review_gate_hours","opt_out_footer","postal_address_footer","unsubscribe_links","detect_reply_opt_outs","test_mode","send_window_start_minute","send_window_end_minute","send_window_timezone","enroll_on_cake","enroll_on_cake_list_id","created_at","updated_at"]},"enrollments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"mailer_id":{"description":"Assigned at the first send; null until then.","type":["string","null"]},"recipient_email":{"type":"string"},"recipient_name":{"type":["string","null"]},"recipient_company":{"type":["string","null"]},"lede_opener":{"type":["string","null"]},"status":{"type":"string","enum":["active","completed","replied","stopped","suppressed","failed"]},"current_block_id":{"type":["string","null"]},"next_send_at":{"type":["string","null"]},"last_error":{"type":["string","null"]},"cakes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"status":{"type":"string","enum":["drafted","skipped","failed"]},"gift_id":{"type":"string"},"checkout_url":{"description":"Stripe link a person opens to pay; valid about 24 hours.","type":"string"},"error":{"description":"Why the cake was skipped or failed.","type":"string"},"at":{"type":"string"}},"required":["status","at"]},"description":"Outcome of each cake step this recipient reached, keyed by cake block id."},"frontrunner_runs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"status":{"type":"string","enum":["pending","started","skipped","failed"],"description":"pending = claimed, agent start in flight. A pending older than one sequence tick was lost to a crash and is never retried."},"run_id":{"description":"The Frontrunner run the step started.","type":"string"},"error":{"description":"Why the step was skipped or failed.","type":"string"},"at":{"type":"string"}},"required":["status","at"]},"description":"Outcome of each Frontrunner (LinkedIn) step this recipient reached, keyed by block id."},"created_at":{"type":"string"}},"required":["id","mailer_id","recipient_email","recipient_name","recipient_company","lede_opener","status","current_block_id","next_send_at","last_error","cakes","frontrunner_runs","created_at"]},"description":"The latest 200 enrollments, newest first."},"block_stats":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sent":{"type":"number"},"replies":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]},"company":{"type":["string","null"]},"domain":{"type":["string","null"]}},"required":["email","name","company","domain"]}}},"required":["sent","replies"]},"description":"Per email block: sends made and who replied at that step."}},"required":["sequence","enrollments","block_stats"]}},{"name":"create_sequence","description":"Create an email sequence from a flow document: email blocks (subject, body, delay_hours) chained by `next`, optional ab_test forks, review stages (emails after a review stage wait in the review queue until approved or their deadline passes), cake steps (a Daymaker cake drafted for a person to pay) and frontrunner steps (a LinkedIn automation; staff only). Optionally attach the org's mailers it sends from (list_mailers). A new sequence starts as a draft: nothing sends until it is set active with update_sequence and recipients are enrolled. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"flow":{"type":"object","properties":{"version":{"type":"number","const":1},"entry":{"type":"string","minLength":1,"maxLength":40,"description":"Id of the block the walk starts at."},"blocks":{"type":"object","propertyNames":{"type":"string","maxLength":40},"additionalProperties":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"email"},"subject":{"type":"string","maxLength":200,"description":"Blank on a follow-up threads it as a reply (Re: ...)."},"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Email body. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}}."},"delay_hours":{"type":"number","minimum":0,"maximum":720,"description":"Hours to wait before THIS email sends (from enrollment or the previous send)."},"media":{"description":"When a picture this email carries ({{custom:<picture column>}}) isn't ready for someone. Omitted = wait 1 hour, then send without it.","type":"object","properties":{"max_wait_hours":{"type":"integer","minimum":0,"maximum":168,"description":"How long to wait for a person's picture"},"then":{"type":"string","enum":["send_without","stop","review"],"description":"If it never comes: send without it, stop this person, or send the email to review"}},"required":["max_wait_hours","then"]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"The block that follows, or null to end this path."}},"required":["id","kind","subject","body","delay_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"ab_test"},"split_a":{"type":"integer","minimum":10,"maximum":90,"description":"Percent of recipients routed to arm A."},"a":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm A."},"b":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm B."}},"required":["id","kind","split_a","a","b"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"review"},"auto_send_after_hours":{"anyOf":[{"type":"integer","minimum":1,"maximum":720},{"type":"null"}],"description":"Hours a queued email waits for review before it auto-sends; null waits forever."},"dispatch_id":{"description":"The dispatch this stage queues to (list_dispatches); null or absent = the org's default dispatch.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","auto_send_after_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"cake"},"image_field":{"type":"string","minLength":1,"maxLength":80,"description":"The prospect field holding the image's public https URL (PNG/JPEG/WebP, max 10MB): `custom:<column key>` for a custom column, or a built-in text field."},"address_field":{"description":"The prospect field holding the delivery street address; blank looks it up from the prospect's website.","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"cake_size":{"description":"Daymaker cake size; blank = Daymaker's default.","anyOf":[{"type":"string","enum":["6in","8in","10in","12in","half_sheet","sheet"]},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","image_field","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"frontrunner"},"agent_id":{"type":"string","minLength":1,"maxLength":120,"description":"The Frontrunner agent (app.usefr.com, `fr agent list`) that runs the LinkedIn automation."},"brief":{"type":"string","minLength":1,"maxLength":2000,"description":"What the agent should do for this recipient, e.g. connect and send a note. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}} {{linkedin_url}} {{title}} {{email}}. The recipient's details are appended either way."},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","agent_id","brief","next"]}]},"description":"Blocks keyed by their id. Blocks chain by `next` pointers; ab_test forks into arms a/b."},"settings":{"description":"Sequence-wide switches kept in the flow document.","type":"object","properties":{"weekdays_only":{"description":"Send only on the recipient's local Monday to Friday. Absent = every day.","type":"boolean"}}}},"required":["version","entry","blocks"],"description":"The sequence's flow document: email blocks (subject, body, delay), ab_test forks, review stages, optional cake steps (a Daymaker cake printed with an image from a prospect field) and optional frontrunner steps (a Frontrunner LinkedIn automation started for the recipient), chained by id."},"mailer_ids":{"maxItems":20,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"The org's mailers this sequence sends from (sends distribute across them). Replaces the attached set."},"default_lede":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"Fallback opener line, used for any recipient whose opener (the {{lede}} token) comes up empty so a first line is never left blank. Tokens {{first_name}} {{name}} {{company}} {{sender_name}} are allowed. Null or blank clears it."},"opt_out_footer":{"type":"boolean","description":"Opt-in: append \"If you'd rather not hear from me, just reply and say so.\" to every email's footer, above the unsubscribe link. Off by default."},"postal_address_footer":{"type":"boolean","description":"Print the org's CAN-SPAM postal address in every email's footer when one is on file (default on; strongly recommended)."},"unsubscribe_links":{"type":"boolean","description":"Include the signed one-click unsubscribe link in the footer and the List-Unsubscribe headers (default on; strongly recommended, and required by Gmail/Yahoo for bulk senders)."},"detect_reply_opt_outs":{"type":"boolean","description":"Classify replies like \"unsubscribe\" or \"remove me\" as opt-outs that suppress the address org-wide (default on; strongly recommended). A reply always stops the sequence for that recipient regardless."},"test_mode":{"type":"boolean","description":"Test mode (internal Letter accounts only to turn on): the sequence walks exactly as live, but no email is actually sent, no cake is drafted and no prospect is marked contacted; each would-be send is logged as a test send. Default off."},"send_window_start_minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minute of day (0–1439) the send window opens; default 540 (9:00). A start above the end wraps midnight (overnight window); equal start and end turns the window off (send at any hour)."},"send_window_end_minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minute of day (0–1439) the send window closes; default 1020 (17:00)."},"send_window_timezone":{"type":"string","maxLength":64,"description":"The SENDER's IANA timezone (e.g. America/New_York); the window falls back to it when a recipient's timezone can't be inferred from their location. Default UTC."},"enroll_on_cake":{"anyOf":[{"type":"string","enum":["paid","scheduled","out_for_delivery","delivered"]},{"type":"null"}],"description":"Start this sequence for a recipient when their Daymaker cake reaches this stage (requires the org's Daymaker account connected in Settings → Integrations; list_cakes shows each cake and why it did or didn't enroll). The cake is matched to a Prospect by gift metadata, recipient email, or contact name + company; only active sequences enroll, each cake at most once per sequence. null = never."},"enroll_on_cake_list_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Scope enroll_on_cake to one prospect list (list_prospect_lists): a cake enrolls its recipient only when the matched Prospect is in this list, and is recorded as 'skipped: not in sequence list' otherwise (final for that cake: moving the Prospect into the list later doesn't enroll them from it). Use it when several sequences enroll on cakes (e.g. one per brand). null = any Prospect in the org (default)."}},"required":["name","flow"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"update_sequence","description":"Edit a sequence: rename it, replace its flow, replace its attached mailers, change its status, adjust its footer/compliance rails (opt_out_footer, postal_address_footer, unsubscribe_links, detect_reply_opt_outs; the last three default on and are strongly recommended), adjust its send window (send_window_start_minute / send_window_end_minute, default 9:00–17:00, evaluated in each recipient's inferred timezone with send_window_timezone — the sender's — as the fallback), set its Daymaker cake trigger (enroll_on_cake, scoped to one prospect list with enroll_on_cake_list_id), or switch test_mode (turning it on is for internal Letter accounts only: the sequence then walks exactly as live but nothing is actually sent). status 'active' starts sending to enrolled recipients, 'paused' stops sending (set 'active' again to resume), 'archived' retires it. Only the fields you pass change. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"flow":{"type":"object","properties":{"version":{"type":"number","const":1},"entry":{"type":"string","minLength":1,"maxLength":40,"description":"Id of the block the walk starts at."},"blocks":{"type":"object","propertyNames":{"type":"string","maxLength":40},"additionalProperties":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"email"},"subject":{"type":"string","maxLength":200,"description":"Blank on a follow-up threads it as a reply (Re: ...)."},"body":{"type":"string","minLength":1,"maxLength":5000,"description":"Email body. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}}."},"delay_hours":{"type":"number","minimum":0,"maximum":720,"description":"Hours to wait before THIS email sends (from enrollment or the previous send)."},"media":{"description":"When a picture this email carries ({{custom:<picture column>}}) isn't ready for someone. Omitted = wait 1 hour, then send without it.","type":"object","properties":{"max_wait_hours":{"type":"integer","minimum":0,"maximum":168,"description":"How long to wait for a person's picture"},"then":{"type":"string","enum":["send_without","stop","review"],"description":"If it never comes: send without it, stop this person, or send the email to review"}},"required":["max_wait_hours","then"]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"The block that follows, or null to end this path."}},"required":["id","kind","subject","body","delay_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"ab_test"},"split_a":{"type":"integer","minimum":10,"maximum":90,"description":"Percent of recipients routed to arm A."},"a":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm A."},"b":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}],"description":"Head block of arm B."}},"required":["id","kind","split_a","a","b"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"review"},"auto_send_after_hours":{"anyOf":[{"type":"integer","minimum":1,"maximum":720},{"type":"null"}],"description":"Hours a queued email waits for review before it auto-sends; null waits forever."},"dispatch_id":{"description":"The dispatch this stage queues to (list_dispatches); null or absent = the org's default dispatch.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","auto_send_after_hours","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"cake"},"image_field":{"type":"string","minLength":1,"maxLength":80,"description":"The prospect field holding the image's public https URL (PNG/JPEG/WebP, max 10MB): `custom:<column key>` for a custom column, or a built-in text field."},"address_field":{"description":"The prospect field holding the delivery street address; blank looks it up from the prospect's website.","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"cake_size":{"description":"Daymaker cake size; blank = Daymaker's default.","anyOf":[{"type":"string","enum":["6in","8in","10in","12in","half_sheet","sheet"]},{"type":"null"}]},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","image_field","next"]},{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":40},"kind":{"type":"string","const":"frontrunner"},"agent_id":{"type":"string","minLength":1,"maxLength":120,"description":"The Frontrunner agent (app.usefr.com, `fr agent list`) that runs the LinkedIn automation."},"brief":{"type":"string","minLength":1,"maxLength":2000,"description":"What the agent should do for this recipient, e.g. connect and send a note. Tokens: {{first_name}} {{name}} {{company}} {{lede}} {{sender_name}} {{linkedin_url}} {{title}} {{email}}. The recipient's details are appended either way."},"next":{"anyOf":[{"type":"string","minLength":1,"maxLength":40},{"type":"null"}]}},"required":["id","kind","agent_id","brief","next"]}]},"description":"Blocks keyed by their id. Blocks chain by `next` pointers; ab_test forks into arms a/b."},"settings":{"description":"Sequence-wide switches kept in the flow document.","type":"object","properties":{"weekdays_only":{"description":"Send only on the recipient's local Monday to Friday. Absent = every day.","type":"boolean"}}}},"required":["version","entry","blocks"],"description":"The sequence's flow document: email blocks (subject, body, delay), ab_test forks, review stages, optional cake steps (a Daymaker cake printed with an image from a prospect field) and optional frontrunner steps (a Frontrunner LinkedIn automation started for the recipient), chained by id."},"status":{"type":"string","enum":["draft","active","paused","archived"],"description":"draft = not sending; active = sending; paused = stop sending (resume with active); archived = retired."},"mailer_ids":{"maxItems":20,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"The org's mailers this sequence sends from (sends distribute across them). Replaces the attached set."},"default_lede":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"Fallback opener line, used for any recipient whose opener (the {{lede}} token) comes up empty so a first line is never left blank. Tokens {{first_name}} {{name}} {{company}} {{sender_name}} are allowed. Null or blank clears it."},"opt_out_footer":{"type":"boolean","description":"Opt-in: append \"If you'd rather not hear from me, just reply and say so.\" to every email's footer, above the unsubscribe link. Off by default."},"postal_address_footer":{"type":"boolean","description":"Print the org's CAN-SPAM postal address in every email's footer when one is on file (default on; strongly recommended)."},"unsubscribe_links":{"type":"boolean","description":"Include the signed one-click unsubscribe link in the footer and the List-Unsubscribe headers (default on; strongly recommended, and required by Gmail/Yahoo for bulk senders)."},"detect_reply_opt_outs":{"type":"boolean","description":"Classify replies like \"unsubscribe\" or \"remove me\" as opt-outs that suppress the address org-wide (default on; strongly recommended). A reply always stops the sequence for that recipient regardless."},"test_mode":{"type":"boolean","description":"Test mode (internal Letter accounts only to turn on): the sequence walks exactly as live, but no email is actually sent, no cake is drafted and no prospect is marked contacted; each would-be send is logged as a test send. Default off."},"send_window_start_minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minute of day (0–1439) the send window opens; default 540 (9:00). A start above the end wraps midnight (overnight window); equal start and end turns the window off (send at any hour)."},"send_window_end_minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minute of day (0–1439) the send window closes; default 1020 (17:00)."},"send_window_timezone":{"type":"string","maxLength":64,"description":"The SENDER's IANA timezone (e.g. America/New_York); the window falls back to it when a recipient's timezone can't be inferred from their location. Default UTC."},"enroll_on_cake":{"anyOf":[{"type":"string","enum":["paid","scheduled","out_for_delivery","delivered"]},{"type":"null"}],"description":"Start this sequence for a recipient when their Daymaker cake reaches this stage (requires the org's Daymaker account connected in Settings → Integrations; list_cakes shows each cake and why it did or didn't enroll). The cake is matched to a Prospect by gift metadata, recipient email, or contact name + company; only active sequences enroll, each cake at most once per sequence. null = never."},"enroll_on_cake_list_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Scope enroll_on_cake to one prospect list (list_prospect_lists): a cake enrolls its recipient only when the matched Prospect is in this list, and is recorded as 'skipped: not in sequence list' otherwise (final for that cake: moving the Prospect into the list later doesn't enroll them from it). Use it when several sequences enroll on cakes (e.g. one per brand). null = any Prospect in the org (default)."},"sequence_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["sequence_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"list_cakes","description":"The org's Daymaker cakes (every cake its connected Daymaker account ordered, on the Daymaker website or by a sequence's cake step), most recently updated first: where each one is (draft / paid / scheduled / out_for_delivery / delivered / delivery_failed / cancelled), delivery time and photos, the Prospect it matched, which sequences it started (a sequence's enroll_on_cake), and enroll_blocker when it can't start one. connection says whether Daymaker is connected and when it last synced. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{"stage":{"description":"Only cakes at this stage: draft (unpaid), paid, scheduled, out_for_delivery, delivered, delivery_failed, cancelled.","type":"string","enum":["draft","paid","scheduled","out_for_delivery","delivered","delivery_failed","cancelled"]},"limit":{"description":"Most recently updated first; default 200.","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connection":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether the org's Daymaker account is connected (Settings → Integrations)."},"webhook":{"type":"boolean","description":"Whether Daymaker pushes events; false = a 15-minute poll keeps cakes current."},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]}},"required":["connected","webhook","last_synced_at","last_sync_error"]},"cakes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"gift_id":{"type":"string","description":"Daymaker gift id; one gift can carry several cakes."},"recipient_id":{"type":"string"},"gift_name":{"type":["string","null"]},"order_number":{"type":["string","null"]},"stage":{"type":"string","description":"draft (unpaid) / paid / scheduled / out_for_delivery / delivered / delivery_failed / cancelled."},"company":{"type":["string","null"]},"contact_name":{"type":["string","null"]},"city":{"type":["string","null"]},"recipient_email":{"type":["string","null"]},"prospect_id":{"type":["string","null"]},"paid_at":{"type":["string","null"]},"scheduled_for":{"description":"Requested delivery date or window, as Daymaker reports it.","type":["string","null"]},"delivered_at":{"type":["string","null"]},"failure_reason":{"type":["string","null"]},"cake_photo_url":{"type":["string","null"]},"delivery_photo_url":{"type":["string","null"]},"updated_at":{"type":"string"},"prospect":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":["string","null"]},"status":{"type":"string"}},"required":["id","email","name","status"]},{"type":"null"}],"description":"The Prospect this cake was matched to, if any."},"triggered":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"at":{"type":"string"},"stage":{"type":"string","description":"The cake's stage when it enrolled the recipient."},"result":{"type":"string","description":"enrolled / suppressed / replied_on_linkedin / already_enrolled, or error: <why> (retried on the next webhook or poll)."}},"required":["at","stage","result"]},"description":"Sequences this cake started, keyed by sequence id."},"enroll_blocker":{"description":"Why this cake can't start a sequence; null when it can.","type":["string","null"]}},"required":["id","gift_id","recipient_id","gift_name","order_number","stage","company","contact_name","city","recipient_email","prospect_id","paid_at","scheduled_for","delivered_at","failure_reason","cake_photo_url","delivery_photo_url","updated_at","prospect","triggered","enroll_blocker"]}}},"required":["connection","cakes"]}},{"name":"sync_cakes","description":"Re-read the connected Daymaker account now instead of waiting for its webhook or the 15-minute poll. A cake that moved into a sequence's enroll_on_cake stage ENROLLS its recipient (and in an active sequence that mails them). Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"gifts":{"type":"number","description":"Gifts re-read from Daymaker (settled ones are skipped)."},"cakes":{"type":"number"},"enrolled":{"type":"number","description":"Recipients enrolled into sequences by this sync."}},"required":["gifts","cakes","enrolled"]}},{"name":"connect_daymaker","description":"Connect (or re-key) the org's Daymaker account with its Partner API key (dm_live_… / dm_test_…, made in Daymaker under Connect AI) so Letter can read cake deliveries (list_cakes) and start sequences from them (enroll_on_cake). The key is checked against Daymaker before it is stored (encrypted; never returned); a webhook is registered on the account and a 15-minute poll backs it up. Cakes ordered before the connection are recorded but enroll nobody. Nothing is ever paid for. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"api_key":{"type":"string","minLength":8,"maxLength":200,"description":"The Daymaker Partner API key (dm_live_… or dm_test_…) from Daymaker → Connect AI."}},"required":["api_key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether the org's Daymaker account is connected (Settings → Integrations)."},"webhook":{"type":"boolean","description":"Whether Daymaker pushes events; false = a 15-minute poll keeps cakes current."},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"warning":{"description":"Set when the webhook couldn't be registered; the poll still runs.","type":["string","null"]}},"required":["connected","webhook","last_synced_at","last_sync_error","warning"]}},{"name":"disconnect_daymaker","description":"Disconnect the org's Daymaker account: removes Letter's webhook from it and forgets the key. Idempotent. Cakes already mirrored stay listed; no new cake starts a sequence until it is connected again. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","const":false}},"required":["connected"]}},{"name":"get_heyreach_webhook","description":"Whether the org has a HeyReach webhook URL, so a LinkedIn reply in HeyReach stops that prospect's sequence email. The URL itself is shown only when it is made (rotate_heyreach_webhook). Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether the org has a HeyReach webhook URL (its token is never shown again)."},"created_at":{"description":"When the current webhook URL was made; null when not connected.","type":["string","null"]}},"required":["connected","created_at"]}},{"name":"rotate_heyreach_webhook","description":"Create (or rotate) the org's HeyReach webhook URL. Paste it into HeyReach → Integrations → Webhooks, subscribed to EVERY_MESSAGE_REPLY_RECEIVED: when a lead replies on LinkedIn, the Prospect with that LinkedIn URL is marked replied and none of their sequence emails send (active enrollments close as replied; later enrollments are recorded as replied). Connection acceptances never stop email. The URL is shown once; rotating makes the old one stop working. Staff org admins only. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"webhook_url":{"type":"string","description":"Paste into HeyReach → Integrations → Webhooks. Shown once; rotating replaces it (the old URL stops working)."},"events":{"type":"array","items":{"type":"string"},"description":"The HeyReach event types to subscribe the webhook to."},"created_at":{"type":"string"}},"required":["webhook_url","events","created_at"]}},{"name":"disconnect_heyreach_webhook","description":"Remove the org's HeyReach webhook URL: HeyReach's deliveries are refused from then on. Idempotent. Prospects already marked as replied on LinkedIn stay marked. Staff org admins only. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","const":false}},"required":["connected"]}},{"name":"enroll_in_sequence","description":"Enroll people into a sequence: either hand-entered recipients or a whole prospect list (list_id; unsubscribed and archived prospects stay out). Addresses on the do-not-send list are recorded as suppressed and never mailed; people who already replied on LinkedIn (HeyReach) are recorded as replied (linkedin_replied) and never mailed; people already in the sequence are skipped. In an active sequence this MAILS them on the flow's schedule, and a frontrunner (LinkedIn) step at the start of the flow fires for them right away. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"recipients":{"description":"Hand-entered recipients. Pass this OR list_id.","minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"company":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}},"required":["email"]}},"list_id":{"description":"Enroll a whole prospect list (unsubscribed and archived prospects stay out; capped per batch). Pass this OR recipients.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"sequence_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["sequence_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"enrolled":{"type":"number","description":"Recipients now active in the sequence."},"suppressed":{"type":"number","description":"On the do-not-send list: recorded, never mailed."},"linkedin_replied":{"type":"number","description":"Already replied on LinkedIn (HeyReach): recorded as replied, never mailed."},"skipped":{"type":"number","description":"Already enrolled in this sequence."}},"required":["enrolled","suppressed","linkedin_replied","skipped"]}},{"name":"stop_sequence_enrollment","description":"Stop one recipient's enrollment (get_sequence lists enrollment ids). They receive nothing further from this sequence. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"sequence_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"enrollment_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The enrollment (one recipient in the sequence) to stop."}},"required":["sequence_id","enrollment_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"get_frontrunner_connection","description":"Staff only (internal Letter accounts). Whether the org has a Frontrunner (app.usefr.com) connection, which sequence frontrunner (LinkedIn) steps run through, and which workspace. Never returns the key. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean"},"workspace":{"type":"string"},"connected_at":{"type":"string"}},"required":["connected"]}},{"name":"connect_frontrunner","description":"Staff only (internal Letter accounts). Connect (or replace) the org's Frontrunner workspace with an API key. The key is checked by listing the workspace's agents before it is stored (encrypted); the agents come back so a frontrunner step can name one. Needs a key minted by an org admin. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"workspace":{"type":"string","minLength":1,"maxLength":120,"description":"The Frontrunner workspace id: the <workspace> in app.usefr.com/api/v1/<workspace>/…"},"api_key":{"type":"string","minLength":1,"maxLength":500,"description":"A Frontrunner API key (fr_…), from Settings → API keys at app.usefr.com."}},"required":["workspace","api_key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"workspace":{"type":"string"},"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"description":"The workspace's agents; a frontrunner step names one by id."}},"required":["workspace","agents"]}},{"name":"disconnect_frontrunner","description":"Staff only (internal Letter accounts). Remove the org's Frontrunner connection. Sequence frontrunner steps are then recorded as skipped until it is connected again. Needs a key minted by an org admin. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean","const":true}},"required":["disconnected"]}},{"name":"list_frontrunner_agents","description":"Staff only (internal Letter accounts). The connected Frontrunner workspace's agents (id + name). A sequence frontrunner step's agent_id is one of these. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["agents"]}},{"name":"list_dispatches","description":"The org's dispatches (named review desks), oldest first — the oldest is the default — each with how many emails are queued on it. A sequence's review stage queues to one desk (its dispatch_id, defaulting to the org default), so several sequences can share a desk or keep their own. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"dispatches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string"},"pending_count":{"type":"number","description":"Emails currently queued (pending review) on this desk."}},"required":["id","name","created_at","pending_count"]},"description":"The org's dispatches, oldest first — the oldest is the default desk."}},"required":["dispatches"]}},{"name":"create_dispatch","description":"Create a dispatch: a named review desk sequences can queue review-gated emails to (set it as a review stage's dispatch_id). Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"The desk's name, e.g. 'Outbound EU' or a sequence's name."}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"dispatch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","created_at"]}},"required":["dispatch"]}},{"name":"update_dispatch","description":"Rename a dispatch. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"dispatch_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["name","dispatch_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"dispatch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","created_at"]}},"required":["dispatch"]}},{"name":"delete_dispatch","description":"Delete a dispatch, and decide its queued emails: pending=move (the default) puts them on another desk (move_to, else the org's default desk); pending=send approves each one now, exactly as decide_review_item approve does. Review stages pointing at it fall back to the default. The last desk can't be deleted. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"pending":{"description":"What happens to the desk's queued emails: move (the default) puts them on another desk; send approves each one now, exactly as approving it on the desk does.","type":"string","enum":["move","send"]},"move_to":{"description":"move only: the desk that takes them (list_dispatches). Defaults to the org's default desk.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"dispatch_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["dispatch_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"pending":{"type":"string","enum":["move","send"]},"moved":{"type":"number","description":"Queued emails moved to moved_to."},"sent":{"type":"number","description":"Queued emails approved (pending=send)."},"moved_to":{"description":"The desk that took the moved emails.","type":["string","null"]}},"required":["id","pending","moved","sent","moved_to"]}},{"name":"list_review_items","description":"The Dispatch review queue: emails a sequence's review stage is holding for a decision, soonest deadline first, each with its frozen subject and body, the sequence and mailer, and the recipient's prospect context. Scope to one desk with dispatch_id. An item left undecided auto-sends at expires_at. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{"dispatch_id":{"description":"Only this dispatch's queued emails (list_dispatches); omit for the whole org.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"sequence_id":{"type":"string"},"sequence_name":{"type":"string"},"enrollment_id":{"type":"string"},"block_id":{"type":"string"},"dispatch_id":{"description":"The dispatch (review desk) holding this email.","type":["string","null"]},"mailer_address":{"type":["string","null"]},"personalized_line":{"description":"The line last written into this email with the personalize action, while the body still carries it. A further personalization should replace this line rather than add another above it.","type":["string","null"]},"recipient_email":{"type":"string"},"subject":{"type":"string"},"body":{"type":"string"},"assets":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"url":{"type":"string"},"alt":{"type":"string"},"label":{"type":"string"},"width":{"type":["number","null"]},"height":{"type":["number","null"]},"link":{"type":["string","null"]}},"required":["url","alt","label","width","height","link"]},"description":"The pictures pinned with this copy, by Prospects column key: what each {{custom:<key>}} picture token left in the body shows when it sends (a linked image in the HTML part, '<label>: <link>' in the text part). Empty when the email has none."},"queued_at":{"type":"string"},"expires_at":{"type":"string","description":"When the email auto-sends if nobody decides (the flow's review deadline)."},"prospect":{"anyOf":[{"type":"object","properties":{"name":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"title":{"type":["string","null"]},"company":{"type":["string","null"]},"website":{"type":["string","null"]},"company_domain":{"type":["string","null"]},"linkedin_url":{"type":["string","null"]},"school":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"years_at_company":{"type":["string","null"]},"notes":{"type":["string","null"]},"funding":{"type":["string","null"]},"stage":{"type":["string","null"]},"practical_stage":{"type":["string","null"]},"funding_total_usd":{"type":["number","null"]},"employees":{"type":["number","null"]},"competitors":{"type":"array","items":{"type":"string"}},"summary":{"type":["string","null"]},"lede":{"type":["string","null"]},"status":{"type":["string","null"]},"custom":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The org's custom prospect columns, verbatim (key → stored value)."}},"required":["name","first_name","last_name","title","company","website","company_domain","linkedin_url","school","city","country","years_at_company","notes","funding","stage","practical_stage","funding_total_usd","employees","competitors","summary","lede","status","custom"]},{"type":"null"}],"description":"Context on the recipient from the Prospects store, when they are in it."}},"required":["id","sequence_id","sequence_name","enrollment_id","block_id","dispatch_id","mailer_address","personalized_line","recipient_email","subject","body","assets","queued_at","expires_at","prospect"]}}},"required":["items"]}},{"name":"update_review_item","description":"Edit a queued email's subject and/or body before it is decided. What is stored here is exactly what sends. When the edit is a personalization, also pass `personalization` (the line you wrote, and whether it replaced the opener or was inserted): it is captured as an opener proposal for the org's admins to review, never added to the library directly. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"subject":{"type":"string","maxLength":200},"body":{"type":"string","maxLength":10000},"personalization":{"type":"object","properties":{"line":{"type":"string","minLength":1,"maxLength":1000},"mode":{"type":"string","enum":["replace","insert"]}},"required":["line","mode"]},"item_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["item_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"decide_review_item","description":"Decide one queued email in the review queue. approve SENDS it now from the sequence's mailer; skip advances the recipient past this email without sending; delete stops the recipient's enrollment. An item can be decided once. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["approve","skip","delete"],"description":"approve = SEND the frozen email now; skip = advance past this email without sending; delete = stop this recipient's enrollment."},"item_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["action","item_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"approved | skipped | deleted. An approved email is sent right away."}},"required":["id","status"]}},{"name":"list_dispatch_links","description":"The org's live Pocket Dispatch links (no-login review-queue links for a phone): who minted each and when it expires. Tokens are never returned. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"expires_at":{"type":"string"}},"required":["id","created_by","created_at","expires_at"]},"description":"Live (unexpired, unrevoked) Pocket Dispatch links, newest first. Tokens are never returned."}},"required":["links"]}},{"name":"create_dispatch_link","description":"Mint a Pocket Dispatch link: a no-login URL, valid 1-30 days, that shows the org's pending review items and lets whoever holds it SEND, skip or delete them. Treat it as a credential. The URL is returned once and never again; revoke it early with revoke_dispatch_link. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"days":{"anyOf":[{"type":"integer","minimum":1,"maximum":30},{"type":"string","const":"never"}]}},"required":["days"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"path":{"type":"string"},"url":{"type":"string","description":"The no-login link. Shown ONCE: it is never readable again."},"expires_at":{"type":"string"}},"required":["id","path","url","expires_at"],"description":"Anyone holding the url can read and decide (send / skip / delete) the org's pending review items until it expires or is revoked."}},{"name":"revoke_dispatch_link","description":"Revoke a Pocket Dispatch link (list_dispatch_links gives ids): its URL stops working on the next request. Idempotent. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"link_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["link_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"revoked_at":{"type":"string"}},"required":["id","revoked_at"]}},{"name":"list_prospect_lists","description":"The org's prospect lists with their prospect counts and attached Ledes Group, plus whether sourcing from a description (create_prospect_list_from_prompt) is available here. Requires capability prospect:read.","capability":"prospect:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"lists":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prospect_count":{"type":"number"},"lede_endpoint_id":{"description":"The Ledes Group whose openers this list uses.","type":["string","null"]},"lede_group_name":{"type":["string","null"]},"pulling":{"type":"boolean","description":"A create_prospect_list_from_prompt fill is working on this list right now; poll until it clears (rows land as they're found)."},"created_at":{"type":"string"}},"required":["id","name","prospect_count","lede_endpoint_id","lede_group_name","pulling","created_at"]}},"sourcing_configured":{"type":"boolean","description":"Whether create_prospect_list_from_prompt can source prospects on this environment."}},"required":["lists","sourcing_configured"]}},{"name":"get_prospect_list","description":"One prospect list with its prospect count. Read its prospects with list_prospects. Requires capability prospect:read.","capability":"prospect:read","inputSchema":{"type":"object","properties":{"list_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["list_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"list":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prospect_count":{"type":"number"},"lede_endpoint_id":{"description":"The Ledes Group whose openers this list uses.","type":["string","null"]},"lede_group_name":{"type":["string","null"]},"pulling":{"type":"boolean","description":"A create_prospect_list_from_prompt fill is working on this list right now; poll until it clears (rows land as they're found)."},"created_at":{"type":"string"}},"required":["id","name","prospect_count","lede_endpoint_id","lede_group_name","pulling","created_at"]}},"required":["list"]}},{"name":"delete_prospect_list","description":"Delete a prospect list. The prospects in it are NOT deleted: they stay in the org's store, unfiled (list_id becomes null). Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"list_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["list_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"list_id":{"type":"string"}},"required":["success","list_id"]}},{"name":"list_prospects","description":"Prospects in the org's store, newest first, optionally narrowed to a list, a status, or a search over any prospect text field (email, name, title, company, domain, location, notes, custom columns). Includes the org's custom column definitions. Requires capability prospect:read.","capability":"prospect:read","inputSchema":{"type":"object","properties":{"list_id":{"description":"Only prospects in this list.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["new","contacted","replied","unsubscribed","archived"],"description":"The worked-state flag. Sequences move new → contacted after a real send."},"q":{"description":"Search every text field of a prospect (email, name, title, company, domain, location, notes, custom columns…).","type":"string","maxLength":200},"limit":{"description":"Max rows (default 200, max 500).","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"prospects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"list_id":{"type":["string","null"]},"email":{"type":"string"},"name":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"title":{"type":["string","null"]},"company":{"type":["string","null"]},"website":{"type":["string","null"]},"company_domain":{"type":["string","null"]},"linkedin_url":{"type":["string","null"]},"school":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"years_at_company":{"type":["string","null"]},"notes":{"type":["string","null"]},"custom":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Custom column values by column key."},"is_yc":{"type":"boolean","description":"Derived: the company is in the bundled Y Combinator startup snapshot (matched by domain)."},"source":{"type":"string"},"status":{"type":"string","enum":["new","contacted","replied","unsubscribed","archived"],"description":"The worked-state flag. Sequences move new → contacted after a real send."},"contacted_at":{"type":["string","null"]},"linkedin_replied_at":{"description":"When they first replied on LinkedIn (HeyReach webhook). Set = no sequence emails them; clear with update_prospect linkedin_replied_at: null.","type":["string","null"]},"enrichment":{"type":"object","properties":{"funding":{"type":"string"},"stage":{"type":"string"},"practical_stage":{"type":"string"},"funding_total_usd":{"type":["number","null"]},"employees":{"type":["number","null"]},"competitors":{"type":"array","items":{"type":"string"}},"lede":{"description":"The printed opener ('' = the library had no fit).","type":"string"},"summary":{"type":"string"}},"description":"Best-effort enrichment; every field may be absent."},"enriched_at":{"type":["string","null"]},"enriching":{"type":"boolean","description":"An enrich_prospects run is working on this row right now; poll until it clears."},"created_at":{"type":"string"}},"required":["id","list_id","email","name","first_name","last_name","title","company","website","company_domain","linkedin_url","school","city","country","years_at_company","notes","custom","is_yc","source","status","contacted_at","linkedin_replied_at","enrichment","enriched_at","enriching","created_at"]}},"columns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"label":{"type":"string"},"position":{"type":"number"}},"required":["id","key","label","position"]},"description":"The org's custom columns (keys used in each prospect's `custom`)."}},"required":["prospects","columns"]}},{"name":"get_prospect","description":"One prospect: contact fields, custom column values, enrichment and worked-state. Requires capability prospect:read.","capability":"prospect:read","inputSchema":{"type":"object","properties":{"prospect_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["prospect_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"prospect":{"type":"object","properties":{"id":{"type":"string"},"list_id":{"type":["string","null"]},"email":{"type":"string"},"name":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"title":{"type":["string","null"]},"company":{"type":["string","null"]},"website":{"type":["string","null"]},"company_domain":{"type":["string","null"]},"linkedin_url":{"type":["string","null"]},"school":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"years_at_company":{"type":["string","null"]},"notes":{"type":["string","null"]},"custom":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Custom column values by column key."},"is_yc":{"type":"boolean","description":"Derived: the company is in the bundled Y Combinator startup snapshot (matched by domain)."},"source":{"type":"string"},"status":{"type":"string","enum":["new","contacted","replied","unsubscribed","archived"],"description":"The worked-state flag. Sequences move new → contacted after a real send."},"contacted_at":{"type":["string","null"]},"linkedin_replied_at":{"description":"When they first replied on LinkedIn (HeyReach webhook). Set = no sequence emails them; clear with update_prospect linkedin_replied_at: null.","type":["string","null"]},"enrichment":{"type":"object","properties":{"funding":{"type":"string"},"stage":{"type":"string"},"practical_stage":{"type":"string"},"funding_total_usd":{"type":["number","null"]},"employees":{"type":["number","null"]},"competitors":{"type":"array","items":{"type":"string"}},"lede":{"description":"The printed opener ('' = the library had no fit).","type":"string"},"summary":{"type":"string"}},"description":"Best-effort enrichment; every field may be absent."},"enriched_at":{"type":["string","null"]},"enriching":{"type":"boolean","description":"An enrich_prospects run is working on this row right now; poll until it clears."},"created_at":{"type":"string"}},"required":["id","list_id","email","name","first_name","last_name","title","company","website","company_domain","linkedin_url","school","city","country","years_at_company","notes","custom","is_yc","source","status","contacted_at","linkedin_replied_at","enrichment","enriched_at","enriching","created_at"]}},"required":["prospect"]}},{"name":"create_prospect_list","description":"Create an empty prospect list by name (an existing list with that name is returned instead). Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"List name. An existing list with this name is returned."}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}},{"name":"import_prospects_csv","description":"Import prospects from CSV text into a list. Headers are bridged to prospect fields server-side (unknown headers become custom columns); rows are deduped by email against the whole store, and one_per_company keeps one person per company. find_emails looks up the business email of rows that have a LinkedIn URL but no email (paid, capped by max_email_lookups) and reports found / not found / skipped plus every row still without one. Returns exact counts and the header mapping used. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"csv_text":{"type":"string","minLength":1,"maxLength":2000000,"description":"The raw CSV, header line included. Headers are bridged to prospect fields server-side; unknown headers become custom columns."},"one_per_company":{"description":"Keep at most one prospect per company.","type":"boolean"},"list_id":{"description":"Existing list the import lands in.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"list_name":{"description":"Or a list name to find-or-create.","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"find_emails":{"description":"Look up the business email of rows that have a LinkedIn URL but no email (Crustdata, then Leadsforge; paid). Rows still without one are listed in email_lookup.","type":"boolean"},"max_email_lookups":{"description":"With find_emails: at most this many paid lookups (default 25, max 100).","type":"integer","minimum":1,"maximum":100}},"required":["csv_text"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"list_id":{"type":["string","null"]},"imported":{"type":"number"},"already_known":{"type":"number","description":"The org already had this email; existing rows win."},"company_covered":{"type":"number","description":"Skipped by the one-per-company rule."},"batch_duplicates":{"type":"number"},"no_email":{"type":"number"},"mapping":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"How each CSV header was bridged."},"new_columns":{"type":"array","items":{"type":"string"}},"email_lookup":{"description":"Present with find_emails.","type":"object","properties":{"found":{"type":"number","description":"Rows with a LinkedIn URL and no email that got one."},"existing":{"type":"number","description":"Rows whose LinkedIn profile is already a Prospect: its email is reused, not looked up."},"not_found":{"type":"number","description":"Looked up; no business email exists."},"skipped":{"type":"number","description":"Not looked up: over max_email_lookups, or no provider available."},"company_covered":{"type":"number","description":"one_per_company: not looked up, the company is already covered."},"no_linkedin":{"type":"number","description":"Rows with neither an email nor a LinkedIn profile URL."},"without_email":{"type":"array","items":{"type":"object","properties":{"line":{"type":"number","description":"CSV data line (header excluded)."},"name":{"type":["string","null"]},"linkedin_url":{"type":["string","null"]},"reason":{"type":"string","enum":["not_found","skipped","company_covered","no_linkedin"]}},"required":["line","name","linkedin_url","reason"]},"description":"The rows still without an email (first 200)."}},"required":["found","existing","not_found","skipped","company_covered","no_linkedin","without_email"]}},"required":["list_id","imported","already_known","company_covered","batch_duplicates","no_email","mapping","new_columns"]}},{"name":"create_prospect_list_from_prompt","description":"Fill a prospect list from a plain-language description of who you want ('Heads of growth at seed-stage fintechs in NYC'). Creates a new list (named from the description unless list_name is given) or fills list_id. SPENDS paid data-provider credits. Runs in the background: poll list_prospects with the returned list_id as rows land. Pass enrich: true to start enrichment on the list as soon as the pull lands. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"list_id":{"description":"Fill this existing list instead of creating a new one.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"list_name":{"description":"Name for the new list. Omitted = the model names it from the description (always a NEW list).","anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"description":{"description":"Plain-language description of who you want, e.g. 'Heads of growth at seed-stage fintechs in NYC'. Companies are assumed to be in the United States unless you name another place. Omit with list_id to add more from the query the list was first filled from.","type":"string","minLength":3,"maxLength":1000},"limit":{"description":"Prospects to pull (default 25, max 100).","type":"integer","minimum":1,"maximum":100},"one_per_company":{"description":"Keep at most one prospect per company.","type":"boolean"},"enrich":{"description":"Start enrichment on the list as soon as the pull lands (skips reviewing or filtering first). Big pulls enrich in successive runs.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"list_id":{"type":"string"},"list_name":{"description":"The new list's name; null when an existing list was filled.","type":["string","null"]},"sourcing":{"type":"string","const":"started"},"message":{"type":"string","description":"What happens next, in a line a member reads"}},"required":["list_id","list_name","sourcing","message"],"description":"Sourcing runs in the background; poll list_prospects with this list_id as rows land."}},{"name":"enrich_prospects","description":"Fill Funding and Competitors for prospects not enriched yet, within a view (a list, a status, a search; up to 500 per run). The model answers from what it knows about the company (best effort, not a data provider); a blank answer is stored and never re-asked. Runs in the background: poll list_prospects and read each prospect's enrichment. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"list_id":{"description":"Enrich this list (omit for the whole store).","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"status":{"type":"string","enum":["new","contacted","replied","unsubscribed","archived"],"description":"The worked-state flag. Sequences move new → contacted after a real send."},"q":{"description":"Only prospects matching this search.","type":"string","maxLength":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"queued":{"type":"number","description":"Prospects queued for enrichment (0 = nothing un-enriched in scope)."}},"required":["queued"]}},{"name":"update_prospect","description":"Edit a prospect's fields, custom column values (merged), or worked-state status (e.g. mark 'unsubscribed'). Only the fields you pass change; pass null to clear a field. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["new","contacted","replied","unsubscribed","archived"],"description":"The worked-state flag. Sequences move new → contacted after a real send."},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"first_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"last_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"website":{"anyOf":[{"type":"string","maxLength":253},{"type":"null"}]},"linkedin_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"school":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"city":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"country":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"years_at_company":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"linkedin_replied_at":{"description":"Pass null to clear the LinkedIn-reply marker (set by the HeyReach webhook) so sequences can enroll and email them again.","type":"null"},"custom":{"description":"Custom column values by column key, merged into the prospect's existing values.","type":"object","propertyNames":{"type":"string","maxLength":40},"additionalProperties":{"type":"string","maxLength":500}},"prospect_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["prospect_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"delete_prospects","description":"Permanently delete prospects by id (up to 500 per call; find ids with list_prospects). One id removes one prospect. Ids from other orgs are skipped, and `deleted` reports how many actually went. Active sequence enrollments for those addresses are not touched. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"prospect_ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["prospect_ids"],"description":"Delete prospects by id, up to 500 per call. Ids from other orgs are skipped.","additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"number","description":"Prospects actually removed (ids from other orgs or already gone are skipped)."}},"required":["deleted"]}},{"name":"create_prospect_column","description":"Add a custom column to the org's prospects (shared by every list, capped per org). Returns its id and the key its values are stored under: fill it per prospect with update_prospect `custom`, or read it into a sequence or cake step as `custom:<key>`. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":60,"description":"The column's header. Its key (for custom values) derives from it."}},"required":["label"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"The key custom values are stored under (update_prospect `custom`)."}},"required":["id","key"]}},{"name":"delete_prospect_column","description":"Drop a custom column definition (list_prospects returns the org's columns with their ids). The values already stored on prospects are left in place but no longer shown. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"column_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A custom column's id (list_prospects returns the org's columns)."}},"required":["column_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"connect_app","description":"Your apps: sign a browser in to one of the org's own apps so enclosures can take pictures in it, or sign in to one again. Only enclosures use it (not Build, Fix, Visitors or Letterprove). Returns a hosted sign-in page a PERSON must open (it expires after about 10 minutes idle); no password ever passes through this tool. Use a demo account: enclosures see everything it can see. Calling it again for the same app starts a fresh sign-in. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"login_url":{"type":"string","maxLength":2048,"format":"uri","description":"The app's sign-in page, e.g. https://app.example.com/login"},"name":{"description":"What to call it; defaults to the app's address","type":"string","minLength":1,"maxLength":80},"remember_sign_in":{"description":"Sign back in on its own when the session ends (the browser provider keeps what was typed; codes sent by text or email still need a person). Default true","type":"boolean"}},"required":["login_url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"login_url":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["connecting","connected","needs_login","error"],"description":"connected = enclosures can use it; needs_login = someone has to sign in again"},"remember_sign_in":{"type":"boolean","description":"Signs back in on its own when a session ends"},"static_ip":{"description":"The address requests come from, for the app's allowlist","type":["string","null"]},"last_checked_at":{"type":["string","null"]},"last_auth_at":{"type":["string","null"]},"last_error":{"description":"What a person should do next, in a sentence","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","login_url","domain","status","remember_sign_in","static_ip","last_checked_at","last_auth_at","last_error","created_at","updated_at"]},"hosted_url":{"type":"string","description":"The sign-in page a PERSON opens to sign in. A secret link: hand it to them, never post it publicly"},"expires_at":{"description":"When the sign-in page stops working (about 10 minutes idle)","type":["string","null"]}},"required":["connection","hosted_url","expires_at"]}},{"name":"get_app_connection","description":"Your apps: one connected app, with its sign-in state read fresh from the browser provider (connected, needs_login and why, the static IP to allowlist). Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{"connection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["connection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"login_url":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["connecting","connected","needs_login","error"],"description":"connected = enclosures can use it; needs_login = someone has to sign in again"},"remember_sign_in":{"type":"boolean","description":"Signs back in on its own when a session ends"},"static_ip":{"description":"The address requests come from, for the app's allowlist","type":["string","null"]},"last_checked_at":{"type":["string","null"]},"last_auth_at":{"type":["string","null"]},"last_error":{"description":"What a person should do next, in a sentence","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","login_url","domain","status","remember_sign_in","static_ip","last_checked_at","last_auth_at","last_error","created_at","updated_at"]}},"required":["connection"]}},{"name":"list_app_connections","description":"Your apps: the org's connected apps, each with its sign-in state read fresh. Watch for needs_login: enclosures that use that app pause until someone signs in again (connect_app). Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"login_url":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["connecting","connected","needs_login","error"],"description":"connected = enclosures can use it; needs_login = someone has to sign in again"},"remember_sign_in":{"type":"boolean","description":"Signs back in on its own when a session ends"},"static_ip":{"description":"The address requests come from, for the app's allowlist","type":["string","null"]},"last_checked_at":{"type":["string","null"]},"last_auth_at":{"type":["string","null"]},"last_error":{"description":"What a person should do next, in a sentence","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","login_url","domain","status","remember_sign_in","static_ip","last_checked_at","last_auth_at","last_error","created_at","updated_at"]}}},"required":["connections"]}},{"name":"disconnect_app","description":"Your apps: remove a connected app. Its sign-in, browser profile and static IP are deleted at the provider. Enclosures that use it stop printing; prints already made stay. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"connection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["connection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}},{"name":"create_generator","description":"Create an enclosure and the Prospects columns it fills (one per output). Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. It starts as a draft: nothing is made or spent until it is tested and a version is chosen. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"What people call it ('Site audit')"},"instructions":{"type":"string","maxLength":4000,"description":"What each person gets, in plain words, with tokens: 'Run an SEO audit of {{website}} in Letter …'"},"inputs":{"maxItems":20,"type":"array","items":{"type":"string","description":"A prospect field the generator reads: website, company, first_name, … or custom:<column key>"}},"outputs":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The prospect column it fills; emails read it as {{custom:<key>}}. Must not clash with an existing column"},"label":{"type":"string","minLength":1,"maxLength":60,"description":"The column's name in Prospects and the editor ('Site audit')"},"shape":{"type":"string","enum":["text","image","animation","video"],"description":"text = a sentence; image = a still; animation = a GIF; video = a clip"},"alt":{"description":"Alt text for a picture, with tokens: 'Screenshot of the audit for {{company}}'","type":"string","maxLength":200}},"required":["key","label","shape"]}},"needs":{"type":"object","properties":{"browser":{"description":"Present when it needs a browser at all","type":"object","properties":{"login":{"description":"An app connection to sign in with (Your apps)","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"stealth":{"description":"Get past bot walls on prospect sites (never with a login)","type":"boolean"},"record":{"description":"Record part of the run for a moving picture or clip","type":"boolean"},"region":{"type":"string","enum":["us-east","eu-west"]}}},"image_model":{"description":"It edits or makes images (a sign photo)","type":"boolean"},"tools":{"description":"Letter operations it may call","maxItems":10,"type":"array","items":{"type":"string","enum":["run_site_audit"]}}}},"cache_scope":{"type":"string","enum":["prospect","company_domain"],"description":"company_domain = one print shared by everyone at the same company"},"freshness_days":{"type":"integer","minimum":1,"maximum":365,"description":"Make a new one after this many days"},"budget_per_person_usd":{"type":"number","minimum":0,"maximum":5,"description":"Most one person's print may cost"},"no_fit_rule":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"description":"When to skip someone, in a sentence: 'fewer than 5 issues are found'"},"disclosure":{"type":"boolean","description":"Mark edited images 'AI-edited' (on by default)"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":40,"description":"The button ('Book a time')"},"url":{"type":"string","maxLength":500,"description":"Where it goes: a booking link, or mailto:"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture; null = none"},"key":{"description":"Defaults to one made from the name","type":"string","pattern":"^[a-z][a-z0-9_]{0,39}$"},"starter":{"description":"Begin from a starter (list_generator_starters): its settings and its version 1. Fields you send override the starter's","type":"string","maxLength":40}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generator":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"},"description":"Prospect fields it reads"},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The prospect column it fills; emails read it as {{custom:<key>}}"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"browser / image_model / tools it needs"},"cache_scope":{"type":"string","enum":["prospect","company_domain"]},"freshness_days":{"type":"number"},"budget_per_person_usd":{"type":"number"},"no_fit_rule":{"type":["string","null"]},"disclosure":{"type":"boolean"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture"},"status":{"type":"string","enum":["draft","trying","approved","paused"]},"current_version":{"description":"The approved version prints run; null = none yet","type":["number","null"]},"approved_by":{"type":["string","null"]},"approved_at":{"type":["string","null"]},"paused_reason":{"type":["string","null"]},"source":{"type":"string","enum":["user","api_key","agent"],"description":"Who made it"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"counts":{"type":"object","properties":{"queued":{"type":"number"},"running":{"type":"number"},"done":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"}},"required":["queued","running","done","no_fit","failed","skipped"],"description":"Current prints per state"}},"required":["id","key","name","instructions","inputs","outputs","needs","cache_scope","freshness_days","budget_per_person_usd","no_fit_rule","disclosure","cta","status","current_version","approved_by","approved_at","paused_reason","source","created_by","created_at","updated_at","counts"]}},"required":["generator"]}},{"name":"update_generator","description":"Edit an enclosure. Omitted fields are unchanged; inputs and outputs are replaced whole when sent. Outputs (its columns) can change only until a version is in use. Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"What people call it ('Site audit')"},"instructions":{"type":"string","maxLength":4000,"description":"What each person gets, in plain words, with tokens: 'Run an SEO audit of {{website}} in Letter …'"},"inputs":{"maxItems":20,"type":"array","items":{"type":"string","description":"A prospect field the generator reads: website, company, first_name, … or custom:<column key>"}},"outputs":{"minItems":1,"maxItems":5,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The prospect column it fills; emails read it as {{custom:<key>}}. Must not clash with an existing column"},"label":{"type":"string","minLength":1,"maxLength":60,"description":"The column's name in Prospects and the editor ('Site audit')"},"shape":{"type":"string","enum":["text","image","animation","video"],"description":"text = a sentence; image = a still; animation = a GIF; video = a clip"},"alt":{"description":"Alt text for a picture, with tokens: 'Screenshot of the audit for {{company}}'","type":"string","maxLength":200}},"required":["key","label","shape"]}},"needs":{"type":"object","properties":{"browser":{"description":"Present when it needs a browser at all","type":"object","properties":{"login":{"description":"An app connection to sign in with (Your apps)","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"stealth":{"description":"Get past bot walls on prospect sites (never with a login)","type":"boolean"},"record":{"description":"Record part of the run for a moving picture or clip","type":"boolean"},"region":{"type":"string","enum":["us-east","eu-west"]}}},"image_model":{"description":"It edits or makes images (a sign photo)","type":"boolean"},"tools":{"description":"Letter operations it may call","maxItems":10,"type":"array","items":{"type":"string","enum":["run_site_audit"]}}}},"cache_scope":{"type":"string","enum":["prospect","company_domain"],"description":"company_domain = one print shared by everyone at the same company"},"freshness_days":{"type":"integer","minimum":1,"maximum":365,"description":"Make a new one after this many days"},"budget_per_person_usd":{"type":"number","minimum":0,"maximum":5,"description":"Most one person's print may cost"},"no_fit_rule":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"description":"When to skip someone, in a sentence: 'fewer than 5 issues are found'"},"disclosure":{"type":"boolean","description":"Mark edited images 'AI-edited' (on by default)"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":40,"description":"The button ('Book a time')"},"url":{"type":"string","maxLength":500,"description":"Where it goes: a booking link, or mailto:"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture; null = none"},"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generator":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"},"description":"Prospect fields it reads"},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The prospect column it fills; emails read it as {{custom:<key>}}"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"browser / image_model / tools it needs"},"cache_scope":{"type":"string","enum":["prospect","company_domain"]},"freshness_days":{"type":"number"},"budget_per_person_usd":{"type":"number"},"no_fit_rule":{"type":["string","null"]},"disclosure":{"type":"boolean"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture"},"status":{"type":"string","enum":["draft","trying","approved","paused"]},"current_version":{"description":"The approved version prints run; null = none yet","type":["number","null"]},"approved_by":{"type":["string","null"]},"approved_at":{"type":["string","null"]},"paused_reason":{"type":["string","null"]},"source":{"type":"string","enum":["user","api_key","agent"],"description":"Who made it"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"counts":{"type":"object","properties":{"queued":{"type":"number"},"running":{"type":"number"},"done":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"}},"required":["queued","running","done","no_fit","failed","skipped"],"description":"Current prints per state"}},"required":["id","key","name","instructions","inputs","outputs","needs","cache_scope","freshness_days","budget_per_person_usd","no_fit_rule","disclosure","cta","status","current_version","approved_by","approved_at","paused_reason","source","created_by","created_at","updated_at","counts"]}},"required":["generator"]}},{"name":"get_generator","description":"Read one enclosure: its settings, how many prints are in each state, and its versions (newest first, with step counts and the hosts each visited). Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generator":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"},"description":"Prospect fields it reads"},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The prospect column it fills; emails read it as {{custom:<key>}}"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"browser / image_model / tools it needs"},"cache_scope":{"type":"string","enum":["prospect","company_domain"]},"freshness_days":{"type":"number"},"budget_per_person_usd":{"type":"number"},"no_fit_rule":{"type":["string","null"]},"disclosure":{"type":"boolean"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture"},"status":{"type":"string","enum":["draft","trying","approved","paused"]},"current_version":{"description":"The approved version prints run; null = none yet","type":["number","null"]},"approved_by":{"type":["string","null"]},"approved_at":{"type":["string","null"]},"paused_reason":{"type":["string","null"]},"source":{"type":"string","enum":["user","api_key","agent"],"description":"Who made it"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"counts":{"type":"object","properties":{"queued":{"type":"number"},"running":{"type":"number"},"done":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"}},"required":["queued","running","done","no_fit","failed","skipped"],"description":"Current prints per state"},"versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number"},"author":{"type":"string","enum":["agent","user"]},"change_summary":{"type":["string","null"]},"steps":{"type":"number","description":"How many plain-language steps it has"},"allowed_hosts":{"type":"array","items":{"type":"string"},"description":"Hosts its authoring run loaded"},"created_at":{"type":"string"}},"required":["version","author","change_summary","steps","allowed_hosts","created_at"]}},"authoring":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"],"description":"provisioning: starting the sandbox and browser; running: Claude Code is working; collecting: saving"},"prospect_id":{"description":"The person whose page it works on while writing","type":["string","null"]},"model":{"type":"string"},"budget_usd":{"type":"number"},"cost_usd":{"type":"number"},"version":{"description":"The version it wrote, once done","type":["number","null"]},"summary":{"description":"What it did, in its own words","type":["string","null"]},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"live_view_url":{"description":"Watch its browser while it runs; null once it ends","type":["string","null"]},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["id","status","phase","prospect_id","model","budget_usd","cost_usd","version","summary","error_code","error","live_view_url","started_at","completed_at"]},{"type":"null"}],"description":"Its newest \"Write the steps\" run (write_generator_steps), or null"},"failures":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","description":"site_down | signed_out | app_changed | ours | no_website | redirects_away | no_fit"},"label":{"type":"string","description":"In words: 'Their site was down'"},"count":{"type":"number"},"fix":{"anyOf":[{"type":"string","enum":["retry","sign_in"]},{"type":"null"}],"description":"retry = print_generator these prospect_ids again; sign_in = connect_app again; null = nothing to fix"},"kind":{"type":"string","enum":["failed","no_fit"]},"prospect_ids":{"type":"array","items":{"type":"string"},"description":"The people in it (at most 1000)"}},"required":["reason","label","count","fix","kind","prospect_ids"]},"description":"Why current prints have no picture, grouped by reason with the fix, biggest first"}},"required":["id","key","name","instructions","inputs","outputs","needs","cache_scope","freshness_days","budget_per_person_usd","no_fit_rule","disclosure","cta","status","current_version","approved_by","approved_at","paused_reason","source","created_by","created_at","updated_at","counts","versions","authoring","failures"]}},"required":["generator"]}},{"name":"list_generators","description":"List the org's enclosures, oldest first, each with how many prints are in each state. Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generators":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"},"description":"Prospect fields it reads"},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The prospect column it fills; emails read it as {{custom:<key>}}"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"browser / image_model / tools it needs"},"cache_scope":{"type":"string","enum":["prospect","company_domain"]},"freshness_days":{"type":"number"},"budget_per_person_usd":{"type":"number"},"no_fit_rule":{"type":["string","null"]},"disclosure":{"type":"boolean"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture"},"status":{"type":"string","enum":["draft","trying","approved","paused"]},"current_version":{"description":"The approved version prints run; null = none yet","type":["number","null"]},"approved_by":{"type":["string","null"]},"approved_at":{"type":["string","null"]},"paused_reason":{"type":["string","null"]},"source":{"type":"string","enum":["user","api_key","agent"],"description":"Who made it"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"counts":{"type":"object","properties":{"queued":{"type":"number"},"running":{"type":"number"},"done":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"}},"required":["queued","running","done","no_fit","failed","skipped"],"description":"Current prints per state"}},"required":["id","key","name","instructions","inputs","outputs","needs","cache_scope","freshness_days","budget_per_person_usd","no_fit_rule","disclosure","cta","status","current_version","approved_by","approved_at","paused_reason","source","created_by","created_at","updated_at","counts"]}}},"required":["generators"]}},{"name":"list_generator_starters","description":"Starters an enclosure can begin from, each with its settings and a version 1 written in plain steps. Pass a starter's key as create_generator's `starter` (and needs.browser.login if it signs in to an app). Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"starters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Pass as create_generator's `starter`"},"name":{"type":"string"},"blurb":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"}},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Add browser.login (an app connection) when it signs in"},"no_fit_rule":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"object","properties":{"n":{"type":"number"},"text":{"type":"string"}},"required":["n","text"]},"description":"Its version 1, in plain words"}},"required":["key","name","blurb","instructions","inputs","outputs","needs","no_fit_rule","steps"]}}},"required":["starters"]}},{"name":"get_generator_version","description":"Read one version of an enclosure in full: its plain-language steps (with what the authoring run saw at each), the script every print replays with no LLM, the hosts it visits and the region a still is cut from. Enclosures (outbound; tool names say generator; the Enclosures page) make one personalised output per prospect: a still, a GIF, a clip or a sentence, taken in a browser or made by an image model. A print is one prospect's run, nothing physical (printed gifts are Daymaker). Each output IS a Prospects column, and sequence emails read it as {{custom:<key>}}. Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"version":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"The version to read (get_generator lists them)"}},"required":["generator_id","version"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"version":{"type":"object","properties":{"version":{"type":"number"},"author":{"type":"string","enum":["agent","user"]},"parent_version":{"description":"The version it was written from","type":["number","null"]},"change_summary":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"object","properties":{"n":{"type":"number"},"text":{"type":"string"},"screenshot_url":{"description":"What the authoring run saw at this step, if kept","type":["string","null"]}},"required":["n","text","screenshot_url"]},"description":"Plain-language steps, in order"},"script":{"type":"string","description":"What every print replays, with no LLM"},"allowed_hosts":{"type":"array","items":{"type":"string"}},"region":{"anyOf":[{"type":"object","properties":{"selectors":{"type":"array","items":{"type":"string"}}},"required":["selectors"]},{"type":"null"}],"description":"Where a still is cut from"},"photo_url":{"description":"A sign photo's photo, signed for an hour; null for other kinds","type":["string","null"]},"current":{"type":"boolean","description":"Prints run this version now"},"created_at":{"type":"string"}},"required":["version","author","parent_version","change_summary","steps","script","allowed_hosts","region","photo_url","current","created_at"]}},"required":["version"]}},{"name":"write_generator_steps","description":"Have Claude Code write an enclosure's steps from its description (instructions): once, in a sandbox driving a browser signed in to your app, on one person's page. It writes the per-person script, plain-language steps with a screenshot each, and proves the script on that person; that becomes a new version (prints keep running the chosen one until approve_generator_version). Takes a few minutes; billed as an agent run, budget up to $5, less if the spend cap leaves less (run.budget_usd), refused when the account can't pay. Watch it with get_generator (authoring). (then append ENCLOSURES_NOTE as today) Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"prospect_id":{"description":"Whose page it works on while writing; defaults to the org's first person with a website","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"]},"budget_usd":{"type":"number"},"started_at":{"type":"string"}},"required":["id","status","phase","budget_usd","started_at"]}},"required":["run"]}},{"name":"set_generator_photo","description":"Give a sign photo its photo (photo_url, https): you holding a blank sheet of paper or a sign, facing the camera. Each print writes on the sheet for that person with an image model. Makes a new version with the same edit; prints keep running the chosen version until approve_generator_version. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"photo_url":{"type":"string","maxLength":2048,"format":"uri","description":"Your photo for a sign photo: you holding a blank sheet of paper or a sign, facing the camera. JPEG or PNG"}},"required":["generator_id","photo_url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"version":{"type":"number","description":"The new version with this photo. Prints don't run it until it's approved"}},"required":["version"]}},{"name":"add_generator_version","description":"Add a version of an enclosure: the per-person script (Playwright run in the browser provider's VM with `page` and `params` in scope), its plain-language steps, the hosts it visits and the page region a still is cut from. Prints keep running the approved version until approve_generator_version. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"script":{"type":"string","maxLength":60000,"description":"The per-person script: Playwright code run in the browser provider's VM with `page` and `params` in scope. Returns { images, text, checks, no_fit?, signed_out? }"},"steps":{"maxItems":40,"type":"array","items":{"type":"object","properties":{"n":{"type":"integer","minimum":1,"maximum":9007199254740991},"text":{"type":"string","minLength":1,"maxLength":200}},"required":["n","text"]},"description":"Plain-language steps, one per action: 'Type acme.com into Site URL'"},"allowed_hosts":{"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"region":{"description":"The part of the page a still is cut from, by page structure (never pixels)","type":"object","properties":{"selectors":{"maxItems":10,"type":"array","items":{"type":"string","maxLength":200}}},"required":["selectors"]},"params_schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"record_span":{"type":"object","properties":{"start_step":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"end_step":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["start_step","end_step"]},"change_summary":{"type":"string","maxLength":300}},"required":["generator_id","script","steps"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"version":{"type":"number","description":"The new version's number. Prints don't run it until it's approved"}},"required":["version"]}},{"name":"approve_generator_version","description":"Choose the version an enclosure's prints run, after testing it on a few people (print_generator before a version is chosen tests at most 3). Records who chose it and when; un-pauses it. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"version":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"The version prints will run"}},"required":["generator_id","version"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generator":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"instructions":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"},"description":"Prospect fields it reads"},"outputs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The prospect column it fills; emails read it as {{custom:<key>}}"},"label":{"type":"string"},"shape":{"type":"string","enum":["text","image","animation","video"]},"alt":{"type":"string"}},"required":["key","label","shape"]}},"needs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"browser / image_model / tools it needs"},"cache_scope":{"type":"string","enum":["prospect","company_domain"]},"freshness_days":{"type":"number"},"budget_per_person_usd":{"type":"number"},"no_fit_rule":{"type":["string","null"]},"disclosure":{"type":"boolean"},"cta":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label","url"]},{"type":"null"}],"description":"The call to action on the page a prospect lands on from the picture"},"status":{"type":"string","enum":["draft","trying","approved","paused"]},"current_version":{"description":"The approved version prints run; null = none yet","type":["number","null"]},"approved_by":{"type":["string","null"]},"approved_at":{"type":["string","null"]},"paused_reason":{"type":["string","null"]},"source":{"type":"string","enum":["user","api_key","agent"],"description":"Who made it"},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"counts":{"type":"object","properties":{"queued":{"type":"number"},"running":{"type":"number"},"done":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"}},"required":["queued","running","done","no_fit","failed","skipped"],"description":"Current prints per state"}},"required":["id","key","name","instructions","inputs","outputs","needs","cache_scope","freshness_days","budget_per_person_usd","no_fit_rule","disclosure","cta","status","current_version","approved_by","approved_at","paused_reason","source","created_by","created_at","updated_at","counts"]}},"required":["generator"]}},{"name":"delete_generator","description":"Delete an enclosure: it leaves every list, never prints again, and its queued prints are skipped. Its prints, their pictures and their prospect pages stay (sent emails and copies on the review desk show them), and its Prospects columns stay as plain columns. Refused while a sequence that isn't archived reads its picture. Records who deleted it and when. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"columns_kept":{"type":"array","items":{"type":"string"},"description":"Its Prospects columns, kept as plain columns with their values"}},"required":["id","name","columns_kept"]}},"required":["deleted"]}},{"name":"print_generator","description":"Print an enclosure for people (prospect_ids or a list_id): make each one's outputs and write them into their Prospects columns. Billed per finished print in tokens (cost plus markup); refused (payment_required) if the whole run doesn't fit the account's tokens or cap. Until a version is chosen, or when `version` isn't the chosen one, it's a test: more than 3 people to print is refused, not trimmed. Skips people with a recent print of the same version and inputs, unsubscribed or archived people, and (when it reads website) people with no website. Refused while paused. dry_run says who, what it costs and how long, and spends nothing. Watch progress with list_generations. Requires capability generator:write.","capability":"generator:write","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"prospect_ids":{"description":"These people","minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"list_id":{"description":"Everyone on this Prospects list","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"version":{"description":"Run this version. Before one is chosen, a print is a test of at most 3 people","type":"integer","minimum":1,"maximum":9007199254740991},"redo":{"description":"Make a new one even for people with a recent print","type":"boolean"},"dry_run":{"description":"Only say who would be printed, what it costs and how long it takes","type":"boolean"}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"version":{"type":"number","description":"The version these prints run"},"will_print":{"type":"number"},"recent":{"type":"number","description":"Skipped: a recent print of this version with the same inputs"},"no_website":{"type":"number","description":"Skipped: nothing to work on"},"never_emailed":{"type":"number","description":"Skipped: unsubscribed or archived"},"estimate":{"type":"object","properties":{"usd":{"type":"number"},"minutes":{"type":"number"}},"required":["usd","minutes"]},"queued":{"type":"number","description":"0 on a dry run"},"dry_run":{"type":"boolean"}},"required":["version","will_print","recent","no_website","never_emailed","estimate","queued","dry_run"]}},{"name":"list_generations","description":"List an enclosure's current prints, newest first: who each is for, its state, outputs, checks, cost and any error. Filter by status to find what failed or was a no-fit. Requires capability generator:read.","capability":"generator:read","inputSchema":{"type":"object","properties":{"generator_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"description":"Only prints in this state","type":"string","enum":["queued","running","done","no_fit","failed","skipped"]},"limit":{"description":"Most rows to return (default 50)","type":"integer","minimum":1,"maximum":200}},"required":["generator_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prospect_id":{"type":"string"},"prospect":{"anyOf":[{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]},"company":{"type":["string","null"]}},"required":["email","name","company"]},{"type":"null"}]},"version":{"type":"number"},"status":{"type":"string","enum":["queued","running","done","no_fit","failed","skipped"]},"outputs":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Text outputs by key; pictures carry their URL"},"qa":{"type":"array","items":{},"description":"Checks, deterministic first"},"cost_usd":{"type":"number"},"browser_seconds":{"type":"number"},"attempts":{"type":"number"},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"page":{"anyOf":[{"type":"object","properties":{"url":{"type":"string","description":"The page the prospect lands on from the picture (/v/<token>)"},"views":{"type":"number","description":"Times it was really seen: on screen for a few seconds or touched, never on load"},"first_viewed_at":{"type":["string","null"]},"last_viewed_at":{"type":["string","null"]}},"required":["url","views","first_viewed_at","last_viewed_at"]},{"type":"null"}],"description":"Null when the print has no picture"}},"required":["id","prospect_id","prospect","version","status","outputs","qa","cost_usd","browser_seconds","attempts","error_code","error","created_at","updated_at","page"]}}},"required":["generations"]}},{"name":"build_campaign","description":"Build a whole cold-email campaign from one prompt (e.g. \"Cold-email 25 SaaS founders with an audit of their own site\"). Claude Code, in a sandbox, works through Letter's own tools the way you would: it makes the prospect list, picks or writes the enclosure (the picture made for each person), tests it on 3 and chooses it, writes the sequence email that carries {{custom:<picture>}}, then launches it behind a review stage and enrolls the list, so each person's copy lands on the review desk with their picture. Nothing sends until a person approves a copy. It reaches at most people_limit people (default 25, at most 50), spends prospect credits for the list and up to $5 of model use, and takes about 10 to 20 minutes. One build at a time per org. Watch it with get_campaign_build (stage, artifacts, activity). Requires capability campaign:build.","capability":"campaign:build","inputSchema":{"type":"object","properties":{"prompt":{"type":"string","minLength":3,"maxLength":2000,"description":"The campaign, in plain words: who to write to and what to show them, e.g. \"Cold-email 25 SaaS founders with an audit of their own site\"."},"people_limit":{"description":"The most people the build may put on its list, print and enroll (default 25, at most 50).","type":"integer","minimum":1,"maximum":50}},"required":["prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string"},"people_limit":{"type":"number"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"]},"stage":{"type":"string","enum":["who","picture","test","email","desk"],"description":"Where it is: Who (the list), The picture (the enclosure), Tested on 3, The email, On the desk"},"model":{"type":"string"},"budget_usd":{"type":"number"},"cost_usd":{"type":"number"},"launched_at":{"description":"When it launched the sequence into review and enrolled the list","type":["string","null"]},"summary":{"description":"What it built, in its own words","type":["string","null"]},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"artifacts":{"type":"object","properties":{"list":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"people":{"type":"number"},"pulling":{"type":"boolean"}},"required":["id","name","people","pulling"]},{"type":"null"}]},"enclosure":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"current_version":{"type":["number","null"]},"prints":{"type":"object","properties":{"done":{"type":"number"},"making":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"}},"required":["done","making","no_fit","failed"]},"picture_key":{"type":["string","null"]}},"required":["id","name","status","current_version","prints","picture_key"]},{"type":"null"}]},"sequence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}},"required":["id","name","status"]},{"type":"null"}]},"desk":{"anyOf":[{"type":"object","properties":{"dispatch_id":{"type":["string","null"]},"waiting":{"type":"number","description":"Review copies waiting for a person"},"with_picture":{"type":"number","description":"Of those, how many carry the person's picture"},"on_the_way":{"type":"number","description":"People still enrolled whose copy isn't on the desk yet (a person stopped, e.g. their picture a no-fit, is not counted)"}},"required":["dispatch_id","waiting","with_picture","on_the_way"]},{"type":"null"}]}},"required":["list","enclosure","sequence","desk"]},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["progress","stage","say","think","tool","user","error"]},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"Its words and every Letter call it makes, oldest first"}},"required":["id","prompt","people_limit","status","phase","stage","model","budget_usd","cost_usd","launched_at","summary","error_code","error","started_at","completed_at","artifacts","activity"]},"message":{"type":"string","description":"What the build does next and where its copies wait, in a line a member reads"}},"required":["build","message"]}},{"name":"get_campaign_build","description":"Read one campaign build (build_campaign): its stage (who → picture → test → email → desk), what it has built so far (the list and how many people, the enclosure and its prints, the sequence, the review copies waiting and how many carry their picture), its spend, and its activity: what it says and every Letter call it makes, live while it runs. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{"build_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The build (build_campaign returns it; list_campaign_builds lists them)."}},"required":["build_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string"},"people_limit":{"type":"number"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"]},"stage":{"type":"string","enum":["who","picture","test","email","desk"],"description":"Where it is: Who (the list), The picture (the enclosure), Tested on 3, The email, On the desk"},"model":{"type":"string"},"budget_usd":{"type":"number"},"cost_usd":{"type":"number"},"launched_at":{"description":"When it launched the sequence into review and enrolled the list","type":["string","null"]},"summary":{"description":"What it built, in its own words","type":["string","null"]},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"artifacts":{"type":"object","properties":{"list":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"people":{"type":"number"},"pulling":{"type":"boolean"}},"required":["id","name","people","pulling"]},{"type":"null"}]},"enclosure":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"current_version":{"type":["number","null"]},"prints":{"type":"object","properties":{"done":{"type":"number"},"making":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"}},"required":["done","making","no_fit","failed"]},"picture_key":{"type":["string","null"]}},"required":["id","name","status","current_version","prints","picture_key"]},{"type":"null"}]},"sequence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}},"required":["id","name","status"]},{"type":"null"}]},"desk":{"anyOf":[{"type":"object","properties":{"dispatch_id":{"type":["string","null"]},"waiting":{"type":"number","description":"Review copies waiting for a person"},"with_picture":{"type":"number","description":"Of those, how many carry the person's picture"},"on_the_way":{"type":"number","description":"People still enrolled whose copy isn't on the desk yet (a person stopped, e.g. their picture a no-fit, is not counted)"}},"required":["dispatch_id","waiting","with_picture","on_the_way"]},{"type":"null"}]}},"required":["list","enclosure","sequence","desk"]},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["progress","stage","say","think","tool","user","error"]},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"Its words and every Letter call it makes, oldest first"}},"required":["id","prompt","people_limit","status","phase","stage","model","budget_usd","cost_usd","launched_at","summary","error_code","error","started_at","completed_at","artifacts","activity"]}},"required":["build"]}},{"name":"list_campaign_builds","description":"List this org's campaign builds, newest first, each with its stage and what it built (without the activity: get_campaign_build has it). Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{"limit":{"description":"Newest first; default 20.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"builds":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string"},"people_limit":{"type":"number"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"]},"stage":{"type":"string","enum":["who","picture","test","email","desk"],"description":"Where it is: Who (the list), The picture (the enclosure), Tested on 3, The email, On the desk"},"model":{"type":"string"},"budget_usd":{"type":"number"},"cost_usd":{"type":"number"},"launched_at":{"description":"When it launched the sequence into review and enrolled the list","type":["string","null"]},"summary":{"description":"What it built, in its own words","type":["string","null"]},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"artifacts":{"type":"object","properties":{"list":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"people":{"type":"number"},"pulling":{"type":"boolean"}},"required":["id","name","people","pulling"]},{"type":"null"}]},"enclosure":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"current_version":{"type":["number","null"]},"prints":{"type":"object","properties":{"done":{"type":"number"},"making":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"}},"required":["done","making","no_fit","failed"]},"picture_key":{"type":["string","null"]}},"required":["id","name","status","current_version","prints","picture_key"]},{"type":"null"}]},"sequence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}},"required":["id","name","status"]},{"type":"null"}]},"desk":{"anyOf":[{"type":"object","properties":{"dispatch_id":{"type":["string","null"]},"waiting":{"type":"number","description":"Review copies waiting for a person"},"with_picture":{"type":"number","description":"Of those, how many carry the person's picture"},"on_the_way":{"type":"number","description":"People still enrolled whose copy isn't on the desk yet (a person stopped, e.g. their picture a no-fit, is not counted)"}},"required":["dispatch_id","waiting","with_picture","on_the_way"]},{"type":"null"}]}},"required":["list","enclosure","sequence","desk"]},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["progress","stage","say","think","tool","user","error"]},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"Its words and every Letter call it makes, oldest first"}},"required":["id","prompt","people_limit","status","phase","stage","model","budget_usd","cost_usd","launched_at","summary","error_code","error","started_at","completed_at","artifacts","activity"]}}},"required":["builds"]}},{"name":"cancel_campaign_build","description":"Stop a running campaign build. Its sandbox and its key end at once; what it built so far (the list, the enclosure, the sequence, any copies on the desk) is kept. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"build_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The build (build_campaign returns it; list_campaign_builds lists them)."}},"required":["build_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string"},"people_limit":{"type":"number"},"status":{"type":"string","enum":["running","done","failed","cancelled"]},"phase":{"type":"string","enum":["provisioning","running","collecting","done"]},"stage":{"type":"string","enum":["who","picture","test","email","desk"],"description":"Where it is: Who (the list), The picture (the enclosure), Tested on 3, The email, On the desk"},"model":{"type":"string"},"budget_usd":{"type":"number"},"cost_usd":{"type":"number"},"launched_at":{"description":"When it launched the sequence into review and enrolled the list","type":["string","null"]},"summary":{"description":"What it built, in its own words","type":["string","null"]},"error_code":{"type":["string","null"]},"error":{"type":["string","null"]},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"artifacts":{"type":"object","properties":{"list":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"people":{"type":"number"},"pulling":{"type":"boolean"}},"required":["id","name","people","pulling"]},{"type":"null"}]},"enclosure":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"current_version":{"type":["number","null"]},"prints":{"type":"object","properties":{"done":{"type":"number"},"making":{"type":"number"},"no_fit":{"type":"number"},"failed":{"type":"number"}},"required":["done","making","no_fit","failed"]},"picture_key":{"type":["string","null"]}},"required":["id","name","status","current_version","prints","picture_key"]},{"type":"null"}]},"sequence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}},"required":["id","name","status"]},{"type":"null"}]},"desk":{"anyOf":[{"type":"object","properties":{"dispatch_id":{"type":["string","null"]},"waiting":{"type":"number","description":"Review copies waiting for a person"},"with_picture":{"type":"number","description":"Of those, how many carry the person's picture"},"on_the_way":{"type":"number","description":"People still enrolled whose copy isn't on the desk yet (a person stopped, e.g. their picture a no-fit, is not counted)"}},"required":["dispatch_id","waiting","with_picture","on_the_way"]},{"type":"null"}]}},"required":["list","enclosure","sequence","desk"]},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["progress","stage","say","think","tool","user","error"]},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"Its words and every Letter call it makes, oldest first"}},"required":["id","prompt","people_limit","status","phase","stage","model","budget_usd","cost_usd","launched_at","summary","error_code","error","started_at","completed_at","artifacts","activity"]}},"required":["build"]}},{"name":"remove_campaign_build","description":"Remove a campaign build and what it made: its sequence is archived and every enrollment stopped, so its unsent copies leave the desk (copies already sent stay sent); its review desk is deleted; its list is deleted only if the build pulled it (the people stay in Prospects) and its enclosure paused only if the build made it. A list or enclosure it was told to use is left alone. A running build is stopped first. Requires capability sequence:write.","capability":"sequence:write","inputSchema":{"type":"object","properties":{"build_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The build (build_campaign returns it; list_campaign_builds lists them)."}},"required":["build_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removal":{"type":"object","properties":{"build_id":{"type":"string"},"removed_at":{"type":"string"},"sequence":{"anyOf":[{"type":"string","enum":["archived"]},{"type":"null"}],"description":"Its sequence, archived with every enrollment in it stopped"},"desk":{"anyOf":[{"type":"string","enum":["deleted","kept"]},{"type":"null"}],"description":"Its review desk"},"list":{"anyOf":[{"type":"string","enum":["deleted","kept"]},{"type":"null"}],"description":"Its list: deleted only if the build pulled it (the people stay in Prospects); kept if it was told to use it"},"enclosure":{"anyOf":[{"type":"string","enum":["paused","kept"]},{"type":"null"}],"description":"Its enclosure: paused only if the build made it; kept if it was told to use it"},"copies_removed":{"type":"number","description":"Copies taken off the desk unsent; copies already sent stay sent"}},"required":["build_id","removed_at","sequence","desk","list","enclosure","copies_removed"]}},"required":["removal"]}},{"name":"suggest_campaigns","description":"Three cold-email campaigns this org could build in one step (build_campaign), each a title and a prompt to build it as written, from who the org is, the pictures it can make and the apps it is signed in to. Requires capability sequence:read.","capability":"sequence:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"prompt":{"type":"string"}},"required":["title","prompt"]}}},"required":["suggestions"]}},{"name":"generate_prospect_openers","description":"Print a personalized opening line for every prospect in a list that doesn't have one yet: attaches a Ledes Group to the list (group_id, new_group_name to create one, or the one already attached) and runs it. Uses model calls, at most `cap` rows per call (call again for the rest). Runs in the background: poll list_prospects and read each prospect's enrichment.lede. Requires capability prospect:write.","capability":"prospect:write","inputSchema":{"type":"object","properties":{"list_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"group_id":{"description":"An existing Ledes Group to attach to the list.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"new_group_name":{"description":"Create a Ledes Group with this name and attach it (instead of group_id).","type":"string","minLength":1,"maxLength":80}},"required":["list_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"queued":{"type":"number","description":"Prospects the run will print an opener for (0 = every row already has one)."},"group_id":{"type":"string","description":"The Ledes Group attached to the list."},"cap":{"type":"number","description":"Most openers one run prints; call again for the rest."}},"required":["queued","group_id","cap"]}},{"name":"get_mail_connections","description":"Status of the org's mail connections: the bring-your-own mailboxes connected to it (Google Workspace / Gmail), whether connecting one is available on this environment, whether the org has set its own Vercel token for domain purchases (set_vercel_key) and the team it buys in, whether it has set the WHOIS registrant contact that purchases on that token require (set_registrant_contact), and whether it has a CAN-SPAM mailing address on file (without one, emails go out with no postal footer line; set it with set_mail_postal_address). Connecting a Google mailbox needs a browser consent, so it happens in the app. No credential is ever returned. Requires capability mailer:read.","capability":"mailer:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"external_mailers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"display_name":{"type":["string","null"]},"status":{"type":"string"},"daily_send_limit":{"type":"number","description":"Manual daily limit; 0 means the mailer follows the sending ramp."},"ready_at":{"description":"When the mailer became ready: the start of its sending ramp.","type":["string","null"]},"effective_daily_limit":{"type":"number","description":"Emails it may send today: the manual limit, else the ramp's."},"created_at":{"type":"string"}},"required":["id","address","display_name","status","daily_send_limit","ready_at","effective_daily_limit","created_at"]},"description":"Connected bring-your-own mailboxes (Google Workspace / Gmail)."},"google_connect_available":{"type":"boolean","description":"Whether Google mailbox connection is set up on this environment (connect happens in the app)."},"vercel_key_configured":{"type":"boolean","description":"Whether the org has its own Vercel token for domain purchases (set_vercel_key or Settings; never returned)."},"vercel_team_id":{"description":"The Vercel team the org's own key buys domains in; null = the token's personal account (or no key).","type":["string","null"]},"registrant_contact_configured":{"type":"boolean","description":"Whether the org has set its own WHOIS registrant contact (set_registrant_contact or Settings → Integrations). A domain bought on the org's own Vercel token registers to that contact and is refused until it is set; platform-billed purchases never use it."},"postal_address_configured":{"type":"boolean","description":"Whether the org has a CAN-SPAM mailing address on file (its own, set with set_mail_postal_address, or a launched Mailers domain's). While false, emails go out with NO postal footer line — set one before commercial sending."}},"required":["external_mailers","google_connect_available","vercel_key_configured","vercel_team_id","registrant_contact_configured","postal_address_configured"]}},{"name":"set_mail_postal_address","description":"Set the org's physical mailing address, printed in the footer of every commercial email (CAN-SPAM). Connected mailboxes (and any mailer whose domain has no address of its own) send with it; while the org has none on file, their emails go out with no postal footer line. Replaces any previous org address. Requires capability mailer:manage.","capability":"mailer:manage","inputSchema":{"type":"object","properties":{"mail_postal_address":{"type":"string","minLength":6,"maxLength":300,"description":"The org's physical mailing address, printed in the footer of every commercial email (CAN-SPAM). Used by connected mailboxes and any mailer whose domain has no address of its own."}},"required":["mail_postal_address"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mail_postal_address":{"type":"string","description":"The saved address, trimmed, as it will print in every email footer."},"postal_address_configured":{"type":"boolean","const":true}},"required":["mail_postal_address","postal_address_configured"]}},{"name":"get_phantom_agent_access","description":"Which AI assistants (ChatGPT, Claude, Perplexity, Gemini, …) read one phantom over the last 7, 14 or 28 days, with per-agent requests, verified requests, live (question-driven) requests and fetch episodes — the access panel's per-agent totals for one site. Our own GEO probe traffic is excluded. For totals use this, not get_recent_activity: that feed is capped and newest-first, so summing it undercounts. Requires capability phantom_access:read.","capability":"phantom_access:read","inputSchema":{"type":"object","properties":{"phantom_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The phantom to report on."},"days":{"description":"Window in whole UTC days, ending today: 7, 14 or 28 (default 7).","anyOf":[{"type":"number","const":7},{"type":"number","const":14},{"type":"number","const":28}]}},"required":["phantom_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"phantom_id":{"type":"string"},"since":{"type":"string","description":"First UTC date (YYYY-MM-DD) in the window."},"days":{"type":"number"},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","description":"Vendor key, e.g. 'chatgpt', 'claude', 'perplexity', 'gemini'."},"label":{"type":"string","description":"Display name for the vendor."},"requests":{"type":"number","description":"AI-agent requests in the window, every purpose."},"verified":{"type":"number","description":"Of those, requests verified against the vendor's published IP ranges."},"live":{"type":"number","description":"Of those, requests purpose-tagged 'live' (a person's question, not a crawl)."},"episodes":{"type":"number","description":"Distinct fetch episodes (bursts collapsed); 0 = rows predate episode counting."},"liveEpisodes":{"type":"number","description":"Episodes on live-purpose rows: times this AI came for a question."},"verifiedLiveEpisodes":{"type":"number","description":"Live episodes IP-verified against the vendor's ranges."}},"required":["agent","label","requests","verified","live","episodes","liveEpisodes","verifiedLiveEpisodes"]},"description":"Named AI assistants that requested the site in the window, most requests first. Search crawlers excluded."},"aiRequests":{"type":"number","description":"Organic AI-agent requests in the window, every purpose."},"ownProbeAiRequests":{"type":"number","description":"AI-agent requests excluded as likely Letter's own GEO probes."}},"required":["phantom_id","since","days","agents","aiRequests","ownProbeAiRequests"]}},{"name":"get_fleet_totals","description":"What the whole phantom engine has done, added up across every live customer site (not just the caller's): posts published, posts Google has picked up, AI-agent reads over the last 7 days and AI answers (live, question-driven fetches) over the last 28, each with a per-assistant breakdown. Totals only: no org, site or post is named and the site count is never returned, so it is safe to quote on a public page. Our own GEO probe traffic is excluded. Cached up to ten minutes. For the caller's own sites use get_phantom_agent_access and list_phantom_seo_articles. Requires capability phantom_access:read.","capability":"phantom_access:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"posts":{"type":"number","description":"Posts published across every live customer phantom site, all time."},"indexed":{"type":"number","description":"Of those, posts Google has served at least once (a Search Console first impression)."},"aiReads":{"type":"object","properties":{"days":{"type":"number"},"total":{"type":"number"},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","description":"Vendor key, e.g. 'chatgpt', 'claude', 'perplexity', 'gemini'."},"label":{"type":"string","description":"Display name for the vendor."},"count":{"type":"number"}},"required":["agent","label","count"]},"description":"Assistants with any reads, most first."}},"required":["days","total","agents"],"description":"Organic AI-agent requests over the last `days` days, every purpose; our own GEO probes excluded."},"aiAnswers":{"type":"object","properties":{"days":{"type":"number"},"total":{"type":"number"},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","description":"Vendor key, e.g. 'chatgpt', 'claude', 'perplexity', 'gemini'."},"label":{"type":"string","description":"Display name for the vendor."},"count":{"type":"number"}},"required":["agent","label","count"]},"description":"Assistants with any answers, most first."}},"required":["days","total","agents"],"description":"Live fetch episodes over the last `days` days: an assistant fetching a post to answer someone's question, one per site-level burst."},"at":{"type":"string","description":"ISO timestamp the totals were computed (cached up to ten minutes)."}},"required":["posts","indexed","aiReads","aiAnswers","at"]}},{"name":"get_page_access","description":"Who is reading the org's PHANTOM sites over a period: the page-access panel as data. Returns the outcome cards (AI answer engines fetching pages live to answer someone, verified vs claimed; search-engine indexing; beacon-confirmed human readers), the scope-wide request rollup, top pages, pillar (cluster) rollup, and one entry per reporting site. Pass collection_id to narrow to the phantom site(s) a collection publishes. Our own GEO probe traffic is excluded. Not the org's own site: that is Visitors (get_site_metrics, visitors_get_page_traffic). For one phantom's per-assistant totals use get_phantom_agent_access; for the newest individual hits use get_recent_activity. Requires capability phantom_access:read.","capability":"phantom_access:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window in whole UTC days, ending today (default 30d).","type":"string","enum":["14d","30d","90d"]},"collection_id":{"description":"Narrow to the phantom site(s) this collection publishes. A collection with no phantom site (a client's own blog) has no page-access feed and reads as no sites reporting.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["14d","30d","90d"]},"summary":{"type":"object","properties":{"total":{"type":"number","description":"Every request in the window, own GEO probe traffic excluded."},"aiLiveRequests":{"type":"number","description":"AI-agent requests purpose-tagged live (a person's question)."},"aiLiveEpisodes":{"type":"number","description":"Live AI fetch EPISODES (bursts collapsed) — the headline 'AI interest' figure, an estimate."},"aiLiveEpisodesVerified":{"type":"number","description":"Of those, IP-verified against the vendor's published ranges."},"pagesAccessed":{"type":"number"},"byClass":{"type":"array","items":{"type":"object","properties":{"requesterClass":{"type":"string"},"requests":{"type":"number"}},"required":["requesterClass","requests"]},"description":"Requests per requester class: ai_agent, search_crawler, other_bot, browser."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"requests":{"type":"number"}},"required":["agent","requests"]},"description":"Named AI assistants, biggest first (search crawlers excluded)."}},"required":["total","aiLiveRequests","aiLiveEpisodes","aiLiveEpisodesVerified","pagesAccessed","byClass","agents"],"description":"Scope-wide rollup over every row in the window."},"outcome":{"type":"object","properties":{"worthwhile":{"type":"object","properties":{"aiAnswerEngines":{"type":"object","properties":{"verifiedEpisodes":{"type":"number"},"claimedEpisodes":{"type":"number"}},"required":["verifiedEpisodes","claimedEpisodes"],"description":"AI answer engines fetching pages live to answer a person — verified vs claimed, never summed."},"searchEngines":{"type":"object","properties":{"indexingRequests":{"type":"number"}},"required":["indexingRequests"]},"humanReaders":{"type":"object","properties":{"visits":{"type":"number"},"avgSeconds":{"type":"number"},"avgScroll":{"type":"number"}},"required":["visits","avgSeconds","avgScroll"],"description":"Beacon-confirmed human page renders."}},"required":["aiAnswerEngines","searchEngines","humanReaders"]}},"required":["worthwhile"],"description":"The outcome cards the app renders: AI answer engines, search engines, human readers."},"geoContext":{"anyOf":[{"type":"object","properties":{"confirmedCitations":{"type":"number"},"citedWithMention":{"type":"number"}},"required":["confirmedCitations","citedWithMention"]},{"type":"null"}],"description":"GEO cross-check (trailing 28d): confirmed citations, and those that also named the client."},"engagement":{"type":"object","properties":{"visits":{"type":"number"},"aiReferredVisits":{"type":"number"}},"required":["visits","aiReferredVisits"]},"reporting":{"type":"object","properties":{"reporting":{"type":"number"},"stale":{"type":"number"}},"required":["reporting","stale"],"description":"Sites reporting telemetry / gone quiet."},"reportingSince":{"description":"Earliest telemetry date in scope — before it, activity was not observable.","type":["string","null"]},"ownProbeAiRequests":{"type":"number","description":"AI requests excluded as likely Letter's own GEO probes."},"topPages":{"type":"array","items":{"type":"object","properties":{"blogId":{"type":"string"},"path":{"type":"string"}},"required":["blogId","path"]},"description":"Top pages across every site in scope, grouped by (site, path)."},"clusters":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"clusterId":{"type":"string"},"name":{"type":"string"},"blogId":{"type":"string"},"siteName":{"type":"string"},"isUnassigned":{"type":"boolean"},"articlesWithAccess":{"type":"number","description":"Articles in this pillar that received any access this period."},"total":{"type":"number"},"ai":{"type":"number"},"aiLiveEpisodes":{"type":"number","description":"Live AI fetch episodes — the primary sort."},"search":{"type":"number"},"confirmed":{"type":"number","description":"Beacon-confirmed human visits."}},"required":["clusterId","name","blogId","siteName","isUnassigned","articlesWithAccess","total","ai","aiLiveEpisodes","search","confirmed"]}},{"type":"null"}],"description":"Access rolled up to pillar; null when cluster metadata couldn't load (never an empty stand-in)."},"sites":{"type":"array","items":{"type":"object","properties":{"blogId":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"]},"status":{"description":"Telemetry status: on / stale.","type":["string","null"]},"reportingSince":{"type":["string","null"]},"lastSeenAt":{"type":["string","null"]},"requests28d":{"type":"number"},"confirmedVisits28d":{"type":"number"},"aiPrior28d":{"description":"Prior-28d AI requests; null when there's no comparable prior.","type":["number","null"]},"summary":{"type":"object","properties":{"total":{"type":"number","description":"Every request in the window, own GEO probe traffic excluded."},"aiLiveRequests":{"type":"number","description":"AI-agent requests purpose-tagged live (a person's question)."},"aiLiveEpisodes":{"type":"number","description":"Live AI fetch EPISODES (bursts collapsed) — the headline 'AI interest' figure, an estimate."},"aiLiveEpisodesVerified":{"type":"number","description":"Of those, IP-verified against the vendor's published ranges."},"pagesAccessed":{"type":"number"},"byClass":{"type":"array","items":{"type":"object","properties":{"requesterClass":{"type":"string"},"requests":{"type":"number"}},"required":["requesterClass","requests"]},"description":"Requests per requester class: ai_agent, search_crawler, other_bot, browser."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"requests":{"type":"number"}},"required":["agent","requests"]},"description":"Named AI assistants, biggest first (search crawlers excluded)."}},"required":["total","aiLiveRequests","aiLiveEpisodes","aiLiveEpisodesVerified","pagesAccessed","byClass","agents"]},"outcome":{"type":"object","properties":{"worthwhile":{"type":"object","properties":{"aiAnswerEngines":{"type":"object","properties":{"verifiedEpisodes":{"type":"number"},"claimedEpisodes":{"type":"number"}},"required":["verifiedEpisodes","claimedEpisodes"],"description":"AI answer engines fetching pages live to answer a person — verified vs claimed, never summed."},"searchEngines":{"type":"object","properties":{"indexingRequests":{"type":"number"}},"required":["indexingRequests"]},"humanReaders":{"type":"object","properties":{"visits":{"type":"number"},"avgSeconds":{"type":"number"},"avgScroll":{"type":"number"}},"required":["visits","avgSeconds","avgScroll"],"description":"Beacon-confirmed human page renders."}},"required":["aiAnswerEngines","searchEngines","humanReaders"]}},"required":["worthwhile"],"description":"The outcome cards the app renders: AI answer engines, search engines, human readers."}},"required":["blogId","name","domain","status","reportingSince","lastSeenAt","requests28d","confirmedVisits28d","aiPrior28d","summary","outcome"]},"description":"One entry per phantom site that has ever reported, strongest AI demand first."}},"required":["period","summary","outcome","geoContext","engagement","reporting","reportingSince","ownProbeAiRequests","topPages","clusters","sites"]}},{"name":"list_kernels","description":"List the writing kernels this organization can run with run_kernel: the shared catalog plus any kernels this organization built itself. Each entry carries a `description` saying what that kernel writes like — pick on that, not on the name, which is an arbitrary codename (Alder, Aspen, Birch…). A null description means nobody has written one yet. `owned` says which are this org's own (editable with update_kernel/archive_kernel); `is_default` marks the org's currently selected kernel (set_org_writing_kernel). Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"kernel_id":{"type":"string"},"name":{"type":"string"},"description":{"description":"What this kernel writes like. The name is an arbitrary codename, so this is the field to pick on. Null if nobody has written one yet.","type":["string","null"]},"image_url":{"type":["string","null"]},"attributed":{"type":"boolean","description":"True if runs are attributed to a catalog kernel server-side vs. anonymous blob fallback."},"created_at":{"type":"string"},"updated_at":{"type":"string"},"owned":{"type":"boolean","description":"True if this is one of the org's OWN kernels, editable with update_kernel."},"is_default":{"type":"boolean","description":"True if this is the org's currently selected kernel (set_org_writing_kernel)."},"forked_from_name":{"description":"The catalog kernel this org's copy was configured from (configure_kernel), if any.","type":["string","null"]}},"required":["kernel_id","name","description","image_url","attributed","created_at","updated_at","owned","is_default","forked_from_name"]}}},"required":["items"]}},{"name":"get_kernel_run_status","description":"Get the status of an article-bound kernel run: one started with run_kernel and an `article_id` (its `kernel_job_id`). Actively polls the kernel server: when the run just finished, the produced article is written into the article's content (a new version, same as watching it complete in the editor) and the response comes back status done with applied: true. A done run also carries `verification` — the app's checks and, under `verification.service`, the service's record of which claims the article used, which sentences it could not tie to the input, which held blocks came back byte-identical, which citations and links carried — the same record get_kernel_endpoint_run returns for a standalone run. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"kernel_job_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Kernel job UUID returned by run_kernel with an article_id"}},"required":["kernel_job_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel_job_id":{"type":"string"},"article_id":{"type":"string"},"kernel_id":{"type":["string","null"]},"status":{"type":"string","description":"Kernel job status; 'done' when finished."},"error":{"type":["string","null"]},"applied":{"type":"boolean","description":"True when this poll just wrote the produced article into the post as a new version."},"run_url":{"type":["string","null"]},"verification":{"description":"THE RECORD, once the run is done: the app's checks and, under `verification.service`, the service's own verification over the shipped text — the same record get_kernel_endpoint_run returns for a standalone run. Absent while running, on a failed run, and on runs that settled before the record existed.","type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["shape","length","person","jury","entities","scaffolding","claims","held","attachments","title"]},"verdict":{"type":"string","enum":["pass","fail","skipped"],"description":"`skipped` = nothing to read back; never a pass."},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"},"service":{"description":"The kernels service's own verification record over the shipped text, verbatim — the trail: which claims the article used, which sentences the gate could not tie to the input, which held blocks came back byte-identical, which citations and links carried. Null when the service sent none.","anyOf":[{"type":"object","properties":{"claims":{"type":"object","properties":{"mode":{"description":"Who decided which claims the text states; read it first. \"index\" (kernel runs from 2026-10-01): assembly tied each shipped sentence to the claims its own figures, dates and identifiers carry; no claims gate, so `mandatory_dropped`, `dropped_attached` and `sentences` are absent and `index` is set. Absent: a gate-era record (stored runs, the standalone shred).","type":"string"},"total":{"description":"Claims extracted from the input.","type":"number"},"used":{"description":"Claim ids the shipped text states (index ties, or the gate-era writer's markers).","type":"array","items":{"type":"string"}},"unused":{"type":"array","items":{"type":"string"}},"index":{"description":"mode \"index\" only: what the index placed and could not place.","type":"object","properties":{"placed":{"description":"Sentences tied to the claims that carry every figure, date and identifier they state.","type":"number"},"unplaced":{"description":"Sentences stating a figure, date or identifier no claim carries exactly: shipped, with no link.","type":"number"},"attached_unplaced":{"description":"Claims carrying a citation or a link that no shipped sentence was tied to: that attachment did not ship. Should be empty.","type":"array","items":{"type":"string"}}},"additionalProperties":{}},"mandatory_dropped":{"description":"GATE-ERA (mode absent): cited or linked claims the gate dropped as a last resort. Absent on index runs, where it is unknown rather than empty.","type":"array","items":{"type":"string"}},"dropped_attached":{"description":"GATE-ERA: the subset of mandatory_dropped that carried a citation or a link.","type":"array","items":{"type":"string"}},"mandatory_said":{"description":"GATE-ERA: the subset of mandatory_dropped the shipped text states anyway, under another claim's marker (gate runs from 2026-10-02). Absent on index runs and on runs before 2026-10-02.","type":"array","items":{"type":"string"}}},"additionalProperties":{}},"sentences":{"description":"GATE-ERA: the claims gate's sentence verdicts. Absent on index runs (claims.mode \"index\"), whose sentences no gate judged; absent is unknown, not zero.","type":"object","properties":{"supported":{"type":"number"},"unreferenced":{"description":"Shipped sentences tied to no claim — invention.","type":"number"},"contradicted":{"description":"Shipped sentences that contradict the claim they cite.","type":"number"},"dropped":{"type":"number"},"methods":{"type":"object","properties":{"tokens":{"type":"number"},"entailment":{"type":"number"}},"additionalProperties":{}}},"additionalProperties":{}},"held":{"type":"object","properties":{"blocks":{"description":"Tables, images, code, quotes, references held from the input.","type":"number"},"restored":{"type":"number"},"misplaced":{"type":"number"},"identical":{"description":"Restored byte for byte.","type":"number"},"detail":{"description":"Per-block verdicts.","type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":{}},"attachments":{"type":"object","properties":{"citations":{"type":"object","properties":{"expected":{"description":"How many the input carried.","type":"number"},"carried":{"description":"How many the article carries.","type":"number"}},"additionalProperties":{}},"links":{"type":"object","properties":{"expected":{"description":"How many the input carried.","type":"number"},"carried":{"description":"How many the article carries.","type":"number"}},"additionalProperties":{}}},"additionalProperties":{}},"shape":{"type":"object","properties":{"headings_expected":{"type":"number"},"headings_held":{"description":"Headings present at their position and level.","type":"number"},"headings_reworded":{"description":"Of the held headings, how many came back with different wording.","type":"number"},"title_held":{"description":"The input's title came back as the article's title, in its own form.","type":"boolean"}},"additionalProperties":{}},"length":{"type":"object","properties":{"prose_words":{"description":"Regenerated prose only; held blocks excluded.","type":"number"},"target":{"type":"number"},"delta":{"type":"number"}},"additionalProperties":{}},"repairs":{"description":"Every sentence-level repair the loop attempted, and whether the gate accepted it.","type":"array","items":{"type":"object","properties":{"pass":{"type":"string"},"before":{"type":"string"},"after":{"type":"string"},"accepted":{"type":"boolean"},"reason":{"type":"string"}},"additionalProperties":{}}},"content":{"description":"The door's reading of the INPUT, not the article: whether it carried enough named things, figures and cases to write something particular at the requested length, and what to add per section when it did not. Advisory: the run was written either way.","type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}}},"additionalProperties":{}},{"type":"null"}]}},"required":["checks","at"]},"content":{"description":"The door's reading of the INPUT: `sufficient` false when it carries too few named things, figures and cases for the requested length (the article will stay general: the writer cannot add particulars you did not give it), with `thin`, what to add per section. Advisory: the run proceeds. Null when the service sent none.","anyOf":[{"type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}},{"type":"null"}]}},"required":["kernel_job_id","article_id","kernel_id","status","error","applied","run_url"]}},{"name":"list_kernel_endpoints","description":"The caller's kernel endpoints: callable URLs that turn a brief into a finished article. Each carries the kernel it writes with (null = follows the org's selected kernel) and its lifetime run count. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"public_id":{"type":"string","description":"Callable id: POST /api/kernel/ingest/<public_id>."},"kernel_id":{"description":"Bound kernel; null = follows the org's selected kernel.","type":["string","null"]},"kernel_name":{"type":["string","null"]},"shredder_endpoint_id":{"description":"Shredder endpoint this endpoint's runs shred against, in the loop. Null = no in-loop shred.","type":["string","null"]},"shredder_endpoint_name":{"type":["string","null"]},"last_used_at":{"type":["string","null"]},"created_at":{"type":["string","null"]},"run_count":{"type":"number","description":"Lifetime runs through this endpoint."}},"required":["id","name","public_id","kernel_id","kernel_name","shredder_endpoint_id","shredder_endpoint_name","last_used_at","created_at","run_count"]}}},"required":["items"]}},{"name":"create_kernel_endpoint","description":"Create a kernel endpoint for the caller's organization. Returns its callable public id; calls authenticate with the org's API key (POST /api/kernel/ingest/<public_id>). Bind a kernel_id to pin what it writes with, or omit it to follow whatever kernel the org has selected. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"kernel_id":{"description":"Kernel this endpoint writes with by default. Omit to follow the org's selected kernel.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"shredder_endpoint_id":{"description":"Bind a shredder endpoint: every run through this endpoint shreds in the loop and is logged against it. Omit for no in-loop shred.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"public_id":{"type":"string","description":"Callable id: POST /api/kernel/ingest/<public_id>."},"kernel_id":{"description":"Bound kernel; null = follows the org's selected kernel.","type":["string","null"]},"kernel_name":{"type":["string","null"]},"shredder_endpoint_id":{"description":"Shredder endpoint this endpoint's runs shred against, in the loop. Null = no in-loop shred.","type":["string","null"]},"shredder_endpoint_name":{"type":["string","null"]},"last_used_at":{"type":["string","null"]},"created_at":{"type":["string","null"]},"run_count":{"type":"number","description":"Lifetime runs through this endpoint."}},"required":["id","name","public_id","kernel_id","kernel_name","shredder_endpoint_id","shredder_endpoint_name","last_used_at","created_at","run_count"]}},"required":["endpoint"]}},{"name":"get_kernel_endpoint_config","description":"The endpoint's whole configuration as ONE document: its name, the kernel it writes with, the shredder it shreds in the loop against, and its standing run defaults (word budget, person, rounds, entity gate, kernel version pin, in-loop shred tuning — every field run_kernel accepts per call except the content ones). This is the contract: what you get back is exactly what set_kernel_endpoint_config accepts, so read it, edit it, replace it. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Kernel endpoint UUID from list_kernel_endpoints."}},"required":["endpoint_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"name":{"type":"string"},"kernel_id":{"type":["string","null"]},"shredder_endpoint_id":{"type":["string","null"]},"defaults":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"seats":{"description":"The seats this endpoint's kernel runs on: who writes, repairs, judges, decomposes and reads the content for every run through it. Null when the service could not be read.","anyOf":[{"type":"object","properties":{"writer":{"type":["string","null"]},"repair":{"type":["string","null"]},"judge":{"type":["string","null"]},"decompose":{"type":["string","null"]},"content":{"type":["string","null"]}},"required":["writer","repair","judge","decompose","content"]},{"type":"null"}]}},"required":["name","kernel_id","shredder_endpoint_id","defaults"]}},"required":["config"]}},{"name":"set_kernel_endpoint_config","description":"Replace the endpoint's whole configuration with one document — the same shape get_kernel_endpoint_config returns. Fields left out of `defaults` are cleared (this REPLACES; use create/update tools for field-by-field edits). The kernel must be one this org may run and the shredder endpoint must be this org's, exactly as on create. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Kernel endpoint UUID from list_kernel_endpoints."},"config":{"type":"object","properties":{"seats":{"anyOf":[{"type":"object","properties":{"writer":{"type":["string","null"]},"repair":{"type":["string","null"]},"judge":{"type":["string","null"]},"decompose":{"type":["string","null"]},"content":{"type":["string","null"]}},"required":["writer","repair","judge","decompose","content"]},{"type":"null"}]},"name":{"type":"string","minLength":1,"maxLength":80,"description":"The endpoint's name (unique per org)."},"kernel_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Kernel this endpoint writes with. Null = follow the org's selected writing kernel at call time."},"shredder_endpoint_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Shredder endpoint every run shreds in the loop against (and is logged to). Null = no in-loop shred."},"defaults":{"default":{},"description":"Standing run constraints applied to every call through this endpoint; a per-call field overrides.","type":"object","properties":{"max_iterations":{"description":"Round-count override (1–5). The kernel config's own when omitted; 1 is the fast lane the Kernel Lab uses.","type":"integer","minimum":1,"maximum":5},"kernel_version":{"description":"Pin the kernel's catalog version for this run (reproducibility). Omitted = the version the kernel row is mapped to, or its head.","type":"integer","minimum":1,"maximum":9007199254740991},"config_patch":{"description":"A deep patch over the kernel's config for THIS run only, applied by the service and recorded on the run (never a new kernel version): try a writer with `{ \"model\": \"echo/echo\" }`, split the repairs with `repairModel`, pin the judge with `quality.model`. The merged config is validated like a kernel file; a patch the schema refuses is a 400.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"shred":{"description":"Tuning for the in-loop shred, when one runs. See each field; all optional.","type":"object","properties":{"tier":{"description":"Which provider tier the rewrite pool draws from. Service default when omitted.","type":"string","enum":["cheap","mid","strong","frontier"]},"families":{"description":"Restrict the rewrite pool to these provider families (service registry `provider` values).","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"skip_voice_bearing":{"description":"Leave voice-bearing sentences unshredded.","type":"boolean"},"judge":{"description":"Run the post-shred jury on the shredded draft.","type":"boolean"},"attempts_per_unit":{"description":"Retries per sentence across providers.","type":"integer","minimum":1,"maximum":10},"max_changeable_retention":{"description":"Turnover gate: max share of each sentence's changeable words a rewrite may keep.","type":"number","minimum":0,"maximum":1},"turnover_retries":{"description":"Feedback retries per provider over the turnover bar.","type":"integer","minimum":0,"maximum":10},"turnover_avoid_list":{"description":"Spell each sentence's changeable words out as a do-not-use list.","type":"boolean"},"evidence":{"description":"Formatted evidence the shred must conserve each rewrite against — the same bundle the accuracy pass reads, so a rewrite cannot introduce a figure or claim the evidence does not carry. Bring your own; a run with none is shredded unconserved.","type":"string","maxLength":60000}}},"target_word_count":{"description":"Per-call word budget, overriding the kernel config's own. 0 clears a config-set budget.","type":"integer","minimum":0,"maximum":20000},"voice":{"description":"How the article speaks. Both fields optional; omit entirely for no person gate.","type":"object","properties":{"person":{"description":"The publication's editorial person, enforced by a gate rather than requested in a prompt. Omitted = no person gate, the kernel's own prompts decide.","type":"string","enum":["third","first_plural","first_singular"]},"allow_experience_claims":{"description":"Permission to state first-hand observation as fact. A separate choice from person: writing 'I think X' is style, writing 'I've watched forty deploys fail' is a claim. Default false.","type":"boolean"}}},"label":{"description":"A free-form tag for your own segmentation — stored on the run and echoed in the trace. Any string works; never changes how the pipeline runs.","type":"string","maxLength":80},"content_kind":{"description":"How the service reads `input`. 'instruction' takes it as direction to write BY — still held to the same content floor as everything else (roughly 150+ words / 8+ substantive sentences; a bare topic or headline is refused, not developed). 'article', 'outline', 'research', 'notes' are SOURCE MATERIAL, decomposed into a faithful brief inside the service's submit and generated fresh, sized differently by kind: an article targets roughly its own length, an outline or research dump targets 1200-2000 words, so submitting an outline as an article writes it as short as the outline. 'auto' (default) lets the service decide between 'instruction' and 'article' by length alone; name the kind yourself for anything else. Your own finished post is 'article' — every decomposed kind keeps its headings, holds its tables, images, code, quotes and references byte for byte, and has every claim checked against the input's own; shape is always preserved and sections never move.","type":"string","enum":["auto","instruction","article","outline","research","notes"]},"shredder_endpoint_id":{"description":"Shred IN THE LOOP and log it against this shredder endpoint: every round is shredded before its jury, so the kernel judges the text that actually ships. Costs a shred as well as a run. Omit to use whatever the kernel endpoint is bound to; if neither is set but shred_aggression or shred is, the org's default shredder endpoint is provisioned on first use (same as a kernel endpoint itself) — you do not have to create one by hand first to shred in the loop. Omit all three for no in-loop shred.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"shred_aggression":{"description":"How hard the in-loop shred rewrites. Setting this (or shred) turns in-loop shredding ON even with no shredder_endpoint_id — the org's default shredder endpoint provisions on first use. 'standard' (default and recommended) rewrites every sentence and keeps the piece's own terminology; 'high' rewrites more and may reword recurring terms; 'max' rewrites hardest, keeping only names and figures — expect noticeable vocabulary drift.","type":"string","enum":["standard","high","max"]},"target_word_tolerance":{"description":"How far from `target_word_count` the draft may land before the writer is asked to trim or extend, as a fraction. Default 0.1 (10%). Raise it for a looser budget, lower it to hold a tight one; it does nothing without a target.","type":"number","minimum":0.01,"maximum":0.5},"directives":{"description":"Your own guidance for the writer — how to write, not what is true: a client to name once, an editorial stance, a format to favour. Appended to the brief AFTER the claims are read from `input`, so nothing here becomes a claim the article must carry. Put facts in `input`; put instructions here.","type":"string","maxLength":50000},"entities":{"description":"How the kernel treats named things — a client, a competitor, anything you want the article to name a specific way. 'never': off-limits, must not appear anywhere. 'mention_once': at most one natural, unrepeated mention. 'mention_often': referenced a few times where genuinely relevant. 'lead_company': named first and treated as the strongest, most prominent option wherever the topic invites a comparison. Sent to the service as data and rendered into the brief there, not left to prose you write yourself. A name your input itself carries survives a treatment — the input is the source of truth; the treatment governs what the writer adds. Checked back on settle for 'never', 'mention_once' and 'mention_often' (a count over the regenerated prose); 'lead_company' and 'linkable' are prose-only today and not independently verified.","maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"treatment":{"type":"string","enum":["never","mention_once","mention_often","lead_company"]},"linkable":{"description":"Whether a mention may be linked to or cited as a source. Default true. Independent of treatment — set false on top of any of them (e.g. mention_often + linkable:false for a competitor named a few times but never linked). Ignored when treatment is 'never'.","type":"boolean"}},"required":["name","treatment"]}},"template":{"description":"Stand a template up for every run through this endpoint: every article is written to that shape. Per-call `template` fields (title, variables, constraints) merge over it, so a call supplies only what is per-post. A template is a FORMAT, not a primitive — docs/formats/templates.md.","type":"object","properties":{"key":{"description":"A template key from list_templates. Mutually exclusive with `blocks`.","type":"string","minLength":1,"maxLength":80},"blocks":{"description":"A template authored in this request, for a caller who stores none. The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back.","minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]}},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":80},"additionalProperties":{"type":"string","minLength":1,"maxLength":400},"description":"Values for the template's {Variables}, e.g. {\"Queue\": \"Sidekiq\"}. Matched case-insensitively against the variable names in the template. A declared value beats anything derived, and is never sent to a model to be guessed."}},"additionalProperties":false}}}},"required":["name","kernel_id","shredder_endpoint_id"],"description":"The whole configuration; replaces what is stored."}},"required":["endpoint_id","config"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"config":{"type":"object","properties":{"name":{"type":"string"},"kernel_id":{"type":["string","null"]},"shredder_endpoint_id":{"type":["string","null"]},"defaults":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"seats":{"description":"The seats this endpoint's kernel runs on: who writes, repairs, judges, decomposes and reads the content for every run through it. Null when the service could not be read.","anyOf":[{"type":"object","properties":{"writer":{"type":["string","null"]},"repair":{"type":["string","null"]},"judge":{"type":["string","null"]},"decompose":{"type":["string","null"]},"content":{"type":["string","null"]}},"required":["writer","repair","judge","decompose","content"]},{"type":"null"}]}},"required":["name","kernel_id","shredder_endpoint_id","defaults"]}},"required":["config"]}},{"name":"delete_kernel_endpoint","description":"Delete one of the caller's kernel endpoints. Its past runs are preserved. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["endpoint_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}},{"name":"add_kernel_custom_eval","description":"Add (or replace-by-id) one custom eval on an org-owned kernel's rules.custom[] — your own check the kernels service runs alongside its built-in rules, on TOP of the always-on gates (em-dash, worn-AI-framing, word rarity) no config can turn off. Every genuinely new id is filed as a PROPOSAL only (status 'proposed') and never auto-activated, whether or not it has a repair — a person must review and activate it through the kernels service before it enforces on any article; this tool cannot skip that. Re-submitting an id the service already filed from an earlier call is a no-op THERE (see custom_evals.skipped in the response) — it only replaces this kernel's own draft declaration of that id, which is what lets you fix a typo before review. detector.kind: 'builtin' is not accepted; that is reserved for the service's own hand-wired gates. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"detector":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"sentence-regex"},"pattern":{"type":"string","minLength":1,"maxLength":500},"flags":{"type":"string","maxLength":10}},"required":["kind","pattern"]},{"type":"object","properties":{"kind":{"type":"string","const":"char-set"},"pattern":{"type":"string","minLength":1,"maxLength":500},"flags":{"type":"string","maxLength":10}},"required":["kind","pattern"]},{"type":"object","properties":{"kind":{"type":"string","const":"heading-regex"},"pattern":{"type":"string","minLength":1,"maxLength":500},"flags":{"type":"string","maxLength":10}},"required":["kind","pattern"]},{"type":"object","properties":{"kind":{"type":"string","const":"word-list"},"words":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","minLength":1}},"flags":{"type":"string","maxLength":10}},"required":["kind","words"]},{"type":"object","properties":{"kind":{"type":"string","const":"structure"},"scope":{"type":"string","enum":["paragraph","section","document"]},"steps":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"object","properties":{"match":{"type":"string","maxLength":500},"flags":{"type":"string","maxLength":10},"maxWords":{"type":"integer","minimum":1,"maximum":9007199254740991},"minWords":{"type":"integer","minimum":1,"maximum":9007199254740991},"question":{"type":"boolean"},"position":{"type":"string","enum":["first","last","notFirst"]},"sameOpener":{"type":"boolean"},"sameOpenerWords":{"type":"integer","minimum":1,"maximum":9007199254740991},"echoesFirst":{"type":"number","minimum":0,"maximum":1},"notMatch":{"type":"string","maxLength":500},"hasNumber":{"type":"boolean"},"paragraphOpener":{"type":"boolean"}}}},"gap":{"type":"number","minimum":0},"minRun":{"type":"integer","minimum":1,"maximum":9007199254740991},"minParagraphSentences":{"type":"integer","minimum":1,"maximum":9007199254740991}},"required":["kind","steps"]},{"type":"object","properties":{"kind":{"type":"string","const":"distribution"},"metric":{"type":"string","enum":["contractions_per_1k","semicolons_per_1k","sentence_length_cv","short_sentence_share","paragraph_sentence_cv","repeated_content_word_share","question_share","section_length_cv","section_ending_short_share","colons_per_1k","commas_per_sentence","heavy_comma_sentence_share","short_paragraph_opener_share","numerals_per_1k","attributions_per_1k","heading_mean_words","heading_wh_share","heading_colon_share","sentence_initial_conjunction_share","oxford_triads_per_1k","rhetorical_moves_per_1k","words_per_1k","reader_per_1k","semicolons_per_sentence"]},"min":{"type":"number"},"max":{"type":"number"},"words":{"maxItems":200,"type":"array","items":{"type":"string","minLength":1}}},"required":["kind","metric"]},{"type":"object","properties":{"kind":{"type":"string","const":"llm-judge"},"prompt":{"type":"string","minLength":1,"maxLength":4000},"model":{"type":"string","maxLength":80}},"required":["kind","prompt"]},{"type":"object","properties":{"kind":{"type":"string","const":"hybrid"},"shape":{"type":"string","minLength":1,"maxLength":500},"prompt":{"type":"string","minLength":1,"maxLength":4000},"pattern":{"type":"string","maxLength":500},"flags":{"type":"string","maxLength":10},"shapeFlags":{"type":"string","maxLength":10},"model":{"type":"string","maxLength":80},"positive":{"maxItems":50,"type":"array","items":{"type":"string"}},"negative":{"maxItems":50,"type":"array","items":{"type":"string"}}},"required":["kind","shape","prompt"]}]},"repair":{"type":"object","properties":{"corrective":{"type":"string","minLength":1,"maxLength":2000},"systemPrompt":{"type":"string","minLength":1,"maxLength":2000},"maxRetries":{"default":2,"type":"integer","minimum":0,"maximum":10},"failClosed":{"default":"keep","type":"string","enum":["drop","keep"]}},"required":["corrective","systemPrompt"]},"regimes":{"type":"object","properties":{"person":{"type":"array","items":{"type":"string","enum":["third","first_plural","first_singular"]}}}},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id","name","detector","kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"}},"required":["id","version"]},"custom_evals":{"anyOf":[{"type":"object","properties":{"filed":{"type":"array","items":{"type":"string"},"description":"Newly proposed this call — filed at status 'proposed', not yet active."},"skipped":{"type":"array","items":{"type":"string"},"description":"Already exist as a rule (any status) from an earlier submission — left untouched, not re-filed."},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"]},"description":"Filing failed for this entry."}},"required":["filed","skipped","errors"]},{"type":"null"}],"description":"The kernels service's per-eval filing outcome for everything currently declared, not just this call's entry — null on the rare response that carries none."}},"required":["kernel","custom_evals"]}},{"name":"remove_kernel_custom_eval","description":"Stop declaring one custom eval on an org-owned kernel — removes it from rules.custom[] going forward. Does NOT retire whatever the kernels service already filed or a person already approved for that id; that stays until a person acts on it through the kernels service's own review surface. Idempotent: removing an id that isn't currently declared succeeds without error. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"eval_id":{"type":"string","minLength":1,"maxLength":80}},"required":["kernel_id","eval_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"}},"required":["id","version"]}},"required":["kernel"]}},{"name":"extract_document","description":"Turn a DOCUMENT into text you can submit. Send the file's raw bytes base64-encoded with its real filename — the extension decides how it is read — and get back plain text for `run_kernel`'s `input`. Reads Word (.docx), PDF, Rich Text (.rtf), OpenDocument (.odt), HTML, Markdown (.md/.markdown/.mdx), plain text, CSV/TSV and caption files (.vtt/.srt); .doc, .pages, .pptx and .xlsx are refused with the format to convert to instead. USE THIS RATHER THAN READING THE FILE YOURSELF for anything that is not already text: a .docx is a zip and a .pdf is a binary container, so reading either as UTF-8 yields bytes no model can use and a run spent on noise. Embedded base64 images are removed on the way through and reported in `notice` — a Google Docs or Notion markdown export inlines every screenshot at ~67KB, which no text pipeline can carry and which is enough on its own to push a short article past a door's character cap. Free: no credit, no model call, nothing stored. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The document's file name — THE EXTENSION IS WHAT DECIDES HOW IT IS READ, so it must be present and correct (a .docx named \"report\" cannot be parsed). Accepts .md, .markdown, .mdx, .txt, .text, .docx, .pdf, .rtf, .odt, .html, .htm, .csv, .tsv, .vtt or .srt."},"content_base64":{"type":"string","minLength":1,"maxLength":13982038,"description":"The document's raw bytes, base64-encoded. Up to 10MB decoded. Send the file EXACTLY as it is on disk — do not decode, re-encode or pretty-print it first; a .docx and a .pdf are binary and any transformation destroys them."}},"required":["filename","content_base64"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"text":{"type":"string","description":"The document's text, ready to pass as `input` to run_kernel."},"format":{"type":"string","description":"Which format was read (the extension), echoed back."},"chars":{"type":"number","description":"Length of `text` in characters — compare against a door's cap before submitting."},"notice":{"description":"Non-fatal: what happened to the document's embedded images. Null when it had none.","type":["string","null"]},"images_hosted":{"type":"number","description":"Embedded images uploaded to durable storage and rewritten as URLs in `text`. These SURVIVE a run: submit the text as source material (content_kind 'article', or 'auto' on anything long enough) and each one is held verbatim in the finished article."},"images_removed":{"type":"number","description":"Embedded images dropped instead (an upload that failed). Their figures will not appear in the output; re-add them afterwards."},"images_unreachable":{"type":"number","description":"Images present in the document that THIS FORMAT cannot surrender — a PDF's image XObjects, an .odt's Pictures/ members, an .rtf's \\pict blobs. Re-uploading will not help; `notice` names the export that would (.docx, whose images do come through)."}},"required":["text","format","chars","notice","images_hosted","images_removed","images_unreachable"]}},{"name":"run_kernel","description":"Write prose with a kernel. Give it `input` and it hands the text back; add `article_id` and it writes into that article instead (locked for the run, the collection's own voice and client-mention policy applied, the finished draft applied with its accuracy record). An article-bound run reads only kernel_id (else the org's selected kernel), input and content_kind: `endpoint_id` is still required but unused, and the endpoint's kernel and defaults, template, skeleton, entities, directives, shred and max_iterations are ignored; the article's collection supplies voice, mentions and rounds. To write a collection post with its research, template and enhance passes, use run_auto_pen instead. With `article_id` and no `input`, it writes from that article's current content. Write an article from one input field, `input` — a brief, an outline, notes, a research dump, or your own finished document to regenerate. PASS THE SOURCE ITSELF, never a summary you wrote of it: tables, links and inline citations are held and restored from what you send, so a digest of a document arrives with none of them. `content_kind` says which: 'instruction' (direction to write BY, still held to the content floor below), or 'article'/'outline'/'research'/'notes' (SOURCE MATERIAL, decomposed into a typed brief first and GENERATED FRESH, never edited in place — 'auto', the default, decides between 'instruction' and 'article' by length alone). Your own finished post is 'article'. On EVERY decomposed kind your tables, images, code, quotes and references are held byte for byte and restored in place in the generated draft, and every claim the article makes is checked against your input's own claims — you do not have to name a kind to get that. Shape is always preserved — sections are never moved — and your own `skeleton`, if you pass one, wins over the derived shape. A kernel writes prose from content it is given and does not source content, so source material submitted as `content_kind: 'instruction'` comes back as its own input. Pass shredder_endpoint_id (or bind one to the endpoint) to shred IN THE LOOP: every round is shredded before the kernel judges it, so the verdict lands on the text that ships, and the shred is logged against that shredder endpoint. That costs a shred as well as a run. You do not have to create a shredder endpoint yourself first: pass shred_aggression (or shred) with no shredder_endpoint_id and none bound, and the org's default shredder endpoint provisions on first use, the same way the org's default kernel endpoint does — it is then a real shredder endpoint, its runs show up in your own shred usage. Pass shred_aggression to control how hard that in-loop shred rewrites ('standard', the default and recommended, 'high', or 'max'). A HEADLINE OR BARE TOPIC IS NOT ENOUGH ON ITS OWN: `content_kind: 'instruction'` (or 'auto' landing there) submits the text as-is, and anything under roughly 150 words / 8 substantive sentences is refused before any model call rather than written — the kernel does not invent the mechanism, examples or figures a thin topic is missing. If you only have a sentence, ask for a few paragraphs of real material (an outline, notes, a rough draft) and submit that with the matching `content_kind`. Pass entities to name how specific things get treated — a client, a competitor, anything — as data instead of writing the instruction yourself: never (off-limits entirely), mention_once (one natural mention), mention_often (referenced a few times where relevant), or lead_company (named first, most prominent). Each also takes `linkable: false` (default true) to forbid a link or citation regardless of how often it's named — that's the old 'name_only' treatment, now a modifier rather than its own slot. Checked back on settle for never/mention_once/mention_often (frequency only; `linkable` and `lead_company` are prose-only). ASYNCHRONOUS AND SLOW: a kernel write commonly takes eight minutes or more, so this returns a run_id immediately and you poll get_kernel_endpoint_run until status is 'done' (or 'error'). The submit itself answers in seconds whatever the input's length — the service mints the run id first and reads the input (decompose included) behind it — and a resubmit of the same input and options within the service's window returns the run already started rather than a second charge, so if a submit times out, resubmit it or call list_kernel_endpoint_runs. `decomposed` on the response is the service's decision (null when it did not say). On 'done', get_kernel_endpoint_run's `verification` carries the app's checks and, under `service`, the service's own record: which claims the article used, which sentences it could not tie to your input, which held blocks came back byte-identical, which citations and links carried. Which kernel writes: kernel_id if you pass one, else the kernel bound to the endpoint, else the org's selected kernel. Without `article_id` this touches no article — the prose comes back to you. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"template":{"description":"Shape the article with one of your templates instead of hand-building `skeleton`. A template decides what sections the article has, in what order and in what form — prose, a bulleted list, numbered steps, a comparison, a quoted literal — and resolving it fills `skeleton`, `target_word_count` and `brief_json` for this call, with the material you sent as the source each section is written from. What it buys over a hand-built skeleton: the authoring grammar, a generated label for every unheaded block, {Variables} filled, word budgets allocated across the sections, optional sections the material did not earn dropped — and the way back, because the finished run comes out ASSEMBLED (`format: \"html\"`) rather than as the scaffolding the kernel writes. Anything you also set explicitly still wins. Not decomposed: a template's brief already carries your material as SOURCE MATERIAL.","type":"object","properties":{"key":{"description":"A template key from list_templates. Mutually exclusive with `blocks`.","type":"string","minLength":1,"maxLength":80},"blocks":{"description":"A template authored in this request, for a caller who stores none. The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back.","minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]}},"title":{"description":"The working headline. What a generated section label is made concrete about — a block reading 'how teams end up here' becomes 'How the task queue starts to show its limits' rather than 'Background'.","type":"string","maxLength":200},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":80},"additionalProperties":{"type":"string","minLength":1,"maxLength":400},"description":"Values for the template's {Variables}, e.g. {\"Queue\": \"Sidekiq\"}. Matched case-insensitively against the variable names in the template. A declared value beats anything derived, and is never sent to a model to be guessed."},"constraints":{"description":"An answer resolve_template already gave for this template. Pass it back and nothing is re-decided — which matters because the kernel writes each section under its label and assembly matches output back by that label, so a second resolution files sections under headings the outline never planned. Always pass this through when the same template drives both a research run and a write.","type":"array","items":{"type":"object","properties":{"blockId":{"type":"string"},"heading":{"type":["string","null"]},"covers":{"type":"string"},"form":{"type":"string","enum":["passage","enumeration","sequence","matrix","literal","open"]},"required":{"type":"boolean"},"label":{"type":"string","description":"What the research plans for and the writer writes under."},"printHeading":{"type":"boolean","description":"Whether the finished article prints a heading here."},"subjects":{"type":"array","items":{"type":"string"}},"dimensions":{"type":"array","items":{"type":"string"}},"layout":{"type":"string","enum":["subjects-as-columns","subjects-as-rows"]},"literal":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"variables":{"type":"array","items":{"type":"string"}}},"required":["blockId","heading","covers","form","required","label","printHeading"]}}}},"skeleton":{"description":"The SHAPE the article must hold, as data: an ordered list of blocks (heading / list / table / literal / paragraphs). This is the field a template resolves to — send your own and the kernel writes within it. Wins over the shape the service derives from source material. A shape written into the brief as prose holds at roughly a quarter fidelity; this holds.","maxItems":80,"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"heading"},"level":{"type":"integer","minimum":1,"maximum":6},"title":{"type":"string","minLength":1,"maxLength":300}},"required":["kind","level","title"]},{"type":"object","properties":{"kind":{"type":"string","const":"list"},"ordered":{"type":"boolean"},"minItems":{"description":"A floor meaning 'genuinely a list', never a target count.","type":"integer","minimum":1,"maximum":50}},"required":["kind","ordered"]},{"type":"object","properties":{"kind":{"type":"string","const":"table"},"cols":{"type":"integer","minimum":1,"maximum":20}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"literal"},"text":{"type":"string","minLength":1,"maxLength":20000,"description":"Held byte for byte; masked out of every copy-edit pass."}},"required":["kind","text"]},{"type":"object","properties":{"kind":{"type":"string","const":"paragraphs"}},"required":["kind"]}]}},"brief_json":{"description":"The brief as typed data (a KernelBrief): title, angle, passages, sources, statistics. The service uses it for its entity and figure gates; the text input stays the writing input. The service's decompose produces one for source material — send your own if you already have a typed brief. Wins over the service's. Claims are never a field: they are derived from your input on every run.","type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":300},"angle":{"type":"string","maxLength":2000},"passages":{"maxItems":60,"type":"array","items":{"type":"object","properties":{"heading":{"type":"string","minLength":1,"maxLength":300},"form":{"type":"string","minLength":1,"maxLength":40},"covers":{"type":"string","maxLength":4000},"wordTarget":{"type":"integer","minimum":0,"maximum":9007199254740991},"items":{"type":"array","items":{"type":"string","maxLength":1000}},"literal":{"type":"string","maxLength":20000}},"required":["heading","form","covers"],"additionalProperties":{}}},"source":{"maxItems":200,"type":"array","items":{"type":"string","maxLength":2000}},"statistics":{"maxItems":200,"type":"array","items":{"type":"object","properties":{"figure":{"type":"string","maxLength":200},"claim":{"type":"string","maxLength":1000}},"required":["figure","claim"]}}},"required":["title","angle","passages","source","statistics"],"additionalProperties":{}},"max_iterations":{"description":"Round-count override (1–5). The kernel config's own when omitted; 1 is the fast lane the Kernel Lab uses.","type":"integer","minimum":1,"maximum":5},"kernel_version":{"description":"Pin the kernel's catalog version for this run (reproducibility). Omitted = the version the kernel row is mapped to, or its head.","type":"integer","minimum":1,"maximum":9007199254740991},"config_patch":{"description":"A deep patch over the kernel's config for THIS run only, applied by the service and recorded on the run (never a new kernel version): try a writer with `{ \"model\": \"echo/echo\" }`, split the repairs with `repairModel`, pin the judge with `quality.model`. The merged config is validated like a kernel file; a patch the schema refuses is a 400.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"shred":{"description":"Tuning for the in-loop shred, when one runs. See each field; all optional.","type":"object","properties":{"tier":{"description":"Which provider tier the rewrite pool draws from. Service default when omitted.","type":"string","enum":["cheap","mid","strong","frontier"]},"families":{"description":"Restrict the rewrite pool to these provider families (service registry `provider` values).","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"skip_voice_bearing":{"description":"Leave voice-bearing sentences unshredded.","type":"boolean"},"judge":{"description":"Run the post-shred jury on the shredded draft.","type":"boolean"},"attempts_per_unit":{"description":"Retries per sentence across providers.","type":"integer","minimum":1,"maximum":10},"max_changeable_retention":{"description":"Turnover gate: max share of each sentence's changeable words a rewrite may keep.","type":"number","minimum":0,"maximum":1},"turnover_retries":{"description":"Feedback retries per provider over the turnover bar.","type":"integer","minimum":0,"maximum":10},"turnover_avoid_list":{"description":"Spell each sentence's changeable words out as a do-not-use list.","type":"boolean"},"evidence":{"description":"Formatted evidence the shred must conserve each rewrite against — the same bundle the accuracy pass reads, so a rewrite cannot introduce a figure or claim the evidence does not carry. Bring your own; a run with none is shredded unconserved.","type":"string","maxLength":60000}}},"target_word_count":{"description":"Per-call word budget, overriding the kernel config's own. 0 clears a config-set budget.","type":"integer","minimum":0,"maximum":20000},"voice":{"description":"How the article speaks. Both fields optional; omit entirely for no person gate.","type":"object","properties":{"person":{"description":"The publication's editorial person, enforced by a gate rather than requested in a prompt. Omitted = no person gate, the kernel's own prompts decide.","type":"string","enum":["third","first_plural","first_singular"]},"allow_experience_claims":{"description":"Permission to state first-hand observation as fact. A separate choice from person: writing 'I think X' is style, writing 'I've watched forty deploys fail' is a claim. Default false.","type":"boolean"}}},"label":{"description":"A free-form tag for your own segmentation — stored on the run and echoed in the trace. Any string works; never changes how the pipeline runs.","type":"string","maxLength":80},"content_kind":{"description":"How the service reads `input`. 'instruction' takes it as direction to write BY — still held to the same content floor as everything else (roughly 150+ words / 8+ substantive sentences; a bare topic or headline is refused, not developed). 'article', 'outline', 'research', 'notes' are SOURCE MATERIAL, decomposed into a faithful brief inside the service's submit and generated fresh, sized differently by kind: an article targets roughly its own length, an outline or research dump targets 1200-2000 words, so submitting an outline as an article writes it as short as the outline. 'auto' (default) lets the service decide between 'instruction' and 'article' by length alone; name the kind yourself for anything else. Your own finished post is 'article' — every decomposed kind keeps its headings, holds its tables, images, code, quotes and references byte for byte, and has every claim checked against the input's own; shape is always preserved and sections never move.","type":"string","enum":["auto","instruction","article","outline","research","notes"]},"shredder_endpoint_id":{"description":"Shred IN THE LOOP and log it against this shredder endpoint: every round is shredded before its jury, so the kernel judges the text that actually ships. Costs a shred as well as a run. Omit to use whatever the kernel endpoint is bound to; if neither is set but shred_aggression or shred is, the org's default shredder endpoint is provisioned on first use (same as a kernel endpoint itself) — you do not have to create one by hand first to shred in the loop. Omit all three for no in-loop shred.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"shred_aggression":{"description":"How hard the in-loop shred rewrites. Setting this (or shred) turns in-loop shredding ON even with no shredder_endpoint_id — the org's default shredder endpoint provisions on first use. 'standard' (default and recommended) rewrites every sentence and keeps the piece's own terminology; 'high' rewrites more and may reword recurring terms; 'max' rewrites hardest, keeping only names and figures — expect noticeable vocabulary drift.","type":"string","enum":["standard","high","max"]},"target_word_tolerance":{"description":"How far from `target_word_count` the draft may land before the writer is asked to trim or extend, as a fraction. Default 0.1 (10%). Raise it for a looser budget, lower it to hold a tight one; it does nothing without a target.","type":"number","minimum":0.01,"maximum":0.5},"directives":{"description":"Your own guidance for the writer — how to write, not what is true: a client to name once, an editorial stance, a format to favour. Appended to the brief AFTER the claims are read from `input`, so nothing here becomes a claim the article must carry. Put facts in `input`; put instructions here.","type":"string","maxLength":50000},"entities":{"description":"How the kernel treats named things — a client, a competitor, anything you want the article to name a specific way. 'never': off-limits, must not appear anywhere. 'mention_once': at most one natural, unrepeated mention. 'mention_often': referenced a few times where genuinely relevant. 'lead_company': named first and treated as the strongest, most prominent option wherever the topic invites a comparison. Sent to the service as data and rendered into the brief there, not left to prose you write yourself. A name your input itself carries survives a treatment — the input is the source of truth; the treatment governs what the writer adds. Checked back on settle for 'never', 'mention_once' and 'mention_often' (a count over the regenerated prose); 'lead_company' and 'linkable' are prose-only today and not independently verified.","maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"treatment":{"type":"string","enum":["never","mention_once","mention_often","lead_company"]},"linkable":{"description":"Whether a mention may be linked to or cited as a source. Default true. Independent of treatment — set false on top of any of them (e.g. mention_often + linkable:false for a competitor named a few times but never linked). Ignored when treatment is 'never'.","type":"boolean"}},"required":["name","treatment"]}},"input":{"type":"string","minLength":1,"maxLength":200000,"description":"The text to write from, or your own document to regenerate — `content_kind` says which. An outline, notes, a research dump or a finished article all work as source material (name the kind, or let 'auto' decide by length) and are decomposed into a faithful brief inside the service's submit; the submit itself answers in seconds and stores your input exactly as sent, so every claim in the article can be checked against it. A bare topic or headline submitted as `content_kind: 'instruction'` is NOT decomposed and is instead measured against a content floor (150+ words, 8+ substantive sentences) — the kernel writes prose from content it is given and does not invent facts, examples or figures to fill a thin topic, so anything short of that floor is refused rather than 'developed'. If what you have is genuinely just a sentence, write a few paragraphs of real material first (notes, a rough draft, research) and submit that instead."},"kernel_id":{"description":"Override the endpoint's kernel for this call.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"endpoint_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Kernel endpoint UUID from list_kernel_endpoints."},"article_id":{"description":"Write the result into this article (from list_articles or ingest_article) instead of handing the text back. The article is locked for the run, its own collection settings apply (voice, client mention, blocklist), and the finished draft is applied to it with its accuracy record. Omit `input` as well to write from the article's CURRENT content — the one call that writes into an article.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["endpoint_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string","description":"Poll this with get_kernel_endpoint_run."},"status":{"type":"string","const":"running"},"kernel":{"type":"object","properties":{"id":{"type":["string","null"]},"name":{"type":["string","null"]}},"required":["id","name"]},"shredding":{"type":"boolean","description":"This run shreds in the loop and will also log a shred run."},"decomposed":{"description":"The service read the input as source material and decomposed it into a brief before writing (its decision, made inside its submit, after the run id was minted). Null when the service did not say — an older deployment, or a resubmit of the same input and options that returned the run already started rather than a second one.","type":["boolean","null"]},"content":{"description":"The door's reading of the INPUT: `sufficient` false when it carries too few named things, figures and cases for the requested length (the article will stay general: the writer cannot add particulars you did not give it), with `thin`, what to add per section. Advisory: the run proceeds. Null when the service sent none.","anyOf":[{"type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}},{"type":"null"}]},"kernel_job_id":{"description":"Article-bound runs only: poll this with get_kernel_run_status. The draft is applied to the article when it settles.","type":"string"},"article_id":{"description":"Article-bound runs only: the article this run writes into.","type":"string"}},"required":["run_id","status","kernel","shredding","decomposed"]}},{"name":"get_kernel_endpoint_run","description":"Poll a run started with run_kernel and collect the article once it is finished. Statuses: 'running' (keep polling, 15 to 30 seconds apart), 'done' (the article is in the response, with `verification`: the app's checks and, under `verification.service`, the service's record of which claims the article used, which sentences it could not tie to the input, which held blocks came back byte-identical, which citations and links carried), 'error' (the reason is in 'error'). Distinct from get_kernel_run_status, which polls an ARTICLE-bound run (run_kernel with `article_id`, by its `kernel_job_id`). Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","minLength":1,"description":"The run id returned by run_kernel."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string"},"status":{"type":"string","enum":["running","done","error"]},"kernel":{"type":"object","properties":{"id":{"type":["string","null"]},"name":{"type":["string","null"]}},"required":["id","name"]},"input":{"description":"The input as the door received it, in full and unaltered — before any inline-payload strip, embed extraction or decompose. What a claim in `article` is checked against. Null on runs logged before it was stored.","type":["string","null"]},"article":{"description":"The finished article. Present only when status is 'done'.","type":"string"},"format":{"description":"What `article` is: the kernel's own markdown, or a templated run's assembled HTML.","type":"string","enum":["markdown","html"]},"article_raw":{"description":"A templated run's pre-assembly output, beside the assembled `article`.","type":"string"},"word_count":{"type":["number","null"]},"shred_run_id":{"description":"The shred_runs row this run's in-loop shred produced.","type":["string","null"]},"error":{"type":"string"},"created_at":{"type":"string"},"completed_at":{"type":["string","null"]},"constraints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"verification":{"type":"object","properties":{"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["shape","length","person","jury","entities","scaffolding","claims","held","attachments","title"]},"verdict":{"type":"string","enum":["pass","fail","skipped"],"description":"`skipped` = nothing to read back; never a pass."},"detail":{"type":"string"}},"required":["id","verdict"]}},"at":{"type":"string"},"service":{"description":"The kernels service's own verification record over the shipped text, verbatim — the trail: which claims the article used, which sentences the gate could not tie to the input, which held blocks came back byte-identical, which citations and links carried. Null when the service sent none.","anyOf":[{"type":"object","properties":{"claims":{"type":"object","properties":{"mode":{"description":"Who decided which claims the text states; read it first. \"index\" (kernel runs from 2026-10-01): assembly tied each shipped sentence to the claims its own figures, dates and identifiers carry; no claims gate, so `mandatory_dropped`, `dropped_attached` and `sentences` are absent and `index` is set. Absent: a gate-era record (stored runs, the standalone shred).","type":"string"},"total":{"description":"Claims extracted from the input.","type":"number"},"used":{"description":"Claim ids the shipped text states (index ties, or the gate-era writer's markers).","type":"array","items":{"type":"string"}},"unused":{"type":"array","items":{"type":"string"}},"index":{"description":"mode \"index\" only: what the index placed and could not place.","type":"object","properties":{"placed":{"description":"Sentences tied to the claims that carry every figure, date and identifier they state.","type":"number"},"unplaced":{"description":"Sentences stating a figure, date or identifier no claim carries exactly: shipped, with no link.","type":"number"},"attached_unplaced":{"description":"Claims carrying a citation or a link that no shipped sentence was tied to: that attachment did not ship. Should be empty.","type":"array","items":{"type":"string"}}},"additionalProperties":{}},"mandatory_dropped":{"description":"GATE-ERA (mode absent): cited or linked claims the gate dropped as a last resort. Absent on index runs, where it is unknown rather than empty.","type":"array","items":{"type":"string"}},"dropped_attached":{"description":"GATE-ERA: the subset of mandatory_dropped that carried a citation or a link.","type":"array","items":{"type":"string"}},"mandatory_said":{"description":"GATE-ERA: the subset of mandatory_dropped the shipped text states anyway, under another claim's marker (gate runs from 2026-10-02). Absent on index runs and on runs before 2026-10-02.","type":"array","items":{"type":"string"}}},"additionalProperties":{}},"sentences":{"description":"GATE-ERA: the claims gate's sentence verdicts. Absent on index runs (claims.mode \"index\"), whose sentences no gate judged; absent is unknown, not zero.","type":"object","properties":{"supported":{"type":"number"},"unreferenced":{"description":"Shipped sentences tied to no claim — invention.","type":"number"},"contradicted":{"description":"Shipped sentences that contradict the claim they cite.","type":"number"},"dropped":{"type":"number"},"methods":{"type":"object","properties":{"tokens":{"type":"number"},"entailment":{"type":"number"}},"additionalProperties":{}}},"additionalProperties":{}},"held":{"type":"object","properties":{"blocks":{"description":"Tables, images, code, quotes, references held from the input.","type":"number"},"restored":{"type":"number"},"misplaced":{"type":"number"},"identical":{"description":"Restored byte for byte.","type":"number"},"detail":{"description":"Per-block verdicts.","type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":{}},"attachments":{"type":"object","properties":{"citations":{"type":"object","properties":{"expected":{"description":"How many the input carried.","type":"number"},"carried":{"description":"How many the article carries.","type":"number"}},"additionalProperties":{}},"links":{"type":"object","properties":{"expected":{"description":"How many the input carried.","type":"number"},"carried":{"description":"How many the article carries.","type":"number"}},"additionalProperties":{}}},"additionalProperties":{}},"shape":{"type":"object","properties":{"headings_expected":{"type":"number"},"headings_held":{"description":"Headings present at their position and level.","type":"number"},"headings_reworded":{"description":"Of the held headings, how many came back with different wording.","type":"number"},"title_held":{"description":"The input's title came back as the article's title, in its own form.","type":"boolean"}},"additionalProperties":{}},"length":{"type":"object","properties":{"prose_words":{"description":"Regenerated prose only; held blocks excluded.","type":"number"},"target":{"type":"number"},"delta":{"type":"number"}},"additionalProperties":{}},"repairs":{"description":"Every sentence-level repair the loop attempted, and whether the gate accepted it.","type":"array","items":{"type":"object","properties":{"pass":{"type":"string"},"before":{"type":"string"},"after":{"type":"string"},"accepted":{"type":"boolean"},"reason":{"type":"string"}},"additionalProperties":{}}},"content":{"description":"The door's reading of the INPUT, not the article: whether it carried enough named things, figures and cases to write something particular at the requested length, and what to add per section when it did not. Advisory: the run was written either way.","type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}}},"additionalProperties":{}},{"type":"null"}]}},"required":["checks","at"]},"content":{"description":"The door's reading of the INPUT: `sufficient` false when it carries too few named things, figures and cases for the requested length (the article will stay general: the writer cannot add particulars you did not give it), with `thin`, what to add per section. Advisory: the run proceeds. Null when the service sent none.","anyOf":[{"type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}},{"type":"null"}]},"rule_gaps":{"description":"Where the live rules did not hold on the shipped text: residual instances by rule, how many the shred put back, and (since 2026-09-29) why each shipped. Present once done; null on a run settled before it was stored.","anyOf":[{"type":"object","properties":{"shippedRound":{"type":["number","null"]},"hadPreShredText":{"type":"boolean"},"totalInstances":{"type":"number","description":"Live-rule pattern instances in the text that shipped."},"totalShredRaised":{"type":"number","description":"…of which the shred put back after the rule had cleaned them."},"rulesClean":{"type":"number"},"causes":{"description":"Hits by cause across every rule: skipped_by_budget, not_live, repair_still_violates, repair_refused_house_guard, repair_refused_contract, shred_raised, reintroduced_by_<pass>, written_by_revision, undetected_at_run, unrepaired, introduced_unattributed. Absent on a run read before the service attributed causes.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"residual":{"type":"array","items":{"type":"object","properties":{"ruleId":{"type":"string"},"name":{"type":"string"},"detectorKind":{"type":"string"},"instances":{"type":"number"},"shredRaised":{"type":"number"},"sentences":{"type":"array","items":{"type":"string"},"description":"The first few offending sentences."},"causes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"attributed":{"type":"array","items":{"type":"object","properties":{"sentence":{"type":"string"},"cause":{"type":"string"},"reason":{"type":"string"}},"required":["sentence","cause"]}}},"required":["ruleId","name","detectorKind","instances","shredRaised","sentences"]}}},"required":["shippedRound","hadPreShredText","totalInstances","totalShredRaised","rulesClean","residual"]},{"type":"null"}]},"build":{"description":"The kernels build that wrote the run (the first 7 of its git SHA). Compare with get_kernel_pipeline's build. Present once done.","type":["string","null"]},"config":{"description":"The effective pipeline config the run executed under, as the service echoed it. Present once done.","anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["run_id","status","kernel","input","created_at","completed_at","constraints"]}},{"name":"get_kernel_endpoint_run_steps","description":"The round-by-round draft text for a run started with run_kernel — every prompt step's input and output, per iteration. The same trace the app's kernel detail page shows as its version-diff and rule-trip views. Use it to see WHY a run's article turned out the way it did, beyond get_kernel_endpoint_run's pass/fail-adjacent verification record. Empty on a run that isn't finished yet. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","minLength":1,"description":"The run id returned by run_kernel."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"iterations":{"type":"array","items":{"type":"object","properties":{"iteration":{"type":"number"},"steps":{"type":"array","items":{"type":"object","properties":{"prompt_id":{"type":"string"},"prompt_name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"skipped":{"type":"boolean"},"change_percent":{"type":"number"},"retries":{"type":"number"},"input_text":{"type":"string"},"output_text":{"type":"string"}},"required":["prompt_id","prompt_name","type","status","skipped","change_percent","retries","input_text","output_text"]}}},"required":["iteration","steps"]},"description":"Round-by-round draft text for a finished run — the same trace the app's kernel detail page renders as its version-diff view. Empty on a run that isn't finished yet, or that finished with nothing to show."}},"required":["iterations"]}},{"name":"get_kernel_endpoint_run_record","description":"The round-by-round record of a run started with run_kernel, for reading WHY the article came out as it did: for every round, what the writer did (retries, a discarded draft), the claims gate (runs before 2026-10-01; null on index runs, whose claims assembly placed: `assembly.claims_mode` and `assembly.claims_index`), each system rule and data-driven rule (found, rewritten, refused, still in the text, skipped by the budget), the shred, its send-backs by rule and the check after it, the rescan, provenance and the quality score, plus the text at each stage (the draft, after the gates, after the shred); then where the live rules did not hold on the shipped text and why (`gaps`), what assembly did (held blocks, links, stray markers, invented figures), the cost, the rules applied at their versions beside the catalog now, and the build that wrote it. Large (the texts of every round). Read get_kernel_endpoint_run for the article and the verification record; read this to see the pipeline's work. `unread_round_keys` names round fields this read does not project yet. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","minLength":1,"description":"The run id returned by run_kernel."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string"},"status":{"type":"string"},"passed":{"type":["boolean","null"]},"error":{"type":["string","null"]},"kernel":{"type":"object","properties":{"id":{"type":["string","null"]},"version":{"type":["number","null"]}},"required":["id","version"]},"build":{"description":"The kernels build that wrote the run; compare with get_kernel_pipeline's build.","type":["string","null"]},"seats":{"anyOf":[{"type":"object","properties":{"writer":{"type":["string","null"]},"repair":{"description":"Every in-place restatement's model; the writer unless the config split them.","type":["string","null"]},"judge":{"description":"The quality panel's model; never follows the writer.","type":["string","null"]},"decompose":{"type":["string","null"]},"content":{"type":["string","null"]}},"required":["writer","repair","judge","decompose","content"]},{"type":"null"}],"description":"The model each seat ran on (since 2026-10-02): who wrote, who repaired, who judged. Null on older runs."},"writer_profile":{"description":"The writer profile a run drafted under, by id: runs between 2026-10-02 and the kernel file (which retired profiles) only; null otherwise.","type":["string","null"]},"config":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"The effective pipeline config the run executed under."},"rules":{"anyOf":[{"type":"object","properties":{"applied":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"}},"required":["id","version"]},"description":"What ran, at the version that ran."},"catalog":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"description":"The same rules as they stand now."}},"required":["applied","catalog"]},{"type":"null"}]},"gaps":{"anyOf":[{"type":"object","properties":{"shippedRound":{"type":["number","null"]},"hadPreShredText":{"type":"boolean"},"totalInstances":{"type":"number","description":"Live-rule pattern instances in the text that shipped."},"totalShredRaised":{"type":"number","description":"…of which the shred put back after the rule had cleaned them."},"rulesClean":{"type":"number"},"causes":{"description":"Hits by cause across every rule: skipped_by_budget, not_live, repair_still_violates, repair_refused_house_guard, repair_refused_contract, shred_raised, reintroduced_by_<pass>, written_by_revision, undetected_at_run, unrepaired, introduced_unattributed. Absent on a run read before the service attributed causes.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"residual":{"type":"array","items":{"type":"object","properties":{"ruleId":{"type":"string"},"name":{"type":"string"},"detectorKind":{"type":"string"},"instances":{"type":"number"},"shredRaised":{"type":"number"},"sentences":{"type":"array","items":{"type":"string"},"description":"The first few offending sentences."},"causes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"attributed":{"type":"array","items":{"type":"object","properties":{"sentence":{"type":"string"},"cause":{"type":"string"},"reason":{"type":"string"}},"required":["sentence","cause"]}}},"required":["ruleId","name","detectorKind","instances","shredRaised","sentences"]}}},"required":["shippedRound","hadPreShredText","totalInstances","totalShredRaised","rulesClean","residual"]},{"type":"null"}]},"shipped_round":{"type":["number","null"]},"rounds":{"type":"array","items":{"type":"object","properties":{"iteration":{"type":"number"},"shipped":{"type":"boolean"},"write":{"anyOf":[{"type":"object","properties":{"retries":{"type":["number","null"]},"skipped":{"type":"boolean","description":"The draft was thrown away for breaking the required sections."},"shape_drift":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"rarity":{"anyOf":[{"type":"object","properties":{"retries":{"type":["number","null"]},"passed":{"type":["boolean","null"]},"attempts":{"type":"number"}},"required":["retries","passed","attempts"]},{"type":"null"}]}},"required":["retries","skipped","shape_drift","rarity"]},{"type":"null"}]},"claims":{"anyOf":[{"type":"object","properties":{"supported":{"type":["number","null"]},"unreferenced":{"type":["number","null"]},"contradicted":{"type":["number","null"]},"dropped":{"type":["number","null"]},"attributed":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"restated":{"type":["number","null"]},"mandatory_dropped":{"type":"array","items":{"type":"string"}}},"required":["supported","unreferenced","contradicted","dropped","attributed","rewritten","restated","mandatory_dropped"]},{"type":"null"}],"description":"GATE-ERA: the claims gate's work this round, on runs stored before 2026-10-01. Null on index runs (assembly.claims_mode \"index\"): the gate no longer exists, and no sentence verdicts are recorded."},"length":{"anyOf":[{"type":"object","properties":{"before":{"type":["number","null"]},"after":{"type":["number","null"]},"target":{"type":["number","null"]},"action":{"type":["string","null"]},"accepted":{"type":["boolean","null"]}},"required":["before","after","target","action","accepted"]},{"type":"null"}]},"entities":{"anyOf":[{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]}},"required":["found","rewritten","dropped","refused","retries","passed"]},{"type":"null"}]},"person":{"anyOf":[{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]},"person":{"type":["string","null"]}},"required":["found","rewritten","dropped","refused","retries","passed","person"]},{"type":"null"}]},"punctuation":{"anyOf":[{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]}},"required":["found","rewritten","dropped","refused","retries","passed"]},{"type":"null"}]},"framing":{"anyOf":[{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]}},"required":["found","rewritten","dropped","refused","retries","passed"]},{"type":"null"}]},"rules":{"type":"array","items":{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]},"rule_id":{"type":"string"},"version":{"type":["number","null"]},"skipped_by_budget":{"type":"boolean","description":"The rule never ran this round: the article's rewrite budget was spent before its turn."}},"required":["found","rewritten","dropped","refused","retries","passed","rule_id","version","skipped_by_budget"]}},"figures":{"anyOf":[{"type":"object","properties":{"found":{"type":["number","null"]},"rewritten":{"type":["number","null"]},"dropped":{"type":["number","null"]},"refused":{"description":"Restatements refused for introducing another enforced rule's pattern.","type":["number","null"]},"retries":{"type":["number","null"]},"passed":{"description":"False when the check left something in the text that shipped.","type":["boolean","null"]}},"required":["found","rewritten","dropped","refused","retries","passed"]},{"type":"null"}]},"shred":{"anyOf":[{"type":"object","properties":{"total_units":{"type":["number","null"]},"shredded":{"type":["number","null"]},"kept_original":{"type":["number","null"]},"kept_share":{"type":["number","null"]},"sentences":{"anyOf":[{"type":"object","properties":{"total":{"type":["number","null"]},"rewritten":{"type":["number","null"]}},"required":["total","rewritten"]},{"type":"null"}],"description":"Sentences, not units: how many the round had and how many the shred rewrote (since 2026-09-29)."},"send_backs":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}],"description":"Why the rule guard sent a shred candidate back, by what it named (`rule <id>`, `framing`): the shred's gateFires.forbiddenBy, on the record since 2026-09-30 (phase 5a)."},"guard":{"anyOf":[{"type":"object","properties":{"applied":{"type":"boolean"},"rules":{"type":"number"}},"required":["applied","rules"]},{"type":"null"}]}},"required":["total_units","shredded","kept_original","kept_share","sentences","send_backs","guard"]},{"type":"null"}]},"verify":{"anyOf":[{"type":"object","properties":{"reverted":{"type":["number","null"]},"attributed":{"type":["number","null"]},"unresolved":{"type":["number","null"]}},"required":["reverted","attributed","unresolved"]},{"type":"null"}]},"rescan":{"anyOf":[{"type":"object","properties":{"total":{"type":["number","null"]},"by_pass":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"by_detector":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"findings":{"type":"array","items":{"type":"object","properties":{"pass":{"type":"string","description":"The pass whose accepted edit wrote the sentence; `writer` when none did."},"detector":{"type":"string","description":"A rule id, or em-dash / person / framing / leak."},"rule_id":{"type":["string","null"]},"sentence":{"type":"string"}},"required":["pass","detector","rule_id","sentence"]},"description":"Every hit with its sentence; the service keeps the first few hundred, the counts are complete."}},"required":["total","by_pass","by_detector","findings"]},{"type":"null"}],"description":"The round's rescan (since 2026-09-30): one deterministic read of the text the judge is about to see, with every enforced rule, the system scans and the leak check. Record only; by_pass says which pass wrote each finding's sentence, by_detector which habit it is."},"provenance":{"anyOf":[{"type":"object","properties":{"p_machine":{"type":["number","null"]}},"required":["p_machine"]},{"type":"null"}]},"quality":{"anyOf":[{"type":"object","properties":{"total":{"type":["number","null"]},"evaluated":{"type":["boolean","null"]},"scores":{"type":"array","items":{"type":"object","properties":{"editor":{"type":"string"},"total":{"type":["number","null"]},"verdict":{"description":"print, rewrite or send back.","type":["string","null"]},"axes":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"evidence":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What the editor cited, per axis."}},"required":["editor","total","verdict","axes","evidence"]},"description":"The judge's panel, editor by editor."}},"required":["total","evaluated","scores"]},{"type":"null"}]},"repairs":{"type":"array","items":{"type":"object","properties":{"pass":{"type":"string","description":"person, framing, entities, figures; `rule` with rule_id; `claims` for the weave's restatements; `shred`."},"rule_id":{"type":["string","null"]},"before":{"type":"string"},"after":{"description":"What the pass proposed; null when the sentence was removed.","type":["string","null"]},"accepted":{"type":"boolean","description":"Whether `after` went into the text."},"reason":{"description":"Why a refused or dropped attempt did not go in.","type":["string","null"]},"contract":{"description":"The repair contract's verdict: ok, the failed condition, or dropped.","type":["string","null"]}},"required":["pass","rule_id","before","after","accepted","reason","contract"]},"description":"Every repair attempted this round, in order: Before, After, whether it went in, and why not."},"texts":{"type":"object","properties":{"draft":{"description":"The writer's draft, before any pass.","type":["string","null"]},"after_gates":{"description":"After the entities, person and framing gates (since 2026-10-02); null when they changed nothing.","type":["string","null"]},"after_rules":{"description":"After the rules pass (since 2026-10-02); null when it changed nothing.","type":["string","null"]},"gated":{"description":"After every pass of the round; before the shred when one ran.","type":["string","null"]},"shredded":{"description":"After the shred; null when the round did not shred.","type":["string","null"]}},"required":["draft","after_gates","after_rules","gated","shredded"]}},"required":["iteration","shipped","write","claims","length","entities","person","punctuation","framing","rules","figures","shred","verify","rescan","provenance","quality","repairs","texts"]}},"assembly":{"type":"object","properties":{"attachments":{"type":["number","null"]},"held":{"anyOf":[{"type":"object","properties":{"blocks":{"type":["number","null"]},"restored":{"type":["number","null"]},"inserted":{"type":["number","null"]},"misplaced":{"description":"Restored at the wrong place (since 2026-09-30).","type":["number","null"]},"identical":{"description":"Restored byte for byte.","type":["number","null"]}},"required":["blocks","restored","inserted","misplaced","identical"]},{"type":"null"}]},"leaked_markers":{"type":["number","null"]},"invented_links":{"type":["number","null"]},"invented_figures":{"type":"array","items":{"type":"string"}},"invented_entities":{"type":"array","items":{"type":"string"}},"claims_mode":{"description":"Who decided which claims the shipped text states. index: runs from 2026-10-01, assembly tied each shipped sentence to the claims its own figures, dates and identifiers carry (no writer markers, no claims gate). gate: a stored run whose claims gate judged the sentences. Read this before any claims field.","anyOf":[{"type":"string","enum":["index","gate","unknown"]},{"type":"null"}]},"claims_index":{"description":"Index runs only; null on gate-era runs.","anyOf":[{"type":"object","properties":{"placed":{"description":"Sentences tied to the claims that carry every fact they state.","type":["number","null"]},"unplaced":{"description":"Sentences stating a figure, date or identifier no claim carries exactly: shipped, no link.","type":["number","null"]},"attached_unplaced":{"type":"array","items":{"type":"string"},"description":"Claims carrying a citation or link no shipped sentence was tied to: that attachment did not ship."}},"required":["placed","unplaced","attached_unplaced"]},{"type":"null"}]},"mandatory_dropped":{"description":"GATE-ERA: attached claims the writer never cited. Null on index runs (unknown there, not empty); read claims_index.attached_unplaced instead.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"mandatory_said":{"description":"GATE-ERA: the subset of mandatory_dropped the shipped text states anyway, under another claim's marker; what the article lacks is the rest. Recorded on gate runs from 2026-10-02; null on index runs (unknown there) and on runs before 2026-10-02.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"claims_used":{"type":["number","null"]}},"required":["attachments","held","leaked_markers","invented_links","invented_figures","invented_entities","claims_used"]},"usage":{"anyOf":[{"type":"object","properties":{"calls":{"type":["number","null"]},"input_tokens":{"type":["number","null"]},"output_tokens":{"type":["number","null"]},"by_stage":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["calls","input_tokens","output_tokens","by_stage"]},{"type":"null"}]},"verification":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"unread_round_keys":{"type":"array","items":{"type":"string"},"description":"Round fields this projection does not read yet: a stage the service added since."}},"required":["run_id","status","passed","error","kernel","build","seats","writer_profile","config","rules","gaps","shipped_round","rounds","assembly","usage","verification","unread_round_keys"]}},{"name":"list_kernel_rules","description":"The live rules every kernel run is held to, as the kernels service publishes them right now: every active and paused rule with its id, name, version, status, scope, detector kind and shred policy. A run's `gaps` and record name rules by these ids; a rule at a higher version here than on the run means the rule changed since the run. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"number"},"status":{"type":"string"},"scope":{"type":"string"},"detectorKind":{"type":"string"},"shredPolicy":{"type":"string"},"regimes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","name"]}}},"required":["rules"]}},{"name":"get_kernel_pipeline","description":"The writing pipeline as the deployed kernels service runs it: every stage in order (the door, each round, once on the shipped round, and the app's two steps outside the kernel), what each does, when it runs, whether it writes and how far one act of it reaches (the whole article, a sentence, a shred unit, nothing), where it lives in the kernels code, and the build that serves the answer. Read from the service's own registry, which its tests hold to the code; compare `build` with a run's `build` to know whether that run was written by the pipeline described here. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"source":{"type":"string","enum":["service","app"],"description":"service: the deployed build's own registry. app: the service did not publish one; only the app's steps are listed."},"build":{"type":["string","null"]},"stages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"phase":{"type":"string","description":"door (once, before round 1) | round (every round, in order) | once (after the last round) | outside (the app's own steps around the kernel)."},"label":{"type":"string"},"what":{"type":"string"},"when":{"type":["string","null"]},"writes":{"type":"boolean"},"scope":{"description":"How far one act reaches when it writes: none | article | sentence | unit | assembly.","type":["string","null"]},"where":{"type":"string"}},"required":["id","phase","label","what","when","writes","scope","where"]}}},"required":["source","build","stages"]}},{"name":"get_kernel_writers","description":"Every writing model the kernels service accepts and what a run on it would be: the seats it fills (writer, repair, judge, decompose, content — the judge, decompose and content models are the platform's and never follow the writer), whether the service can call it, and which of your kernels (and the catalog's) name it as their `writer`. The model providers beyond the gateway and whether each one's key is set. Use before switching a kernel's writer, and to read which models are under test. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"source":{"type":"string","enum":["service","app"],"description":"service: every read answered; app: the service's model list could not be read."},"production":{"type":"object","properties":{"fleet_writer":{"description":"The writer the catalog's kernels name: what every entry way writes with unless a kernel says otherwise.","type":["string","null"]},"kernels_on_fleet_writer":{"type":"number"},"live_kernels":{"type":"number"},"kernels_on_other":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"model":{"type":"string"},"org_id":{"type":["string","null"]}},"required":["id","name","model","org_id"]}}},"required":["fleet_writer","kernels_on_fleet_writer","live_kernels","kernels_on_other"],"description":"Who writes in production now."},"seats":{"anyOf":[{"type":"object","properties":{"judge":{"type":"string"},"decompose":{"type":"string"},"content":{"type":"string"}},"required":["judge","decompose","content"]},{"type":"null"}],"description":"The seats' defaults on the service: the models a config leaves unsaid run on."},"providers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"baseUrl":{"type":"string"},"api":{"type":"string"},"keyEnv":{"type":"string"},"configured":{"type":"boolean","description":"Whether the service holds the provider's key; never the key."},"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}},"required":["id","label","roles"]}},"family":{"type":"string"},"docs":{"type":["string","null"]}},"required":["id","label","baseUrl","api","keyEnv","configured","models","family","docs"]}},"provider_errors":{"type":"array","items":{"type":"string"}},"writers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"provider":{"type":"string"},"available":{"description":"A provider's key presence; null for the gateway's models.","type":["boolean","null"]},"seats":{"type":"object","properties":{"writer":{"type":"string"},"repair":{"type":"string","description":"The writer, unless a kernel names its own repair writer (advanced.repairWriter)."},"judge":{"description":"The quality panel's model; never follows the writer.","type":["string","null"]},"decompose":{"type":["string","null"]},"content":{"type":["string","null"]}},"required":["writer","repair","judge","decompose","content"]},"kernels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"version":{"type":"number"},"org_id":{"description":"null = the catalog.","type":["string","null"]},"repair_model":{"type":["string","null"]}},"required":["id","name","version","org_id","repair_model"]}},"trials":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"session_id":{"type":"string"},"session_title":{"type":"string"},"repair_model":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["id","name","session_id","session_title","repair_model","updated_at"]}}},"required":["id","label","provider","available","seats","kernels","trials"]}}},"required":["source","production","seats","providers","provider_errors","writers"]}},{"name":"get_kernel_file","description":"A kernel's version as its kernel file: one complete JSON document saying how it writes — `writer` (the model), `voice` (text or named paragraphs), `register`/`humor`/`inquisitive`/`person`, `rules` (every check as id → repair, measure or off, the system checks among them: system-rarity with its thresholds, system-person, system-framing, system-figures, system-entities), `effort` (rounds, samples, floor) and `advanced`. The length and the shred are per run (your endpoint's defaults or the request), and the judge is the platform's, so they are not in it. The live version by default; `version` reads another one of your own kernel's (list_kernel_versions lists them). For the catalog's kernels and your own. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A kernel id from list_kernels: the catalog's, or one your org owns."},"version":{"description":"One stored version (your own kernels only); omitted, the live one.","type":"integer","minimum":1,"maximum":9007199254740991}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel_id":{"type":"string"},"version":{"type":"number","description":"The version read: the live one unless you named another."},"status":{"type":"string","enum":["live","candidate","retired"],"description":"live: what the kernel's runs use; candidate: runs only when named; retired: history."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The kernel file (v2): name, description, writer, voice (text or named paragraphs), register, humor, inquisitive, person, rules (id → repair | measure | off; system-rarity with settings), effort (rounds, samples, floor 0–9), advanced."}},"required":["kernel_id","version","status","file"]}},{"name":"get_kernel_offer","description":"For a kernel your org copied from the catalog: whether the catalog kernel has a newer live version than the one your copy was made from, which fields of its file changed, and your kernel file with those changes applied over your own edits. Nothing changes your kernel until you accept (accept_kernel_offer). `offer: null` when there is nothing new. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Your org's kernel (a copy of a catalog kernel), from list_kernels."}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel_id":{"type":"string"},"offer":{"anyOf":[{"type":"object","properties":{"base_version_from":{"type":"number","description":"The catalog version your copy carries."},"base_version_to":{"type":"number","description":"The catalog's live version now."},"changes":{"type":"array","items":{"type":"string"},"description":"The fields of the catalog's file that changed between the two (rules.plainly-tic, writer, …)."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Your kernel file with those changes applied over your own edits: what accepting saves."}},"required":["base_version_from","base_version_to","changes","file"]},{"type":"null"}]}},"required":["kernel_id","offer"]}},{"name":"accept_kernel_offer","description":"Accept the catalog's newer version into a kernel your org copied (get_kernel_offer shows it first): the catalog's changes are applied over your own edits and saved as a new version of your kernel. `status: candidate` (the default) keeps it out of your runs until you make it live (promote_kernel_version); `live` makes your endpoints run it from their next call. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"status":{"description":"candidate (when omitted): save it as a version that runs only when named, to compare first; live: your endpoints run it from their next call.","type":"string","enum":["candidate","live"]},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Your org's kernel (a copy of a catalog kernel) with an offer waiting."}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel_id":{"type":"string"},"version":{"type":"number"},"status":{"type":"string","enum":["candidate","live"]},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The kernel file (v2): name, description, writer, voice (text or named paragraphs), register, humor, inquisitive, person, rules (id → repair | measure | off; system-rarity with settings), effort (rounds, samples, floor 0–9), advanced."},"voice_settings_detached":{"type":"boolean","description":"True when this made a new voice text live, so the kernel's structured voice settings (update_kernel voice_config) no longer apply: it is edited as a file from now on."}},"required":["kernel_id","version","status","file","voice_settings_detached"]}},{"name":"list_kernel_versions","description":"Every version of one of your org's kernels, newest first, each with its status (live: what runs use; candidate: runs only when named; retired: history) and the fields it changed; the live file; the catalog kernel it was copied from, with where your live file differs from it; and any newer catalog version waiting as an offer. The kernel page's file panel, in one read. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Your org's kernel id, from list_kernels (owned: true)."}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel_id":{"type":"string"},"live":{"type":"object","properties":{"version":{"type":"number"},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The kernel file (v2): name, description, writer, voice (text or named paragraphs), register, humor, inquisitive, person, rules (id → repair | measure | off; system-rarity with settings), effort (rounds, samples, floor 0–9), advanced."}},"required":["version","file"],"description":"The version the kernel's runs use."},"versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number"},"status":{"type":"string","enum":["live","candidate","retired"]},"writer":{"type":"string","description":"The model that writes in this version."},"note":{"type":["string","null"]},"created_by":{"description":"user:<id> or api-key:<id> for your saves; staff or the service otherwise.","type":["string","null"]},"created_at":{"type":["string","null"]},"translated":{"type":"boolean","description":"Saved before the file format existed; read through the service's translator."},"changes":{"type":"array","items":{"type":"string"},"description":"Fields that differ from the version saved before it."}},"required":["version","status","writer","note","created_by","created_at","translated","changes"]},"description":"Every version, newest first."},"base":{"anyOf":[{"type":"object","properties":{"kernel_id":{"type":"string","description":"The catalog kernel this one was copied from."},"name":{"type":"string"},"version":{"type":"number","description":"Its live version now."},"changes":{"type":"array","items":{"type":"string"},"description":"Where your live file differs from it (name and description aside)."}},"required":["kernel_id","name","version","changes"]},{"type":"null"}],"description":"Null for a kernel made from your own file."},"offer":{"anyOf":[{"type":"object","properties":{"base_version_from":{"type":"number"},"base_version_to":{"type":"number"},"changes":{"type":"array","items":{"type":"string"}}},"required":["base_version_from","base_version_to","changes"]},{"type":"null"}],"description":"The base's newer version, waiting for accept_kernel_offer. Null when there is none."},"edited_as_file":{"type":"boolean","description":"True when the voice is edited as text rather than through update_kernel's voice_config."}},"required":["kernel_id","live","versions","base","offer","edited_as_file"]}},{"name":"save_kernel_file","description":"Save a kernel file as a new version of one of your org's kernels. Send the whole file you want (get_kernel_file, edit, send back) — only `name` and `voice` are required, and anything left out is completed with the platform's settings. `status: candidate` (the default) changes no run: try it with run_kernel's `kernel_version` (or start_kernel_studio_run), then promote_kernel_version. `live` makes your kernel's runs use it from their next call. A refused file names each failing path. Your own rules in `rules` are filed for review, never auto-activated. Every version is kept, so any change can be undone by promoting an earlier one. The schema: get_kernel_file_schema. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A kernel file (v2): one JSON object. Only `name` and `voice` are required; everything else (writer, register, humor, inquisitive, person, rules, effort, advanced) is completed with the platform's settings and handed back whole. The schema: get_kernel_file_schema."},"status":{"description":"candidate (when omitted): a new version that runs only when named (test it with kernel_version), so nothing changes yet. live: your kernel's runs use it from their next call.","type":"string","enum":["candidate","live"]},"note":{"description":"What changed and why, shown in the version history.","type":"string","maxLength":500},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Your org's kernel id, from list_kernels (owned: true)."}},"required":["file","kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kernel_id":{"type":"string"},"version":{"type":"number","description":"The new version."},"status":{"type":"string","enum":["candidate","live"]},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The file as stored: every field written out."},"custom_evals":{"description":"How the file's own rules were filed for review; present only when it carried any.","type":"object","properties":{"filed":{"type":"array","items":{"type":"string"},"description":"Newly proposed this call — filed at status 'proposed', not yet active."},"skipped":{"type":"array","items":{"type":"string"},"description":"Already exist as a rule (any status) from an earlier submission — left untouched, not re-filed."},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"]},"description":"Filing failed for this entry."}},"required":["filed","skipped","errors"]},"voice_settings_detached":{"type":"boolean","description":"True when this made a new voice text live, so the kernel's structured voice settings (update_kernel voice_config) no longer apply: it is edited as a file from now on."}},"required":["ok","kernel_id","version","status","file","voice_settings_detached"]}},{"name":"promote_kernel_version","description":"Make a stored version of one of your org's kernels live: ship a candidate you tested, or roll back to an earlier version. The version that was live is kept and returned as previous_live, so this is undone by promoting it again. Your kernel's runs use the new live version from their next call. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Your org's kernel id, from list_kernels (owned: true)."},"version":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"The version to make live: a candidate to ship, or an older version to roll back to."}},"required":["kernel_id","version"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kernel_id":{"type":"string"},"version":{"type":"number","description":"The version now live."},"previous_live":{"description":"The version that was live; promote it to undo this.","type":["number","null"]},"voice_settings_detached":{"type":"boolean","description":"True when this made a new voice text live, so the kernel's structured voice settings (update_kernel voice_config) no longer apply: it is edited as a file from now on."}},"required":["ok","kernel_id","version","previous_live","voice_settings_detached"]}},{"name":"create_kernel","description":"Create a kernel your org owns, stored as version 1 (live) and handed back whole; nothing of ours changes under it later. Three starts, one call. From a file: as short as `{ \"file\": { \"name\": \"Acme\", \"voice\": \"Write like our CTO talks: plain, specific, a little dry.\" } }` — the rest is completed with the platform's settings. From a kernel you can see: `{ \"from\": \"<kernel id or name>\", \"file\": { \"name\": \"Acme v2\" } }` copies its file (your org's current kernel, from get_org_writing_kernel, gives a new kernel that starts as your regular settings; any other is a fork that stays yours), with `file` laid over it. From your own skill: `{ \"skill\": \"<the text of a SKILL.md or any markdown / plain-text writing instructions>\" }` — its body becomes the kernel's `voice`, its frontmatter `name` and `description` name it, and everything else comes from `from` or else your org's current kernel. To copy a catalog kernel that keeps receiving our newer versions as offers, use configure_kernel. The schema: get_kernel_file_schema. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"file":{"description":"A kernel file (v2): one JSON object. On its own, only `name` and `voice` are required and everything else is completed with the platform's settings. With `from` or `skill` it is laid over that start, field by field — `{ \"name\": \"Acme\" }` just names the new kernel. The schema: get_kernel_file_schema.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"from":{"description":"Start from a kernel your org can see — the catalog's or your own, by id or name (list_kernels): its file (at the version it runs) minus its name and description, with `file` over it. Your org's current kernel (get_org_writing_kernel) makes a new kernel that starts as your regular settings; any other is a fork that stays yours (no offers arrive — configure_kernel makes a copy that keeps receiving them). Unnamed, the kernel is called \"<source> (copy)\".","type":"string","minLength":1,"maxLength":200},"skill":{"description":"Your own skill as a kernel: the text of a SKILL.md (YAML frontmatter with `name` and `description`, then markdown instructions) or any markdown / plain-text writing instructions. The body becomes the kernel's `voice`, verbatim (up to 20,000 characters); the name comes from `file.name`, else the frontmatter, else the first heading, else `skill_filename`; the description from the frontmatter. Everything else comes from `from`, or else your org's current kernel, so it starts as a working copy of your regular settings.","type":"string","minLength":1,"maxLength":40000},"skill_filename":{"description":"The uploaded skill's file name; names the kernel when the skill has no name or heading.","type":"string","maxLength":255}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kernel":{"type":"object","properties":{"id":{"type":"string","description":"Your new kernel's id: pass it as kernel_id anywhere."},"name":{"type":"string"},"description":{"type":["string","null"]},"service_kernel_id":{"type":"string"}},"required":["id","name","description","service_kernel_id"]},"version":{"type":"number"},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The file as stored: every field written out."},"based_on":{"description":"The kernel (and its version) whose file this one started from: `from`, or your current kernel under a skill.","anyOf":[{"type":"object","properties":{"kernel_id":{"type":"string"},"name":{"type":"string"},"version":{"type":"number"}},"required":["kernel_id","name","version"]},{"type":"null"}]},"skill_ignored":{"description":"The skill's frontmatter keys with no place in a kernel file (allowed-tools, license, …), left out.","type":"array","items":{"type":"string"}}},"required":["ok","kernel","version","file"]}},{"name":"get_kernel_file_schema","description":"The kernel file's JSON Schema, exactly as the kernels service validates it: every field, its type, its range and what it means. Read it before writing a file for save_kernel_file or create_kernel. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The kernel file's JSON Schema, derived on the kernels service from the schema it validates with."}},"required":["schema"]}},{"name":"check_prose","description":"Check one article for the prose defects the tuning comparisons and the live scoreboard read (lib/evals/prose-shape.ts): brief text that leaked into it (SOURCE PAGES and the other reference blocks), heading layout (a title straight into a heading, empty sections, prose above the title), too many short paragraphs or too-long ones, a long opening, and semicolon or dash joins. Returns the counts, which limits the article is past, and the limits. Deterministic and free: no model reads the text. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":400000,"description":"The article: a kernel's markdown (with its `# ` title) or a post's HTML."},"format":{"description":"How to read `text`; HTML when it starts with a tag, else markdown, when omitted.","type":"string","enum":["markdown","html"]}},"required":["text"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"format":{"type":"string","enum":["markdown","html"]},"shape":{"type":"object","properties":{"leaked":{"type":"array","items":{"type":"string"},"description":"Brief text in the article, verbatim: reference headings, source-page lines, held markers."},"stackedHeadings":{"type":"array","items":{"type":"string"},"description":"Empty sections: a heading straight into another at its own level."},"opensOnHeading":{"type":"boolean","description":"The `# ` title runs straight into a section heading."},"proseAboveTitle":{"type":"boolean"},"openingWords":{"description":"Prose words before the first section heading; null with no sections.","type":["number","null"]},"paragraphs":{"type":"number"},"sentencesPerParagraph":{"type":["number","null"]},"shortParagraphShare":{"description":"Percent of paragraphs of one or two sentences.","type":["number","null"]},"longParagraphShare":{"description":"Percent of paragraphs past 8 sentences or 150 words.","type":["number","null"]},"longestParagraph":{"type":["number","null"]},"semicolons":{"type":"number"},"dashes":{"type":"number"},"fragments":{"type":"array","items":{"type":"string"},"description":"Sentences of three words or fewer."},"danglingRefs":{"type":"array","items":{"type":"string"},"description":"Sentences ending on \"…than that.\" with nothing to point at."},"longSentenceShare":{"type":["number","null"]},"brokenBlocks":{"type":"array","items":{"type":"string"},"description":"Held blocks that did not survive as blocks: a table flattened into one paragraph, pipe rows with no delimiter row."},"crlfLines":{"type":"number","description":"Lines ending in a carriage return; transport, never the writer's."},"words":{"type":"number"}},"required":["leaked","stackedHeadings","opensOnHeading","proseAboveTitle","openingWords","paragraphs","sentencesPerParagraph","shortParagraphShare","longParagraphShare","longestParagraph","semicolons","dashes","fragments","danglingRefs","longSentenceShare","brokenBlocks","crlfLines","words"]},"defects":{"type":"object","properties":{"brief_text":{"type":"boolean"},"heading_layout":{"type":"boolean"},"paragraphs_too_short":{"type":"boolean"},"paragraphs_too_long":{"type":"boolean"},"long_opening":{"type":"boolean"},"joined_sentences":{"type":"boolean"},"blocks_broken":{"type":"boolean","description":"A table flattened into a paragraph, or pipe rows no renderer reads as a table."}},"required":["brief_text","heading_layout","paragraphs_too_short","paragraphs_too_long","long_opening","joined_sentences","blocks_broken"]},"limits":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"The limits each defect is read against."}},"required":["format","shape","defects","limits"]}},{"name":"judge_paired","description":"A blind paired read of two texts, paragraph by paragraph: which side an editor-model prefers on each aligned pair, the tally, and an editorial rubric (natural, cohesion, fidelity, 1 to 5) for each side. It answers 'is this prose better than that prose', never 'does it pass', and one read sits inside about 24 points in 100 of noise, so compare against a control before reading a difference. Costs one judge call per pair, plus two per pair for the rubric. Read-only: no model writes a word. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"left":{"type":"string","minLength":1,"maxLength":400000,"description":"The first text (markdown or plain prose)."},"right":{"type":"string","minLength":1,"maxLength":400000,"description":"The second text, aligned with the first paragraph by paragraph."},"judge_model":{"description":"The judge model; the service's default when omitted.","type":"string","minLength":1,"maxLength":120},"max_paragraphs":{"description":"Cap on aligned paragraph pairs read (default 40).","type":"integer","minimum":1,"maximum":200},"rubric":{"description":"Also score natural / cohesion / fidelity, 1 to 5 (default true; two extra calls per pair).","type":"boolean"},"seed":{"description":"Fix the paragraph order for a repeatable read.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["left","right"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"pairs":{"type":"array","items":{"type":"object","properties":{"index":{"type":"number"},"left":{"type":"string"},"right":{"type":"string"},"winner":{"type":"string","enum":["left","right","tie"]},"reason":{"type":"string"}},"required":["index","left","right","winner","reason"]},"description":"One entry per aligned paragraph pair with the judge's pick and reason."},"wins":{"type":"object","properties":{"left":{"type":"number"},"right":{"type":"number"},"tie":{"type":"number"}},"required":["left","right","tie"]},"rubric":{"anyOf":[{"type":"object","properties":{"left":{"type":"object","properties":{"natural":{"type":"number","description":"1 to 5"},"cohesion":{"type":"number","description":"1 to 5"},"fidelity":{"type":"number","description":"1 to 5"}},"required":["natural","cohesion","fidelity"]},"right":{"type":"object","properties":{"natural":{"type":"number","description":"1 to 5"},"cohesion":{"type":"number","description":"1 to 5"},"fidelity":{"type":"number","description":"1 to 5"}},"required":["natural","cohesion","fidelity"]}},"required":["left","right"]},{"type":"null"}]},"judgeModel":{"type":"string"},"calls":{"type":"number"}},"required":["pairs","wins","rubric","judgeModel","calls"]}},{"name":"list_kernel_endpoint_runs","description":"Recent endpoint writes (telemetry) for the caller's org, newest first, with each run's run_id and status. Optionally filter to one endpoint. Use it to find runs in progress, and to recover when a run_kernel submit timed out before returning a run_id — the run may have been accepted (and charged) anyway, so look here before resubmitting. A list carries only a short preview of each input and no article; get_kernel_endpoint_run returns the full input as received and the article. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"endpoint_id":{"description":"Filter to one endpoint; omitted = the whole org.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"endpoint_id":{"type":["string","null"]},"endpoint_name":{"type":["string","null"]},"kernel_id":{"type":["string","null"]},"kernel_name":{"type":["string","null"]},"external_id":{"type":"string","description":"The run id — what get_kernel_endpoint_run polls."},"status":{"type":"string","description":"running | done | error."},"brief_chars":{"type":["number","null"]},"brief_preview":{"type":["string","null"]},"result_chars":{"type":["number","null"]},"word_count":{"type":["number","null"]},"latency_ms":{"type":["number","null"]},"error":{"type":["string","null"]},"shred_requested":{"type":"boolean","description":"The run asked for an in-loop shred."},"shredder_endpoint_id":{"type":["string","null"]},"shred_run_id":{"description":"The shred_runs row the in-loop shred produced, once settled.","type":["string","null"]},"created_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["id","endpoint_id","endpoint_name","kernel_id","kernel_name","external_id","status","brief_chars","brief_preview","result_chars","word_count","latency_ms","error","shred_requested","shredder_endpoint_id","shred_run_id","created_at","completed_at"]}}},"required":["items"]}},{"name":"get_kernel_run_balance","description":"The organization's standalone kernel-run balance: granted, used, and available. A standalone run_kernel call (no article_id) or a Studio run spends one; a run that errors is free, and article-bound runs and runs other tools start are not counted. When `tokensDecide` is true this counter limits nothing and the balance is get_billing's. Otherwise check it before a batch: running out mid-way fails the remaining calls rather than queueing them. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"granted":{"type":"number","description":"Runs granted to this org (starts at 10; staff top-ups add to it)."},"used":{"type":"number","description":"Consuming runs already logged (status running or done)."},"available":{"type":"number","description":"granted minus used, floored at 0. Zero blocks new runs."},"tokensDecide":{"type":"boolean","description":"True when this account's work is decided by tokens (billed on Lago, or on free tokens while the free paywall enforces): this counter doesn't limit it, and get_billing is the balance to read."}},"required":["granted","used","available","tokensDecide"]}},{"name":"configure_kernel","description":"Make an org-editable COPY of a kernel from list_kernels. The shared catalog is one row per kernel across every organization, so a catalog kernel cannot be edited in place — configuring copies its config into a kernel this org owns, which can then be tuned. The copy records what it came from. Editing its rules is a dashboard action (/kernels); this tool creates the copy. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"source_kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The kernel to copy — a shared catalog kernel, or one of your own."},"name":{"description":"Name for the copy. Defaults to '<source> (yours)'.","type":"string","minLength":1,"maxLength":200}},"required":["source_kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"description":"What this kernel writes like. Pick on this, not the codename.","type":["string","null"]},"org_id":{"description":"null = the shared catalog every org gets.","type":["string","null"]},"owned":{"type":"boolean","description":"This org owns it, so its rules can be edited."},"is_default":{"type":"boolean","description":"This org's currently selected writing kernel."},"forked_from_name":{"description":"The catalog kernel this copy was configured from.","type":["string","null"]},"service_kernel_id":{"type":["string","null"]},"service_kernel_version":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","description","org_id","owned","is_default","forked_from_name","service_kernel_id","service_kernel_version","created_at","updated_at"]}},"required":["kernel"]}},{"name":"update_kernel","description":"Update an org-owned kernel's name, description, and/or voice (register/humor/inquisitive/notes) — the same fields the app's kernel detail page edits. Raw prompt/config editing stays staff-only, same as always. A kernel set up directly by Letterbrace (voice_config: null on the app's own kernel detail page) refuses voice_config here too — name/description still work for it. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"voice_config":{"type":"object","properties":{"register":{"type":"string","enum":["casual","authoritative"]},"humor":{"type":"object","properties":{"enabled":{"type":"boolean"},"deviceCount":{"type":"integer","minimum":1,"maximum":5}},"required":["enabled"]},"inquisitive":{"type":"boolean"},"notes":{"type":"string","maxLength":2000}},"required":["register","humor","inquisitive"]},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Org-owned kernel id, from list_kernels."}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"kernel":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"}},"required":["id","version"]},"custom_evals":{"anyOf":[{"type":"object","properties":{"filed":{"type":"array","items":{"type":"string"},"description":"Newly proposed this call — filed at status 'proposed', not yet active."},"skipped":{"type":"array","items":{"type":"string"},"description":"Already exist as a rule (any status) from an earlier submission — left untouched, not re-filed."},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"]},"description":"Filing failed for this entry."}},"required":["filed","skipped","errors"]},{"type":"null"}],"description":"Present only when this update's voice_config also touched the kernel's rules.custom[] filing (a config round-trip re-checks every declared eval) — null otherwise."}},"required":["kernel","custom_evals"]}},{"name":"archive_kernel","description":"Archive an org-owned kernel (never a hard delete — every run it ever produced keeps its attribution). It stops being offered for new runs; existing runs and articles are unaffected. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Org-owned kernel id, from list_kernels."}},"required":["kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}},{"name":"start_kernel_studio_run","description":"Try a kernel the way the Kernels page's Studio does: write a throwaway article from a brief with any kernel your org can use (the catalog's or your own), and keep it in the Studio's History, where your team sees it beside their own runs. Same door, floor, credits and options as run_kernel, including `kernel_version` to try a candidate version of your own kernel before promote_kernel_version. Spends one kernel run. Returns at once; poll with get_kernel_studio_run. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"template":{"description":"Shape the article with one of your templates instead of hand-building `skeleton`. A template decides what sections the article has, in what order and in what form — prose, a bulleted list, numbered steps, a comparison, a quoted literal — and resolving it fills `skeleton`, `target_word_count` and `brief_json` for this call, with the material you sent as the source each section is written from. What it buys over a hand-built skeleton: the authoring grammar, a generated label for every unheaded block, {Variables} filled, word budgets allocated across the sections, optional sections the material did not earn dropped — and the way back, because the finished run comes out ASSEMBLED (`format: \"html\"`) rather than as the scaffolding the kernel writes. Anything you also set explicitly still wins. Not decomposed: a template's brief already carries your material as SOURCE MATERIAL.","type":"object","properties":{"key":{"description":"A template key from list_templates. Mutually exclusive with `blocks`.","type":"string","minLength":1,"maxLength":80},"blocks":{"description":"A template authored in this request, for a caller who stores none. The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back.","minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]}},"title":{"description":"The working headline. What a generated section label is made concrete about — a block reading 'how teams end up here' becomes 'How the task queue starts to show its limits' rather than 'Background'.","type":"string","maxLength":200},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":80},"additionalProperties":{"type":"string","minLength":1,"maxLength":400},"description":"Values for the template's {Variables}, e.g. {\"Queue\": \"Sidekiq\"}. Matched case-insensitively against the variable names in the template. A declared value beats anything derived, and is never sent to a model to be guessed."},"constraints":{"description":"An answer resolve_template already gave for this template. Pass it back and nothing is re-decided — which matters because the kernel writes each section under its label and assembly matches output back by that label, so a second resolution files sections under headings the outline never planned. Always pass this through when the same template drives both a research run and a write.","type":"array","items":{"type":"object","properties":{"blockId":{"type":"string"},"heading":{"type":["string","null"]},"covers":{"type":"string"},"form":{"type":"string","enum":["passage","enumeration","sequence","matrix","literal","open"]},"required":{"type":"boolean"},"label":{"type":"string","description":"What the research plans for and the writer writes under."},"printHeading":{"type":"boolean","description":"Whether the finished article prints a heading here."},"subjects":{"type":"array","items":{"type":"string"}},"dimensions":{"type":"array","items":{"type":"string"}},"layout":{"type":"string","enum":["subjects-as-columns","subjects-as-rows"]},"literal":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"variables":{"type":"array","items":{"type":"string"}}},"required":["blockId","heading","covers","form","required","label","printHeading"]}}}},"skeleton":{"description":"The SHAPE the article must hold, as data: an ordered list of blocks (heading / list / table / literal / paragraphs). This is the field a template resolves to — send your own and the kernel writes within it. Wins over the shape the service derives from source material. A shape written into the brief as prose holds at roughly a quarter fidelity; this holds.","maxItems":80,"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"heading"},"level":{"type":"integer","minimum":1,"maximum":6},"title":{"type":"string","minLength":1,"maxLength":300}},"required":["kind","level","title"]},{"type":"object","properties":{"kind":{"type":"string","const":"list"},"ordered":{"type":"boolean"},"minItems":{"description":"A floor meaning 'genuinely a list', never a target count.","type":"integer","minimum":1,"maximum":50}},"required":["kind","ordered"]},{"type":"object","properties":{"kind":{"type":"string","const":"table"},"cols":{"type":"integer","minimum":1,"maximum":20}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"literal"},"text":{"type":"string","minLength":1,"maxLength":20000,"description":"Held byte for byte; masked out of every copy-edit pass."}},"required":["kind","text"]},{"type":"object","properties":{"kind":{"type":"string","const":"paragraphs"}},"required":["kind"]}]}},"brief_json":{"description":"The brief as typed data (a KernelBrief): title, angle, passages, sources, statistics. The service uses it for its entity and figure gates; the text input stays the writing input. The service's decompose produces one for source material — send your own if you already have a typed brief. Wins over the service's. Claims are never a field: they are derived from your input on every run.","type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":300},"angle":{"type":"string","maxLength":2000},"passages":{"maxItems":60,"type":"array","items":{"type":"object","properties":{"heading":{"type":"string","minLength":1,"maxLength":300},"form":{"type":"string","minLength":1,"maxLength":40},"covers":{"type":"string","maxLength":4000},"wordTarget":{"type":"integer","minimum":0,"maximum":9007199254740991},"items":{"type":"array","items":{"type":"string","maxLength":1000}},"literal":{"type":"string","maxLength":20000}},"required":["heading","form","covers"],"additionalProperties":{}}},"source":{"maxItems":200,"type":"array","items":{"type":"string","maxLength":2000}},"statistics":{"maxItems":200,"type":"array","items":{"type":"object","properties":{"figure":{"type":"string","maxLength":200},"claim":{"type":"string","maxLength":1000}},"required":["figure","claim"]}}},"required":["title","angle","passages","source","statistics"],"additionalProperties":{}},"max_iterations":{"description":"Round-count override (1–5). The kernel config's own when omitted; 1 is the fast lane the Kernel Lab uses.","type":"integer","minimum":1,"maximum":5},"kernel_version":{"description":"Pin the kernel's catalog version for this run (reproducibility). Omitted = the version the kernel row is mapped to, or its head.","type":"integer","minimum":1,"maximum":9007199254740991},"config_patch":{"description":"A deep patch over the kernel's config for THIS run only, applied by the service and recorded on the run (never a new kernel version): try a writer with `{ \"model\": \"echo/echo\" }`, split the repairs with `repairModel`, pin the judge with `quality.model`. The merged config is validated like a kernel file; a patch the schema refuses is a 400.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"shred":{"description":"Tuning for the in-loop shred, when one runs. See each field; all optional.","type":"object","properties":{"tier":{"description":"Which provider tier the rewrite pool draws from. Service default when omitted.","type":"string","enum":["cheap","mid","strong","frontier"]},"families":{"description":"Restrict the rewrite pool to these provider families (service registry `provider` values).","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"skip_voice_bearing":{"description":"Leave voice-bearing sentences unshredded.","type":"boolean"},"judge":{"description":"Run the post-shred jury on the shredded draft.","type":"boolean"},"attempts_per_unit":{"description":"Retries per sentence across providers.","type":"integer","minimum":1,"maximum":10},"max_changeable_retention":{"description":"Turnover gate: max share of each sentence's changeable words a rewrite may keep.","type":"number","minimum":0,"maximum":1},"turnover_retries":{"description":"Feedback retries per provider over the turnover bar.","type":"integer","minimum":0,"maximum":10},"turnover_avoid_list":{"description":"Spell each sentence's changeable words out as a do-not-use list.","type":"boolean"},"evidence":{"description":"Formatted evidence the shred must conserve each rewrite against — the same bundle the accuracy pass reads, so a rewrite cannot introduce a figure or claim the evidence does not carry. Bring your own; a run with none is shredded unconserved.","type":"string","maxLength":60000}}},"target_word_count":{"description":"Per-call word budget, overriding the kernel config's own. 0 clears a config-set budget.","type":"integer","minimum":0,"maximum":20000},"voice":{"description":"How the article speaks. Both fields optional; omit entirely for no person gate.","type":"object","properties":{"person":{"description":"The publication's editorial person, enforced by a gate rather than requested in a prompt. Omitted = no person gate, the kernel's own prompts decide.","type":"string","enum":["third","first_plural","first_singular"]},"allow_experience_claims":{"description":"Permission to state first-hand observation as fact. A separate choice from person: writing 'I think X' is style, writing 'I've watched forty deploys fail' is a claim. Default false.","type":"boolean"}}},"label":{"description":"A free-form tag for your own segmentation — stored on the run and echoed in the trace. Any string works; never changes how the pipeline runs.","type":"string","maxLength":80},"content_kind":{"description":"How the service reads `input`. 'instruction' takes it as direction to write BY — still held to the same content floor as everything else (roughly 150+ words / 8+ substantive sentences; a bare topic or headline is refused, not developed). 'article', 'outline', 'research', 'notes' are SOURCE MATERIAL, decomposed into a faithful brief inside the service's submit and generated fresh, sized differently by kind: an article targets roughly its own length, an outline or research dump targets 1200-2000 words, so submitting an outline as an article writes it as short as the outline. 'auto' (default) lets the service decide between 'instruction' and 'article' by length alone; name the kind yourself for anything else. Your own finished post is 'article' — every decomposed kind keeps its headings, holds its tables, images, code, quotes and references byte for byte, and has every claim checked against the input's own; shape is always preserved and sections never move.","type":"string","enum":["auto","instruction","article","outline","research","notes"]},"shredder_endpoint_id":{"description":"Shred IN THE LOOP and log it against this shredder endpoint: every round is shredded before its jury, so the kernel judges the text that actually ships. Costs a shred as well as a run. Omit to use whatever the kernel endpoint is bound to; if neither is set but shred_aggression or shred is, the org's default shredder endpoint is provisioned on first use (same as a kernel endpoint itself) — you do not have to create one by hand first to shred in the loop. Omit all three for no in-loop shred.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"shred_aggression":{"description":"How hard the in-loop shred rewrites. Setting this (or shred) turns in-loop shredding ON even with no shredder_endpoint_id — the org's default shredder endpoint provisions on first use. 'standard' (default and recommended) rewrites every sentence and keeps the piece's own terminology; 'high' rewrites more and may reword recurring terms; 'max' rewrites hardest, keeping only names and figures — expect noticeable vocabulary drift.","type":"string","enum":["standard","high","max"]},"target_word_tolerance":{"description":"How far from `target_word_count` the draft may land before the writer is asked to trim or extend, as a fraction. Default 0.1 (10%). Raise it for a looser budget, lower it to hold a tight one; it does nothing without a target.","type":"number","minimum":0.01,"maximum":0.5},"directives":{"description":"Your own guidance for the writer — how to write, not what is true: a client to name once, an editorial stance, a format to favour. Appended to the brief AFTER the claims are read from `input`, so nothing here becomes a claim the article must carry. Put facts in `input`; put instructions here.","type":"string","maxLength":50000},"entities":{"description":"How the kernel treats named things — a client, a competitor, anything you want the article to name a specific way. 'never': off-limits, must not appear anywhere. 'mention_once': at most one natural, unrepeated mention. 'mention_often': referenced a few times where genuinely relevant. 'lead_company': named first and treated as the strongest, most prominent option wherever the topic invites a comparison. Sent to the service as data and rendered into the brief there, not left to prose you write yourself. A name your input itself carries survives a treatment — the input is the source of truth; the treatment governs what the writer adds. Checked back on settle for 'never', 'mention_once' and 'mention_often' (a count over the regenerated prose); 'lead_company' and 'linkable' are prose-only today and not independently verified.","maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"treatment":{"type":"string","enum":["never","mention_once","mention_often","lead_company"]},"linkable":{"description":"Whether a mention may be linked to or cited as a source. Default true. Independent of treatment — set false on top of any of them (e.g. mention_often + linkable:false for a competitor named a few times but never linked). Ignored when treatment is 'never'.","type":"boolean"}},"required":["name","treatment"]}},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"brief":{"type":"string","minLength":1,"maxLength":200000},"title":{"description":"Label for the run history; defaults to the brief's first line.","type":"string","maxLength":200}},"required":["kernel_id","brief"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"external_id":{"type":"string","description":"The run id: poll it with get_kernel_studio_run."},"kernel_id":{"type":"string"},"kernel_name":{"type":["string","null"]},"source":{"type":"string","description":"Who started it: studio, builder (the kernel page's test), or mcp."},"title":{"type":["string","null"]},"brief_preview":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","error"]},"word_count":{"type":["number","null"]},"error":{"type":["string","null"]},"shred_requested":{"type":"boolean"},"shredder_endpoint_id":{"type":["string","null"]},"shred_run_id":{"type":["string","null"]},"created_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["id","external_id","kernel_id","kernel_name","source","title","brief_preview","status","word_count","error","shred_requested","shredder_endpoint_id","shred_run_id","created_at","completed_at"]},"decomposed":{"description":"Whether the door read the brief as material to decompose.","type":["boolean","null"]},"content":{"description":"The door's reading of the INPUT: `sufficient` false when it carries too few named things, figures and cases for the requested length (the article will stay general: the writer cannot add particulars you did not give it), with `thin`, what to add per section. Advisory: the run proceeds. Null when the service sent none.","anyOf":[{"type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}},{"type":"null"}]}},"required":["run","decomposed"]}},{"name":"list_kernel_studio_runs","description":"The Kernels Studio's History: every Studio run, kernel-page test run and start_kernel_studio_run of your org, newest first, with who started it (source), its status and length. Filter to one kernel with kernel_id. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"kernel_id":{"description":"Only this kernel's runs; omitted, every kernel's.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"How many, newest first (default 30).","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"external_id":{"type":"string","description":"The run id: poll it with get_kernel_studio_run."},"kernel_id":{"type":"string"},"kernel_name":{"type":["string","null"]},"source":{"type":"string","description":"Who started it: studio, builder (the kernel page's test), or mcp."},"title":{"type":["string","null"]},"brief_preview":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","error"]},"word_count":{"type":["number","null"]},"error":{"type":["string","null"]},"shred_requested":{"type":"boolean"},"shredder_endpoint_id":{"type":["string","null"]},"shred_run_id":{"type":["string","null"]},"created_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["id","external_id","kernel_id","kernel_name","source","title","brief_preview","status","word_count","error","shred_requested","shredder_endpoint_id","shred_run_id","created_at","completed_at"]}}},"required":["items"]}},{"name":"get_kernel_studio_run","description":"Poll one Studio run (start_kernel_studio_run's run.external_id, or any run in list_kernel_studio_runs): its status and, once done, the article. The same run is readable in full — input, constraints, verification — with get_kernel_endpoint_run, and round by round with get_kernel_endpoint_run_steps. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","minLength":1,"description":"The run id start_kernel_studio_run returned (run.external_id)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"external_id":{"type":"string","description":"The run id: poll it with get_kernel_studio_run."},"kernel_id":{"type":"string"},"kernel_name":{"type":["string","null"]},"source":{"type":"string","description":"Who started it: studio, builder (the kernel page's test), or mcp."},"title":{"type":["string","null"]},"brief_preview":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","error"]},"word_count":{"type":["number","null"]},"error":{"type":["string","null"]},"shred_requested":{"type":"boolean"},"shredder_endpoint_id":{"type":["string","null"]},"shred_run_id":{"type":["string","null"]},"created_at":{"type":"string"},"completed_at":{"type":["string","null"]}},"required":["id","external_id","kernel_id","kernel_name","source","title","brief_preview","status","word_count","error","shred_requested","shredder_endpoint_id","shred_run_id","created_at","completed_at"]},"article":{"description":"The finished article; null while running or on an error.","type":["string","null"]},"format":{"type":"string","enum":["markdown","html"]},"passed":{"description":"The kernel's own verdict on the finished draft; null while running.","type":["boolean","null"]},"content":{"description":"The door's reading of the INPUT: `sufficient` false when it carries too few named things, figures and cases for the requested length (the article will stay general: the writer cannot add particulars you did not give it), with `thin`, what to add per section. Advisory: the run proceeds. Null when the service sent none.","anyOf":[{"type":"object","properties":{"sufficient":{"type":"boolean","description":"False when the input is thin for the requested length: below `threshold` particulars per 1,000 requested words. Advisory: the run was written anyway."},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}},"description":"What to add, at most eight, in the input's order. Empty when sufficient."},"measures":{"type":"object","properties":{"words":{"type":"number"},"particulars":{"description":"Distinct names, identifiers, figures, dates and units the writer must restate.","type":"number"},"names":{"type":"number"},"figures":{"type":"number"},"quoted":{"description":"Particulars only in quoted sources and table cells: not counted.","type":"number"},"targetWordCount":{"type":"number"},"targetRequested":{"type":"boolean"},"perTargetK":{"description":"Particulars per 1,000 words of `targetWordCount`: the verdict.","type":"number"},"sections":{"type":"array","items":{"type":"object","properties":{"heading":{"type":"string"},"particulars":{"type":"number"}},"required":["heading","particulars"],"additionalProperties":{}}}},"additionalProperties":{}},"judge":{"description":"The content judge's reading; runs only when the count reads thin. Null otherwise.","anyOf":[{"type":"object","properties":{"specificity":{"description":"0–3, the quality rubric's anchors applied to the material.","type":"number"},"theses":{"type":"number"},"supportableWords":{"type":["number","null"]},"thin":{"type":"array","items":{"type":"object","properties":{"section":{"description":"The input's own heading; null = the material as a whole.","type":["string","null"]},"what":{"type":"string","description":"What is missing, in a few words."},"ask":{"type":"string","description":"One sentence an agent can act on: the material to add for this section."}},"required":["section","what","ask"],"additionalProperties":{}}},"model":{"type":"string"},"version":{"type":"string"}},"additionalProperties":{}},{"type":"null"}]},"threshold":{"type":"number"}},"required":["sufficient","thin"],"additionalProperties":{}},{"type":"null"}]}},"required":["run","article","format","passed"]}},{"name":"suggest_kernel_voice_settings","description":"Read a free-form note about a kernel's voice (\"make it funnier\", \"more formal\") and say which of the structured voice settings it asks for — register, humor, inquisitive — and what of the note is left over for voice_config.notes. Changes nothing: merge `patch` into the kernel's voice_config and save it with update_kernel. The kernel page runs this before saving its notes field. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{"notes":{"type":"string","maxLength":2000},"current":{"type":"object","properties":{"register":{"type":"string","enum":["casual","authoritative"]},"humor":{"type":"object","properties":{"enabled":{"type":"boolean"},"deviceCount":{"type":"integer","minimum":1,"maximum":5}},"required":["enabled"]},"inquisitive":{"type":"boolean"}},"required":["register","humor","inquisitive"]},"kernel_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Org-owned kernel id, from list_kernels."}},"required":["notes","current","kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"patch":{"type":"object","properties":{"register":{"type":"string","enum":["casual","authoritative"]},"humor":{"type":"object","properties":{"enabled":{"type":"boolean"},"deviceCount":{"type":"number"}}},"inquisitive":{"type":"boolean"}},"description":"The voice settings the note asks for; merge into voice_config before update_kernel."},"matched":{"type":"array","items":{"type":"string"},"description":"One line per setting found, e.g. 'Humor → on'."},"residual_notes":{"type":"string","description":"The note with those sentences taken out: what stays in voice_config.notes."}},"required":["patch","matched","residual_notes"]}},{"name":"update_kernel_endpoint","description":"Rebind one of your kernel endpoints: which kernel it writes with and which shredder endpoint its runs shred against, in the loop. An explicit null unbinds (a kernel null follows your org's selected kernel; a shredder null stops shredding); a field you leave out is unchanged. For the endpoint's whole configuration, defaults included, use set_kernel_endpoint_config. Only the endpoint's creator or an org admin can change it. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"kernel_id":{"description":"The kernel this endpoint writes with; null = follow the org's selected kernel. Omit to leave it.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"shredder_endpoint_id":{"description":"The shredder endpoint its runs shred against, in the loop; null = stop shredding. Omit to leave it.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"endpoint_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Kernel endpoint UUID from list_kernel_endpoints."}},"required":["endpoint_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"public_id":{"type":"string","description":"Callable id: POST /api/kernel/ingest/<public_id>."},"kernel_id":{"description":"Bound kernel; null = follows the org's selected kernel.","type":["string","null"]},"kernel_name":{"type":["string","null"]},"shredder_endpoint_id":{"description":"Shredder endpoint this endpoint's runs shred against, in the loop. Null = no in-loop shred.","type":["string","null"]},"shredder_endpoint_name":{"type":["string","null"]},"last_used_at":{"type":["string","null"]},"created_at":{"type":["string","null"]},"run_count":{"type":"number","description":"Lifetime runs through this endpoint."}},"required":["id","name","public_id","kernel_id","kernel_name","shredder_endpoint_id","shredder_endpoint_name","last_used_at","created_at","run_count"]}},"required":["endpoint"]}},{"name":"get_company_info","description":"Get the caller's organization profile: name, slug, website domain, and company manifesto (the longer-form description that feeds on-brand content generation: posts and outbound campaign builds). Fields that haven't been set come back null. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":["string","null"]},"manifesto":{"description":"Longer-form company description feeding on-brand generation.","type":["string","null"]}},"required":["name","slug","domain","manifesto"]}},{"name":"set_company_info","description":"Update the caller's organization profile: name, website domain, manifesto, and/or search market. Each field is optional: a provided field is written, an omitted field is left unchanged. The manifesto is the longer-form company description (organizations.description) that feeds on-brand content generation: posts and outbound campaign builds (build_campaign), so new messaging goes here. The market is the country keyword demand is measured in — it sticks across discovery, suggestions and scoring, so set it here rather than passing country on each call. The domain is the organization's website: always mapped into The Site as its own site, which is what Set-up (prepare_account), Plan, Build and Fix work on when the org has no domain yet; it becomes the organization's domain (the one new sign-ups join it by) only when the org has none and it is the key minter's own verified email domain (or the minter is Letter staff); otherwise `domain_note` says so. Set-up's Set website field is this call. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"name":{"description":"Company / organization display name","type":"string","minLength":1,"maxLength":200},"domain":{"description":"Company website domain, e.g. example.com (scheme, www. and path are stripped)","type":"string","maxLength":253,"pattern":"^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$"},"manifesto":{"description":"The company manifesto: the longer-form description (plain text or HTML) that feeds on-brand content generation. Pass an empty string to clear it.","type":"string","maxLength":50000},"market":{"description":"ISO 3166-1 alpha-2 code of the search market this company sells into, e.g. 'AU'. Keyword volume and difficulty are measured per market, so this decides what discovery finds, what the numbers under a headline mean, and how opportunities are scored. Defaults to 'US'. Changing it re-prices the existing keyword pool on the next sync.","type":"string","pattern":"^[A-Z]{2}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":["string","null"]},"manifesto":{"type":["string","null"]},"market":{"type":"string","description":"The org's search market (ISO 3166-1 alpha-2)."},"domain_note":{"description":"Set when the website was mapped but not made the org's domain (the sign-up join key).","type":"string"}},"required":["name","slug","domain","manifesto","market"]}},{"name":"get_positioning","description":"Get the caller's organization editorial positioning: priority_topics (what to write about), stances (points of view to take), avoid_topics (what to steer clear of), and the company model (company_kind, stage, motion, category: typed fields the company read fills from the site when they are empty, under the row's provenance stamp, and set_positioning sets by hand; null until then). Use this to keep generated content on-brand. Returns empty arrays if positioning hasn't been set yet. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"priority_topics":{"type":"array","items":{"type":"string"},"description":"What the org wants to write about."},"stances":{"type":"array","items":{"type":"string"},"description":"Points of view to take."},"avoid_topics":{"type":"array","items":{"type":"string"},"description":"What to steer clear of."},"source":{"description":"How the positioning was set, e.g. 'user'.","type":["string","null"]},"updated_at":{"type":["string","null"]},"company_kind":{"anyOf":[{"type":"string","enum":["b2b_saas","devtools","ai_product","ecommerce","marketplace","agency","consumer","media","local_business"]},{"type":"null"}],"description":"The kind of company; read from the site when empty, set by set_positioning; null until then."},"stage":{"anyOf":[{"type":"string","enum":["pre-seed","seed","series-a-plus","established"]},{"type":"null"}],"description":"pre-seed, seed, series-a-plus or established; null until known."},"motion":{"anyOf":[{"type":"string","enum":["self-serve","sales-led","product-led","open-source"]},{"type":"null"}],"description":"How it sells: self-serve, sales-led, product-led or open-source; null until known."},"category":{"description":"The category in the company's own words; null until known.","type":["string","null"]}},"required":["priority_topics","stances","avoid_topics","source","updated_at","company_kind","stage","motion","category"]}},{"name":"set_positioning","description":"Set the caller's organization editorial positioning. Each of priority_topics, stances, and avoid_topics is optional; a provided field REPLACES that list, an omitted field is left unchanged. Pass an empty array to clear a field. The company model (company_kind, stage, motion, category) is set the same way: a provided field replaces it (null clears it), an omitted one stays; setting any field marks the row as yours (source user), undoing what the company read inferred. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"priority_topics":{"description":"Topics the org wants to write about (replaces the existing list if provided)","type":"array","items":{"type":"string"}},"stances":{"description":"Points of view / opinions the org wants to take (replaces the existing list if provided)","type":"array","items":{"type":"string"}},"avoid_topics":{"description":"Topics the org wants to avoid (replaces the existing list if provided)","type":"array","items":{"type":"string"}},"company_kind":{"description":"The kind of company (replaces it if provided; null clears it).","anyOf":[{"type":"string","enum":["b2b_saas","devtools","ai_product","ecommerce","marketplace","agency","consumer","media","local_business"]},{"type":"null"}]},"stage":{"description":"Where the company is: pre-seed, seed, series-a-plus or established (null clears it).","anyOf":[{"type":"string","enum":["pre-seed","seed","series-a-plus","established"]},{"type":"null"}]},"motion":{"description":"How it sells: self-serve, sales-led, product-led or open-source (null clears it).","anyOf":[{"type":"string","enum":["self-serve","sales-led","product-led","open-source"]},{"type":"null"}]},"category":{"description":"The category in the company's own words, under 8 words (null clears it).","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"priority_topics":{"type":"array","items":{"type":"string"}},"stances":{"type":"array","items":{"type":"string"}},"avoid_topics":{"type":"array","items":{"type":"string"}},"source":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"company_kind":{"anyOf":[{"type":"string","enum":["b2b_saas","devtools","ai_product","ecommerce","marketplace","agency","consumer","media","local_business"]},{"type":"null"}],"description":"The kind of company; read from the site when empty, set by set_positioning; null until then."},"stage":{"anyOf":[{"type":"string","enum":["pre-seed","seed","series-a-plus","established"]},{"type":"null"}],"description":"pre-seed, seed, series-a-plus or established; null until known."},"motion":{"anyOf":[{"type":"string","enum":["self-serve","sales-led","product-led","open-source"]},{"type":"null"}],"description":"How it sells: self-serve, sales-led, product-led or open-source; null until known."},"category":{"description":"The category in the company's own words; null until known.","type":["string","null"]}},"required":["priority_topics","stances","avoid_topics","source","updated_at","company_kind","stage","motion","category"]}},{"name":"list_competitors","description":"List the competitor domains tracked by the caller's organization, for market/positioning context. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"competitor_domain_id":{"type":["string","null"]},"name":{"type":["string","null"]},"domain":{"type":["string","null"]},"last_scraped_at":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["competitor_domain_id","name","domain","last_scraped_at","created_at"]}}},"required":["items"]}},{"name":"get_competitor_feed","description":"The 8 newest articles scraped from the caller's tracked competitors (undated ones last), the same feed behind The Story's 'Competitor Fresh Posts' card. Empty when no competitors are tracked or none has been scraped yet (scrape_competitor). For every competitor article ranked by this org's keyword priority, use list_competitor_topics. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Scraped article title; 'Untitled' when missing."},"url":{"type":["string","null"]},"published_at":{"type":["string","null"]},"competitor":{"type":"string","description":"Tracked competitor name, falling back to domain or 'A competitor'."}},"required":["id","title","url","published_at","competitor"]}}},"required":["items"]}},{"name":"list_competitor_topics","description":"The org's tracked competitors' articles, across ALL of them at once, ranked most relevant first (this org's keyword_priority, then recency) — the same feed behind the Topics tab's 'What they publish' card. Category/hub listing pages and undated pages are filtered out. For one specific competitor's full history instead, use get_competitor_topics. Does not trigger a new scrape — use scrape_competitor for that. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"source_url":{"type":"string"},"scraped_at":{"type":"string"},"inferred_keyword":{"description":"Keyword inferred for this topic, if any","type":["string","null"]},"description":{"type":["string","null"]},"published_at":{"description":"Original publish date of the source page, if known","type":["string","null"]},"keyword_priority":{"description":"The caller org's priority for this topic's keyword, if set. One of: high | medium | low.","type":["string","null"]},"competitor_name":{"type":"string","description":"The org's label for this competitor, falling back to its domain"}},"required":["id","title","source_url","scraped_at","inferred_keyword","description","published_at","keyword_priority","competitor_name"]}}},"required":["items"]}},{"name":"add_competitor","description":"Track a competitor domain for the caller's organization. The domain is scraped in the background on first add so its topics feed positioning context. To add several at once, use add_competitors. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name for the competitor"},"domain":{"type":"string","minLength":1,"maxLength":255,"description":"Competitor domain, e.g. example.com (scheme/path are stripped)"}},"required":["name","domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"competitor_domain_id":{"type":"string"},"name":{"type":["string","null"]},"domain":{"type":"string"},"last_scraped_at":{"type":["string","null"]},"created_at":{"type":"string"},"scrape_started":{"type":"boolean","description":"True when a background scrape was kicked off on first add."}},"required":["competitor_domain_id","name","domain","last_scraped_at","created_at","scrape_started"]}},{"name":"add_competitors","description":"Track up to 50 competitor domains for the caller's organization in one call — same handling as add_competitor (background scrape on first add, org-level dedup) per domain. Partial failures don't abort the batch: check each result's status (added / already_tracked / failed) instead of the call's overall success. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"competitors":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name for the competitor"},"domain":{"type":"string","minLength":1,"maxLength":255,"description":"Competitor domain, e.g. example.com (scheme/path are stripped)"}},"required":["name","domain"]},"description":"Up to 50 competitors to add in one call — each needs its own name and domain, same as add_competitor."}},"required":["competitors"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","description":"The input domain this result corresponds to."},"status":{"type":"string","enum":["added","already_tracked","failed"],"description":"added: newly tracked. already_tracked: this org already tracks this domain. failed: could not be added."},"competitor":{"anyOf":[{"type":"object","properties":{"competitor_domain_id":{"type":"string"},"name":{"type":["string","null"]},"domain":{"type":"string"},"last_scraped_at":{"type":["string","null"]},"created_at":{"type":"string"},"scrape_started":{"type":"boolean","description":"True when a background scrape was kicked off on first add."}},"required":["competitor_domain_id","name","domain","last_scraped_at","created_at","scrape_started"]},{"type":"null"}],"description":"Present when status is 'added'."},"error":{"description":"Present when status is 'already_tracked' or 'failed'.","type":["string","null"]}},"required":["domain","status","competitor","error"]}},"added":{"type":"number","description":"Count of results with status 'added'."},"already_tracked":{"type":"number","description":"Count of results with status 'already_tracked'."},"failed":{"type":"number","description":"Count of results with status 'failed'."}},"required":["results","added","already_tracked","failed"]}},{"name":"get_onboarding_status","description":"Get the caller's organization onboarding progress: overall status, the current step, the completed and skipped step lists, and per-step saved state. Also returns the step registry (which steps exist, in order) and whether every step is resolved. Lazy-creates a not_started record on first read. This is the dashboard wizard's bookkeeping (goals, domain, description, stances, import, flow) — for what the organization actually still needs (context, main blog, Planner, CMS, phantom) and which of those is ready to run, call get_onboarding_plan. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"onboarding":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"status":{"type":"string","enum":["not_started","in_progress","completed","dismissed"]},"current_step":{"description":"Step id the user is currently on, or null","type":["string","null"]},"completed_steps":{"type":"array","items":{"type":"string"},"description":"Step ids marked complete"},"skipped_steps":{"type":"array","items":{"type":"string"},"description":"Step ids marked skipped"},"step_state":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Per-step scratch data keyed by step id; each entry is an object owned by that step"},"started_at":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","status","current_step","completed_steps","skipped_steps","step_state","started_at","completed_at","created_at","updated_at"]},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Step id (e.g. goals, domain, import)"},"title":{"type":"string"},"purpose":{"type":"string","description":"Terse one-liner shown under the step title"}},"required":["id","title","purpose"]},"description":"The step registry: every step of the flow, in order"},"all_wired_resolved":{"type":"boolean","description":"True when every step is completed or skipped"}},"required":["onboarding","steps","all_wired_resolved"]}},{"name":"propose_primitive_plan","description":"WHAT WOULD LETTER STAND UP FOR THIS COMPANY — reads the organization's domain (or `domain`), infers its positioning, and returns the common stack as an org-config plan: the blog's writing kernel, three phantom sites (one per priority topic, each on its own collection written in that kernel's family, with an arena for its beat, and on a domain named after its masthead that it buys as it launches, its record's `domain` + `buyDomain`, for as many sites as the org may still hold a domain for), three cold-email mailers on a sending domain of their own (named for the asking user, plus outreach@), and a sequence gated on its own dispatch — each item checked against what the org already runs. `primitives` (a goal's, from propose_account_plan's selection.bound) keeps only those — kernel, phantom with its collection and arena, mailer, sequence with its dispatch — items and bundle records alike. `plan.config` is an `Organization` bundle: the org's live config for these sections plus every proposed record, so diff_org_config shows exactly the additions and apply_org_config creates them (pass each item's `gates` as `allow`; drop a rejected item's `record` from the bundle first). Persists nothing beyond the per-domain read the public composer caches. A first read crawls the site and can take a minute; status:pending means another read is in flight — call again shortly. A failed read is reported as failed until you pass retry:true. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"domain":{"description":"The domain to plan for. Defaults to the organization's own domain.","type":"string","minLength":1},"retry":{"description":"Re-read a domain whose last read failed. Without it a failed read is reported as-is, so polling never re-crawls.","type":"boolean"},"primitives":{"description":"Only plan these primitives (a phantom brings its collection and arena, a sequence its dispatch): the goal's when the plan was picked for one (propose_account_plan's selection.bound). Omit for the whole stack.","maxItems":4,"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["complete","pending","failed"],"description":"pending = the domain read is still running; call again in a few seconds"},"domain":{"type":"string"},"error":{"type":["string","null"]},"plan":{"anyOf":[{"type":"object","properties":{"domain":{"type":"string"},"company_name":{"type":"string"},"description":{"type":"string"},"priority_topics":{"type":"array","items":{"type":"string"}},"outcome_ladder":{"type":"array","items":{"type":"string"}},"generated_at":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within the plan, e.g. phantom:1, phantom:1:arena"},"primitive":{"type":"string","enum":["kernel","phantom","collection","arena","mailer","sequence","dispatch"]},"title":{"type":"string"},"detail":{"type":"string","description":"What applying it creates or changes, in one line"},"why":{"type":"string","description":"Why it fits this company (empty on a part of its parent)"},"parent":{"description":"The item this one is part of setting up (shown indented), or null","type":["string","null"]},"status":{"type":"string","enum":["proposed","exists"],"description":"exists = the org already runs this"},"existing":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"description":"What already runs (up to 5)"},"record":{"anyOf":[{"type":"object","properties":{"section":{"type":"string","description":"The bundle section the record sits in (`settings` for the default kernel, else a list section)"},"name":{"type":"string","description":"The record's name (records match by name); for settings, the value it sets"},"field":{"description":"For a settings record: the field it sets","type":"string"}},"required":["section","name"]},{"type":"null"}],"description":"The record in `config` applying this item writes; drop it from the bundle to reject the item"},"gates":{"type":"array","items":{"type":"string","enum":["spends","sends"]},"description":"The apply_org_config gates it needs, passed as `allow` (with the dry run's `confirm`)"}},"required":["id","primitive","title","detail","why","parent","status","existing","record","gates"]}},"config":{"type":"object","properties":{"apiVersion":{"type":"string","const":"letterstory.com/org-config/v1"},"kind":{"type":"string","const":"Organization"},"metadata":{"type":"object","properties":{"org":{"type":"string","description":"The org's slug."},"exportedAt":{"type":"string"},"source":{"type":"string","description":"The Letter origin the bundle was read from."},"sections":{"type":"array","items":{"type":"string"},"description":"The sections this bundle carries."},"pending":{"type":"array","items":{"type":"string"},"description":"Config sections no reader exports yet."},"redacted":{"type":"array","items":{"type":"string"},"description":"Paths whose value looked like a secret and was replaced by `ref:redacted`."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts that could not be read (e.g. the kernels service was unreachable)."}},"required":["org","exportedAt","source","sections","pending","redacted","warnings"]},"spec":{"type":"object","properties":{"settings":{"type":"object","properties":{"organization":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"branding":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"appearance":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"defaultKernel":{"type":["string","null"]},"reviewers":{"description":"Standing reviewers: invited to review every approval.","type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]}},"required":["email","name"]}},"mail":{"description":"Org-wide mail settings.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["organization","branding","appearance","defaultKernel"],"additionalProperties":{}},"primitives":{"type":"object","properties":{"phantoms":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Phantom"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Phantom, keyed by name."}},"kernels":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Kernel"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Kernel, keyed by name."}},"seers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Seer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Seer, keyed by name."}},"shredders":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Shredder"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Shredder, keyed by name."}},"arenas":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Arena"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Arena, keyed by name."}},"ledes":{"type":"object","properties":{"library":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Lede"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Lede, keyed by name."}},"endpoints":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"LedeEndpoint"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One LedeEndpoint, keyed by name."}}},"required":["library","endpoints"]},"mailers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Mailer"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Mailer, keyed by name."}},"prospects":{"description":"Prospect lists, columns and enclosures (the generators that fill columns) — never the prospects themselves.","type":"object","properties":{"lists":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectList"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectList, keyed by name."}},"columns":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"ProspectColumn"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One ProspectColumn, keyed by name."}},"enclosures":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Enclosure"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Enclosure, keyed by name."}}},"required":["lists","columns"]},"dispatches":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Dispatch"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Dispatch, keyed by name."}}},"additionalProperties":{}},"data":{"type":"object","properties":{"context":{"type":"object","properties":{"positioning":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"offerings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Offering"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Offering, keyed by name."}},"competitors":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Competitor"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Competitor, keyed by name."}},"brandProfiles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"BrandProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One BrandProfile, keyed by name."}},"sourceRoles":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SourceRole"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SourceRole, keyed by name."}},"memory":{"description":"Custom memory sections, by slug, with their entries.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"MemorySection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One MemorySection, keyed by name."}}},"required":["positioning","offerings","competitors"]},"collections":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Collection"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Collection, keyed by name."}},"templates":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Template"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Template, keyed by name."}},"compositions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Composition"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Composition, keyed by name."}},"sequences":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Sequence"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Sequence, keyed by name."}},"landings":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"Landing"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One Landing, keyed by name."}},"canvasTypes":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"CanvasType"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One CanvasType, keyed by name."}},"profiles":{"description":"Social publishing personas.","type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"SocialProfile"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One SocialProfile, keyed by name."}}},"additionalProperties":{}},"tools":{"type":"object","properties":{"measure":{"type":"object","properties":{"rankingTargets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"RankingTarget"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One RankingTarget, keyed by name."}},"topics":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"TrackedTopic"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One TrackedTopic, keyed by name."}},"keywordExclusions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"KeywordExclusion"},"name":{"type":"string","minLength":1}},"required":["kind","name"],"additionalProperties":{},"description":"One KeywordExclusion, keyed by name."}},"native":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"cap":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]}},"required":["rankingTargets","topics","keywordExclusions","native"]}},"additionalProperties":{}}},"required":["primitives","data","tools"]}},"required":["apiVersion","kind","metadata","spec"],"description":"Live config for the plan's sections + every proposed record — run it through diff_org_config / apply_org_config"}},"required":["domain","company_name","description","priority_topics","outcome_ladder","generated_at","items","config"]},{"type":"null"}],"description":"Present when status is complete"}},"required":["status","domain","error","plan"]}},{"name":"stand_up_stack","description":"STAND UP THE COMMON STACK IN ONE CALL — propose_primitive_plan's plan for the organization's domain (or `domain`), applied at once: what Plan's stack review does with a dry run and an apply, and what Build runs when a member's own words ask it to launch phantom sites (\"launch 2-3 third-party sites\"), set up mailers or a sequence (Build allows sends only when they named mailers). `primitives` keeps only those parts ([\"phantom\"]: phantom sites, each with its collection and arena, and nothing else). Applied through apply_org_config, behind its gates: an item goes up only when every gate it needs is in `allow` (spends = launch a phantom site and buy its domain, or buy a mailer domain; sends = start a mailbox warming) and the caller is an org admin; anything that needs more is left for review and listed in `held`. Answers `stood_up` (each phantom site created, its deploy and setup carrying on in the background: follow it with list_phantoms / get_phantom), `held`, the apply's `result` and a `message` a member can read. `dry_run: true` says what would go up and creates nothing. status pending: the domain is being read for the first time; call again shortly. Re-running converges: what runs already is not created again. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"domain":{"description":"The domain to plan for. Defaults to the organization's own domain.","type":"string","minLength":1},"retry":{"description":"Re-read a domain whose last read failed. Without it a failed read is reported as-is, so polling never re-crawls.","type":"boolean"},"primitives":{"description":"Only stand up these (a phantom brings its collection and arena, a sequence its dispatch), e.g. [\"phantom\"] for phantom sites alone. Omit for the whole stack.","maxItems":4,"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},"allow":{"description":"The gates you allow, as apply_org_config takes them (org admins only): spends = launch a phantom site (provisions it, uses a phantom credit) or buy a mailer domain; sends = start a mailbox warming. An item that needs a gate not named here is left for review and listed in `held`. Omitted: none, so only what needs no gate (a sequence held for review, its dispatch, the default kernel) is created.","maxItems":2,"type":"array","items":{"type":"string","enum":["spends","sends"]}},"dry_run":{"description":"true = say what would be stood up (apply_org_config's dry run) and create nothing.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["applied","dry_run","nothing","pending"],"description":"applied = created; dry_run = what would be; nothing = nothing to create (it runs already, or waits for review); pending = the domain is still being read, call again shortly"},"domain":{"type":"string"},"stood_up":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within the plan, e.g. phantom:1, phantom:1:arena"},"primitive":{"type":"string","enum":["kernel","phantom","collection","arena","mailer","sequence","dispatch"]},"title":{"type":"string"},"detail":{"type":"string","description":"What applying it creates or changes, in one line"}},"required":["id","primitive","title","detail"]},"description":"What went up (or, on a dry run, would): one per thing stood up, its parts with it"},"held":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within the plan, e.g. phantom:1, phantom:1:arena"},"primitive":{"type":"string","enum":["kernel","phantom","collection","arena","mailer","sequence","dispatch"]},"title":{"type":"string"},"gates":{"type":"array","items":{"type":"string","enum":["spends","sends"]},"description":"The gates it needs that weren't allowed"}},"required":["id","primitive","title","gates"]},"description":"What was left for the Plan's review: it needs a gate the call didn't allow"},"result":{"anyOf":[{"type":"object","properties":{"plan":{"type":"object","properties":{"org":{"type":"string"},"sections":{"type":"array","items":{"type":"string"},"description":"The sections compared — those present in the bundle."},"changes":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"],"description":"`remove`: in the org but not the bundle — apply leaves it unless pruning."},"section":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"fields":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Leaf changes, live → bundle (`change` only)."}},"required":["op","section","kind","name","fields"]}},"summary":{"type":"object","properties":{"add":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remove":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["add","change","remove","unchanged"]},"warnings":{"type":"array","items":{"type":"string"}}},"required":["org","sections","changes","summary","warnings"],"description":"What applying the bundle would change."},"dryRun":{"type":"boolean"},"prune":{"type":"boolean"},"applied":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"}},"required":["op","section","name"]},"description":"Written — or, on a dry run, what would be written."},"skipped":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"reason":{"type":"string"}},"required":["op","section","name","reason"]}},"failed":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"error":{"type":"string"}},"required":["op","section","name","error"]}},"gated":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","change","remove"]},"section":{"type":"string"},"name":{"type":"string"},"detail":{"description":"For a kernel, shredder or arena file: what applying it does, e.g. \"save + promote\", \"save as candidate\".","type":"string"},"gate":{"type":"string","enum":["spends","sends","destroys","access"]}},"required":["op","section","name","gate"]},"description":"Changes held behind a gate: applied only when `allow` names the gate and `confirm` matches."},"confirm":{"description":"The hash of the gated changes, to pass back as `confirm`.","type":["string","null"]}},"required":["plan","dryRun","prune","applied","skipped","failed","gated","confirm"],"description":"What the apply did (or, on a dry run, would do)."},{"type":"null"}],"description":"apply_org_config's result for what was applied (null when nothing was)"},"message":{"type":"string","description":"What happened, in sentences a member reads"}},"required":["status","domain","stood_up","held","result","message"]}},{"name":"prepare_account","description":"SET THE ACCOUNT UP ON FIRST LOAD — what the Instances console shows as Set-up before Build, Plan and Fix open. Three stages side by side: `site` reads the sitemap (maps the organization's website if it isn't; the audit and quick check start with it), `repo` reads the repo for context once the site is mapped (which connected repo builds the site, its routes, and where its posts come from — the same check as detect_repo_sites), and `brand` learns the site's design (palette, type, logo; once per website, as at sign-up). Answers at once with each stage's state (done / running / pending / blocked / failed), a few `lines` of what it has read, and `cms`: the CMS the site's posts come from when the repo shows one (connect it with connect_cms when `provider` is set). The first page suggestions run behind it (`suggestions`). `finished` says whether Set up was already finished or passed for the organization (finish_account_setup), and by whom. Starts whatever hasn't started in the background; call it again to watch the setup finish (`ready`). Idempotent: nothing is mapped, read, learned or suggested twice. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"description":"The organization's website; null until its domain is set","type":["string","null"]},"stages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["site","repo","brand"]},"label":{"type":"string"},"state":{"type":"string","enum":["done","running","pending","blocked","failed"],"description":"blocked: needs something only a person gives (the website, GitHub, the repo that builds the site); failed: note says why"},"note":{"type":["string","null"]},"lines":{"type":"array","items":{"type":"string"},"description":"What the stage has read so far, a line each, oldest first"},"url":{"description":"Where a person moves a blocked stage outside Letter: GitHub's page for which repos the Letter app reaches","type":["string","null"]}},"required":["id","label","state","note","lines"]}},"ready":{"type":"boolean","description":"Every stage done, or waiting on a person, or failed; stop polling"},"cms":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"provider":{"anyOf":[{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},{"type":"null"}],"description":"The provider connect_cms takes; null when Letter can't connect it"},"prefix":{"type":"string","description":"The URL folder whose pages it serves"}},"required":["name","provider","prefix"]},{"type":"null"}],"description":"The CMS the site's posts come from, as read in the repo that builds it; null when none was found"},"suggestions":{"type":"object","properties":{"state":{"type":"string","enum":["done","running","pending","blocked","failed"]},"note":{"type":["string","null"]}},"required":["state","note"],"description":"The first page suggestions, run behind the setup once the site is mapped; nothing waits on them"},"finished":{"anyOf":[{"type":"object","properties":{"finished_by":{"description":"The member who finished it; null for an agent's key with no person","type":["string","null"]},"finished_at":{"type":"string"}},"required":["finished_by","finished_at"]},{"type":"null"}],"description":"Set up was finished or passed (finish_account_setup): Build, Plan and Fix open straight away. Null while it still stands in front of them"},"message":{"type":"string","description":"In words: 'Your account is set up.' only when every stage is done; otherwise what each open stage waits on and how to move it"}},"required":["host","stages","ready","cms","suggestions","finished","message"]}},{"name":"finish_account_setup","description":"FINISH SET UP — what Letterpress-1 does when its Set-up screen finishes, or a member presses Continue past it. Records that the organization has been through the first-load setup (prepare_account), so Build, Plan and Fix open straight away in every browser, and prepare_account's `finished` says who finished it and when. Nothing about the stages changes: one still blocked stays blocked. `finished: false` shows Set up again. Idempotent: finishing twice keeps the first. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"finished":{"description":"false shows Set up again before Build, Plan and Fix. Default true.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"finished":{"anyOf":[{"type":"object","properties":{"finished_by":{"description":"The member who finished it; null for an agent's key with no person","type":["string","null"]},"finished_at":{"type":"string"}},"required":["finished_by","finished_at"]},{"type":"null"}],"description":"Who finished Set up and when; null once it is shown again"}},"required":["finished"]}},{"name":"propose_account_plan","description":"EVERYTHING LETTER CAN DO FOR THIS ACCOUNT, AS ONE PLAN — the Instances console's Plan. Reads the organization's domain (or `domain`): the onboarding facts, GitHub, the writing kernel, Visitors and the primitives already running, and returns every action in chapters — blog (context, CMS, a writing kernel, onboarding the existing blog, the Planner, Search Console), visitors (Visitors / LetterVisit: the companies and AI agents that read the site, de-anonymized; visitors_get_people), proofs (Letterprove: signed proof of the companies whose users sign in to the customer's product, for agents; never identifies site visitors), instances (the common stack, reviewed with propose_primitive_plan), outbound (cold email from the member's words), site (a goal's fix row) and strategy (staff's recipes that fit). The plan is for creating something new; fixing what the site already has (audit findings, the quick check) is The Site's: get_site_tree.focus and fix_site_observations. Each action carries its status (ready / blocked / running / done), what blocks it and where a person fixes that, and the exact call that performs it (`tool` + `args`; `callable` says whether this key may make it, `batch` names the array argument that merges several actions into one call). With no `recipe` and no `prompt` the plan is everything. `recipe` (an id from list_recipes to ground the pick in that recipe alone, or \"auto\" to read the whole library as direction and follow the one that fits; with `builds`, that many different recipes, each its own growth build) or `prompt` (the customer's own words) adds `selection.picks`: the `limit` actions to do first, each with why, chosen only from the plan's actions — anything asked that no action does comes back in `selection.unmet`. A goal also says what the plan may stand up: every action lists what it stands up, a pick never includes one that stands up only primitives the goal leaves out, and `selection.bound` is what to pass to propose_primitive_plan for the stack's review. `selection.builds` are the growth builds (a recipe each, with its picks); start one with start_growth_build and watch it with list_growth_builds. Reads only; nothing is created. A first read of a new domain crawls it and can take a minute: status:pending means call again shortly. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"domain":{"description":"The site to plan for. Defaults to the organization's own domain.","type":"string","minLength":1},"retry":{"description":"Re-read a domain whose last read failed. Without it a failed read is reported as-is.","type":"boolean"},"recipe":{"description":"Ground the pick in a recipe (list_recipes): a recipe id, or \"auto\" to read the whole library as direction and follow the recipe that fits the company.","anyOf":[{"type":"string","const":"auto"},{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}]},"prompt":{"description":"What the customer wants, in their words. The pick chooses the actions that do it.","type":"string","minLength":1,"maxLength":2000},"limit":{"description":"How many actions to pick (with recipe or prompt), per build. Default 3 with a recipe, 5 with a prompt. With `goal` it counts actions and site changes together, default 3: a goal plan recommends the next few things that serve the goal directly.","type":"integer","minimum":1,"maximum":12},"quick":{"description":"With `goal`: answer at once with the goal's own moves in its order (what to connect, run or answer next), with no model pick and no waiting on a site read. The site changes and the why lines come from the same call without `quick`, which takes longer; the Plan asks for both and shows the moves first. With no goal, recipe or prompt: every action's state as it stands, never waiting on (or starting) a first read of the site.","type":"boolean"},"builds":{"description":"With recipe \"auto\": how many growth builds, each a different recipe that fits with its own `limit` actions; an action is in one build only. Default 1.","type":"integer","minimum":1,"maximum":4},"goal":{"description":"The outcome to improve (the Plan's goal boxes): agent_conviction (AI agents recommend the company), geo (cited in AI answers), seo (rank in search), inbound_conversion (reach the people who read the site), other (any strategy across every outcome; with `prompt`, the member's own goal). outbound is not available yet. The plan's rows are the strategies staff wrote that fit this company (its set-up rows first), in staff's order; a strategy fits when its conditions hold for the company, decided from the facts the account already holds.","type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"intent":{"description":"build (default): something new. fix: a problem with what exists (a bug, a page missing something such as sitemap entries); the plan answers with the site changes that fix it and stands nothing new up.","type":"string","enum":["build","fix"]},"refresh":{"description":"With `goal`: decide every strategy again from the facts as they are now, instead of reading the fit kept for this account (the standing fit). Default false: a strategy's verdict is read as kept and decided again only when what it rested on moved.","type":"boolean"},"primitives":{"description":"What this plan may stand up (kernel, phantom, mailer, sequence), on top of the goal's bound. Omit for no extra bound; [] for nothing new.","maxItems":4,"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},"files":{"description":"Files the member attached to the ask (start_build reads them for you). The plan may build on them, and its site changes name them in their instructions; pass the files themselves to the build's changes.","maxItems":20,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The file's name, as the build will receive it."},"excerpt":{"description":"The start of a text file (a brief, notes, a CSV), so it can shape the plan.","type":"string","maxLength":1500}},"required":["name"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["complete","pending"],"description":"pending = the domain read is still running; call again shortly"},"domain":{"type":"string"},"error":{"type":"null"},"plan":{"anyOf":[{"type":"object","properties":{"domain":{"type":"string"},"company_name":{"type":"string"},"description":{"type":"string"},"chapters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"summary":{"type":"string"}},"required":["id","title","summary"]}},"actions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable across reads, e.g. blog:collection, instances:stack"},"chapter":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"detail":{"type":"string","description":"What doing it does"},"why":{"type":"string"},"impact":{"type":"number","description":"1–10"},"status":{"type":"string","enum":["ready","blocked","running","done"]},"blocked_by":{"type":"array","items":{"type":"object","properties":{"need":{"type":"string","description":"github, site_repo, visitors_script, or an onboarding fact (positioning_set, …)"},"label":{"type":"string","description":"What is missing, as a member reads it"},"href":{"description":"The app page where a person supplies it","type":["string","null"]},"tool":{"description":"The tool that supplies it, for an agent","type":["string","null"]}},"required":["need","label","href","tool"]}},"tool":{"type":"string","description":"The tool that performs it"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"The exact arguments, when the account determines them; null when a person supplies input"},"batch":{"description":"The array argument that merges across actions calling the same tool (one call for several)","type":["string","null"]},"prepare":{"type":"array","items":{"type":"string"},"description":"Read-only tools worth calling first"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"The primitives doing it stands up; empty when it creates none. A goal's `bound` holds the pick"},"callable":{"type":"boolean","description":"This key may call `tool` itself; false means a person does it at `href`"},"href":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"},"value":{"type":"string"}},"required":["label"]}},"words":{"description":"On an action that does what the member's words describe (a cold-email campaign, a list of people): the argument their ask fills (`text`) and the one the number of people it names fills (`count`, at most `max`). Started from start_growth_build with the build's `prompt`","type":"object","properties":{"text":{"type":"string"},"chars":{"type":"number"},"count":{"type":"string"},"max":{"type":"number"}},"required":["text","chars"]},"letterbase":{"description":"The letterbase (collection) this row stands up or writes on, once it exists: pass `id` as collection_id to list_automations (what it writes) and list_collection_runs (its posts)","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},"follow":{"description":"Where what its call started is followed: `href` with `{id}` read from the answer at path `id`","type":"object","properties":{"id":{"type":"string"},"href":{"type":"string"}},"required":["id","href"]},"required":{"description":"On a goal's set-up: the results wait on this row (the GitHub connection); every other row is additive","type":"boolean"},"stage":{"description":"On a goal's plan: setup (connect, answer, launch; done first and all at once), launched (what the set-up stood up, with what it writes) or result (the strategies that fit, in staff's order)","type":"string","enum":["setup","launched","result"]}},"required":["id","chapter","title","detail","why","impact","status","blocked_by","tool","args","batch","prepare","primitives","callable","href","evidence"]}},"dismissed":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"dismissed_by":{"description":"The user who dismissed it","type":["string","null"]},"dismissed_at":{"type":"string"},"kind":{"description":"later: for the dismissal window; never: at any age.","type":"string","enum":["later","never"]}},"required":["action_id","dismissed_by","dismissed_at"]},"description":"Actions a member dismissed on this site (dismiss_plan_action); Set up leaves them out"},"selection":{"type":"object","properties":{"by":{"type":"string","enum":["everything","recipe","prompt"],"description":"Who chose what comes first"},"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}],"description":"The first build's recipe"},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The goal the pick moves"},"intent":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"Every pick in order, build by build; empty for everything"},"setup":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"A goal's set-up in its order, uncapped: every open row to connect, answer or launch before the recommendations are read (the Plan shows these first, with one Approve). Empty outside a goal"},"launched":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"What the set-up stood up, each with what it writes (a phantom's automations, the main blog's); outside the cap. Empty outside a goal"},"setup_open":{"type":"boolean","description":"The set-up still has a row to do: the picks were planned without what it reads. Do the set-up, then plan again"},"goal_done":{"type":"number","description":"How many of the goal's own moves are done; 0 outside a goal"},"ground":{"anyOf":[{"type":"string","enum":["answers","search"]},{"type":"null"}],"description":"What the goal's starter rows read from: the assistants' answers (GEO) or Search Console (SEO); null otherwise"},"bound":{"description":"What the goal lets the plan stand up: picks hold to it; pass it to propose_primitive_plan as `primitives`. Null or absent: anything","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"builds":{"type":"array","items":{"type":"object","properties":{"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}]},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"changes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"What it builds"},"instructions":{"type":"string","description":"For the build sandbox: what to build or fix, where, with what content"},"why":{"type":"string"},"host":{"description":"The site it changes; null: the organization's main site","type":["string","null"]},"primitives_access":{"type":"boolean","description":"The planner judged the sandbox must read Letter primitives (read-only) to build it"},"reference_files":{"description":"Of the files attached to the ask (`files`), the ones this change only reads (a screenshot or mockup to follow, a brief): pass them to its start_growth_build change with reference: true. The rest go on the site","type":"array","items":{"type":"string"}}},"required":["title","instructions","why","host","primitives_access"]},"description":"Site changes the plan proposes for what no action does; built by a sandbox"}},"required":["recipe","picks","changes"]},"description":"The growth builds, one per recipe chosen (one for a prompt), each with its picks and site changes; start one with start_growth_build (action_ids + changes)"},"unmet":{"description":"What was asked for that no action does","type":["string","null"]},"unmet_kind":{"description":"With unmet: code when a change to their own code would do it, non_code when nothing in code does","anyOf":[{"type":"string","enum":["code","non_code"]},{"type":"null"}]},"more":{"description":"Valid picks past the cap: chosen, not shown","type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"fit_waiting":{"description":"A new account's strategies, not decided yet: what their first fit still waits on, in plain words. Ask again in a minute; null or absent once decided","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"stand_up":{"description":"What the member's own words asked to stand up now (phantom sites, say); null when they asked for none. start_build passes it to start_growth_build as `stand_up`, so the stack goes up without waiting for its review in Plan","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"model":{"type":["string","null"]},"fallback":{"description":"Set when the pick fell back to the catalog's ranking, and why","type":["string","null"]}},"required":["by","recipe","prompt","goal","intent","picks","setup","launched","setup_open","goal_done","ground","builds","unmet","model","fallback"]},"generated_at":{"type":"string"}},"required":["domain","company_name","description","chapters","actions","dismissed","selection","generated_at"]},{"type":"null"}]}},"required":["status","domain","error","plan"]}},{"name":"ask_letterpress","description":"ASK LETTERPRESS, the one door (docs/architecture/one-recipe-engine.md §9b): what should this account do next. Start here for any what-to-do ask. For an outcome (`outcome`: the Plan's goal, as the Plan tab's box asks it; `other` is any strategy across every outcome) or in the member's own words (`words`), the answer is the strategies staff wrote that fit this account, in staff's order, under the outcome's set-up rows: a strategy fits when its conditions hold for this company, decided from the facts the account already holds; nothing fitting comes back as `unmet`. With words the answer is every strategy that fits, for the caller to keep the ones that answer the words; for an ask that points outside the account (a case study, a competitor), read it first and pass what you found as `found`, then map each finding onto a strategy that does it or a tool that does. On the site (`lens`: fix for what's broken, most pressing first; build for what the site should build) it reads what the passes already hold. What is broken or what to fix on the site is always `lens: fix`, never `words`: words read only the strategies, and a strategy the account has not done is not something broken. Nothing asked is each list's head. Every item is a title, a why in the account's numbers, and the one call that does it (`action`, with whether this key may make it; `blocked_by` says what it waits on). A first read of a new domain can take a minute: status pending means ask again shortly. The same answer as the console's tabs, the terminal and Slack: `letterstory ask`. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"outcome":{"description":"A Plan goal: the plan for that outcome (geo, seo, …).","type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"lens":{"description":"What's broken on the site (fix), or what the site should build (build).","type":"string","enum":["build","fix"]},"words":{"description":"The ask in the member's own words. When it leans on the conversation ('these findings', 'that competitor'), restate it to stand alone, naming what it refers to.","type":"string","minLength":1,"maxLength":2000},"found":{"description":"With words, when the ask points outside the account (a case study, a competitor, a trend): what you read for it, as short findings, each with its numbers exactly as the source states them; for a follow-up on such an answer ('act on these findings'), what that answer found. Recorded on the run beside the words; the answer is every strategy that fits this account, to map each finding onto.","type":"string","minLength":1,"maxLength":4000},"refresh":{"description":"With an outcome: decide every strategy again from the facts as they are now, instead of reading the fit kept for this account (the standing fit). Default false.","type":"boolean"},"domain":{"description":"The site; the organization's own when omitted.","type":"string","minLength":1,"maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ask":{"type":"object","properties":{"outcome":{"type":"string"},"lens":{"type":"string","enum":["build","fix"]},"words":{"type":"string"}}},"kind":{"type":"string","enum":["suggestion","next"]},"status":{"type":"string","enum":["ready","pending"],"description":"pending: the site is still being read for the first time; `items` are what is in so far, `waiting_on` says what is still coming. Ask again in a minute"},"waiting_on":{"description":"With pending: what is still being read, in plain words (\"checking acme.com's pages (45 of 100 read)\")","type":"array","items":{"type":"string"}},"items":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","enum":["plan","build","fix"],"description":"Where it comes from: the plan, the site's builds, or its fixes"},"id":{"type":"string"},"title":{"type":"string"},"why":{"type":"string","description":"Why, in the member's own numbers and words"},"status":{"type":"string","enum":["ready","blocked","running","done"]},"action":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"callable":{"type":"boolean"},"href":{"type":["string","null"]}},"required":["tool","args","callable","href"]},{"type":"null"}],"description":"The one call that does it; null when a person does it where href points"},"blocked_by":{"type":"array","items":{"type":"string"},"description":"What it waits on, in plain words"},"needs":{"description":"The same waits as structured needs (what, where a person supplies it, the tool, how in place); plan rows that wait on something only","type":"array","items":{"type":"object","properties":{"need":{"type":"string","description":"github, site_repo, visitors_script, or an onboarding fact (positioning_set, …)"},"label":{"type":"string","description":"What is missing, as a member reads it"},"href":{"description":"The app page where a person supplies it","type":["string","null"]},"tool":{"description":"The tool that supplies it, for an agent","type":["string","null"]},"input":{"anyOf":[{},{"type":"null"}]}},"required":["need","label","href","tool"]}},"evidence":{"description":"What it rests on, in the account's own terms (a quote and its page, a count, a rival); absent when it carries none","type":"array","items":{"type":"string"}},"serves":{"description":"With words: how directly it moves what was asked, 0..1, as the judge read it (items are ordered by it); absent when nothing was read","type":"number"}},"required":["from","id","title","why","status","action","blocked_by"]}},"unmet":{"description":"What was asked that nothing here does, in one plain sentence","type":["string","null"]},"unmet_kind":{"anyOf":[{"type":"string","enum":["code","non_code"]},{"type":"null"}],"description":"With unmet: code when a change to their own code would do it (ours to build), non_code when nothing in code does"},"message":{"type":"string"},"run_id":{"description":"The run this answer is recorded as, for staff","type":["string","null"]}},"required":["ask","kind","status","items","unmet","unmet_kind","message","run_id"]}},{"name":"start_plan_run","description":"PLAN A GOAL, DURABLY — what choosing a goal in Instances → Plan does. Records a plan run for `goal` on the organization's domain (or `domain`) and answers at once with it; the rest runs in the background on this key: the site is read, then the evidence the goal supplies is read side by side and waited for (every goal: the newest site audit of the main site, started here when there is none fresh and waited for while it crawls; GEO: the ranking targets and what the assistants answered on them, share of voice included; SEO: Search Console's own queries), then the plan is picked (propose_account_plan, grounded in what was read, each row's why naming the gap or strength it rests on), one batch. Watch it with get_plan_run until `state` is ready (its `plan`) or failed (its `error`); `stage` and `evidence` say what it is doing. A run of this goal still going is joined (`joined`), never doubled; a ready run younger than ten minutes is handed back as it is unless `again`. Nothing is created in the account: the audit it may start is held to this key's own audit:write. Creates nothing a person must undo. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"goal":{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"],"description":"The outcome to plan for (the Plan's goal boxes): agent_conviction, geo, seo, inbound_conversion, or other (with `prompt`). outbound is not available yet."},"prompt":{"description":"With goal \"other\": the member's own goal, in their words. Ignored for the named goals.","type":"string","minLength":1,"maxLength":2000},"domain":{"description":"The site to plan for. Defaults to the organization's own domain.","type":"string","minLength":1},"refresh":{"description":"Decide every strategy again from the facts as they are now, instead of reading the fit kept for this account (the standing fit). Default false.","type":"boolean"},"again":{"description":"Plan again: a ready run of this goal younger than ten minutes is handed back as it is unless this is true. A run still going is always joined, never doubled.","type":"boolean"}},"required":["goal"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"goal":{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"prompt":{"description":"The member's own words (goal other); null otherwise","type":["string","null"]},"state":{"type":"string","enum":["gathering","planning","ready","failed"],"description":"gathering (reading the site and the evidence) → planning (the pick) → ready (plan set) | failed (error set). A live run silent for two minutes, or older than ten, reads as failed"},"stage":{"description":"The step under way: site, an evidence source's id, or planning; null once finished","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The source: site_audit, ranking_targets, ai_answers, search_console, …"},"label":{"type":"string","description":"What the member sees while it is read: \"Running the site audit\""},"status":{"type":"string","enum":["waiting","ready","empty","unavailable","failed"],"description":"waiting: still being read (an audit crawling, probes being asked); ready: read; empty: read, nothing to say yet; unavailable: not there (`need` says what supplies it); failed: couldn't be read"},"line":{"type":"string","description":"What was read, in one line"},"detail":{"type":"array","items":{"type":"string"},"description":"The detail behind the line, a line each: what the planner was handed"},"need":{"description":"Unavailable: what supplies it, in a line; the plan's row for it is the move","type":["string","null"]}},"required":["id","label","status","line","detail","need"]},"description":"What was read, one entry per source, as it moves"},"plan":{"anyOf":[{"type":"object","properties":{"domain":{"type":"string"},"company_name":{"type":"string"},"description":{"type":"string"},"chapters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"summary":{"type":"string"}},"required":["id","title","summary"]}},"actions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable across reads, e.g. blog:collection, instances:stack"},"chapter":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"detail":{"type":"string","description":"What doing it does"},"why":{"type":"string"},"impact":{"type":"number","description":"1–10"},"status":{"type":"string","enum":["ready","blocked","running","done"]},"blocked_by":{"type":"array","items":{"type":"object","properties":{"need":{"type":"string","description":"github, site_repo, visitors_script, or an onboarding fact (positioning_set, …)"},"label":{"type":"string","description":"What is missing, as a member reads it"},"href":{"description":"The app page where a person supplies it","type":["string","null"]},"tool":{"description":"The tool that supplies it, for an agent","type":["string","null"]}},"required":["need","label","href","tool"]}},"tool":{"type":"string","description":"The tool that performs it"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"The exact arguments, when the account determines them; null when a person supplies input"},"batch":{"description":"The array argument that merges across actions calling the same tool (one call for several)","type":["string","null"]},"prepare":{"type":"array","items":{"type":"string"},"description":"Read-only tools worth calling first"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"The primitives doing it stands up; empty when it creates none. A goal's `bound` holds the pick"},"callable":{"type":"boolean","description":"This key may call `tool` itself; false means a person does it at `href`"},"href":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"},"value":{"type":"string"}},"required":["label"]}},"words":{"description":"On an action that does what the member's words describe (a cold-email campaign, a list of people): the argument their ask fills (`text`) and the one the number of people it names fills (`count`, at most `max`). Started from start_growth_build with the build's `prompt`","type":"object","properties":{"text":{"type":"string"},"chars":{"type":"number"},"count":{"type":"string"},"max":{"type":"number"}},"required":["text","chars"]},"letterbase":{"description":"The letterbase (collection) this row stands up or writes on, once it exists: pass `id` as collection_id to list_automations (what it writes) and list_collection_runs (its posts)","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},"follow":{"description":"Where what its call started is followed: `href` with `{id}` read from the answer at path `id`","type":"object","properties":{"id":{"type":"string"},"href":{"type":"string"}},"required":["id","href"]},"required":{"description":"On a goal's set-up: the results wait on this row (the GitHub connection); every other row is additive","type":"boolean"},"stage":{"description":"On a goal's plan: setup (connect, answer, launch; done first and all at once), launched (what the set-up stood up, with what it writes) or result (the strategies that fit, in staff's order)","type":"string","enum":["setup","launched","result"]}},"required":["id","chapter","title","detail","why","impact","status","blocked_by","tool","args","batch","prepare","primitives","callable","href","evidence"]}},"dismissed":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"dismissed_by":{"description":"The user who dismissed it","type":["string","null"]},"dismissed_at":{"type":"string"},"kind":{"description":"later: for the dismissal window; never: at any age.","type":"string","enum":["later","never"]}},"required":["action_id","dismissed_by","dismissed_at"]},"description":"Actions a member dismissed on this site (dismiss_plan_action); Set up leaves them out"},"selection":{"type":"object","properties":{"by":{"type":"string","enum":["everything","recipe","prompt"],"description":"Who chose what comes first"},"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}],"description":"The first build's recipe"},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The goal the pick moves"},"intent":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"Every pick in order, build by build; empty for everything"},"setup":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"A goal's set-up in its order, uncapped: every open row to connect, answer or launch before the recommendations are read (the Plan shows these first, with one Approve). Empty outside a goal"},"launched":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"What the set-up stood up, each with what it writes (a phantom's automations, the main blog's); outside the cap. Empty outside a goal"},"setup_open":{"type":"boolean","description":"The set-up still has a row to do: the picks were planned without what it reads. Do the set-up, then plan again"},"goal_done":{"type":"number","description":"How many of the goal's own moves are done; 0 outside a goal"},"ground":{"anyOf":[{"type":"string","enum":["answers","search"]},{"type":"null"}],"description":"What the goal's starter rows read from: the assistants' answers (GEO) or Search Console (SEO); null otherwise"},"bound":{"description":"What the goal lets the plan stand up: picks hold to it; pass it to propose_primitive_plan as `primitives`. Null or absent: anything","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"builds":{"type":"array","items":{"type":"object","properties":{"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}]},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"changes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"What it builds"},"instructions":{"type":"string","description":"For the build sandbox: what to build or fix, where, with what content"},"why":{"type":"string"},"host":{"description":"The site it changes; null: the organization's main site","type":["string","null"]},"primitives_access":{"type":"boolean","description":"The planner judged the sandbox must read Letter primitives (read-only) to build it"},"reference_files":{"description":"Of the files attached to the ask (`files`), the ones this change only reads (a screenshot or mockup to follow, a brief): pass them to its start_growth_build change with reference: true. The rest go on the site","type":"array","items":{"type":"string"}}},"required":["title","instructions","why","host","primitives_access"]},"description":"Site changes the plan proposes for what no action does; built by a sandbox"}},"required":["recipe","picks","changes"]},"description":"The growth builds, one per recipe chosen (one for a prompt), each with its picks and site changes; start one with start_growth_build (action_ids + changes)"},"unmet":{"description":"What was asked for that no action does","type":["string","null"]},"unmet_kind":{"description":"With unmet: code when a change to their own code would do it, non_code when nothing in code does","anyOf":[{"type":"string","enum":["code","non_code"]},{"type":"null"}]},"more":{"description":"Valid picks past the cap: chosen, not shown","type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"fit_waiting":{"description":"A new account's strategies, not decided yet: what their first fit still waits on, in plain words. Ask again in a minute; null or absent once decided","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"stand_up":{"description":"What the member's own words asked to stand up now (phantom sites, say); null when they asked for none. start_build passes it to start_growth_build as `stand_up`, so the stack goes up without waiting for its review in Plan","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"model":{"type":["string","null"]},"fallback":{"description":"Set when the pick fell back to the catalog's ranking, and why","type":["string","null"]}},"required":["by","recipe","prompt","goal","intent","picks","setup","launched","setup_open","goal_done","ground","builds","unmet","model","fallback"]},"generated_at":{"type":"string"}},"required":["domain","company_name","description","chapters","actions","dismissed","selection","generated_at"]},{"type":"null"}],"description":"The plan (propose_account_plan's shape), once the run is ready"},"error":{"description":"Why it failed, in a line","type":["string","null"]},"started_by":{"description":"The member who asked; null for an agent's key with no person","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string","description":"The runner's last write: its heartbeat"},"finished_at":{"type":["string","null"]}},"required":["id","domain","goal","prompt","state","stage","evidence","plan","error","started_by","created_at","updated_at","finished_at"]},"joined":{"type":"boolean","description":"True when a run of this goal was already going (or ready and fresh) and was handed back rather than started"}},"required":["run","joined"]}},{"name":"list_plan_runs","description":"The organization's plan runs, newest first — what Instances → Plan's Planning panel lists: each with its goal, state (gathering, planning, ready, failed), the stage under way, the sources read so far (each line, without its detail) and what the plan chose once ready (its picks, set-up and launched rows). `domain` keeps one site's, `goal` one goal's, `id` one run. Poll it to watch runs started with start_plan_run; get_plan_run reads one whole (the full plan and every source's detail). Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"description":"One run.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"domain":{"description":"Only runs for this site; default every site.","type":"string","minLength":1},"goal":{"description":"Only runs of this goal; default every goal.","type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"limit":{"description":"Newest first; default 20.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"goal":{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"prompt":{"description":"The member's own words (goal other); null otherwise","type":["string","null"]},"state":{"type":"string","enum":["gathering","planning","ready","failed"],"description":"gathering (reading the site and the evidence) → planning (the pick) → ready (plan set) | failed (error set). A live run silent for two minutes, or older than ten, reads as failed"},"stage":{"description":"The step under way: site, an evidence source's id, or planning; null once finished","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The source: site_audit, ranking_targets, ai_answers, search_console, …"},"label":{"type":"string","description":"What the member sees while it is read: \"Running the site audit\""},"status":{"type":"string","enum":["waiting","ready","empty","unavailable","failed"],"description":"waiting: still being read (an audit crawling, probes being asked); ready: read; empty: read, nothing to say yet; unavailable: not there (`need` says what supplies it); failed: couldn't be read"},"line":{"type":"string","description":"What was read, in one line"},"need":{"description":"Unavailable: what supplies it, in a line; the plan's row for it is the move","type":["string","null"]}},"required":["id","label","status","line","need"]},"description":"What was read, one entry per source, as it moves; the detail behind each line is on get_plan_run"},"plan":{"anyOf":[{"type":"object","properties":{"selection":{"type":"object","properties":{"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"Every pick in order"},"setup":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"A goal's set-up in its order"},"launched":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"What the set-up stood up"}},"required":["picks","setup","launched"]}},"required":["selection"]},{"type":"null"}],"description":"What the plan chose, once the run is ready; the whole plan (actions, changes, why lines) is on get_plan_run"},"error":{"description":"Why it failed, in a line","type":["string","null"]},"started_by":{"description":"The member who asked; null for an agent's key with no person","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string","description":"The runner's last write: its heartbeat"},"finished_at":{"type":["string","null"]}},"required":["id","domain","goal","prompt","state","stage","evidence","plan","error","started_by","created_at","updated_at","finished_at"]}}},"required":["runs"]}},{"name":"get_plan_run","description":"One plan run (start_plan_run), as it stands: its state, the stage under way, every evidence source with its status, line and detail, and the plan once it is ready — everything the Plan shows while a goal is planned. Poll it every few seconds while `state` is gathering or planning. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run (start_plan_run, list_plan_runs)."}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"goal":{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"prompt":{"description":"The member's own words (goal other); null otherwise","type":["string","null"]},"state":{"type":"string","enum":["gathering","planning","ready","failed"],"description":"gathering (reading the site and the evidence) → planning (the pick) → ready (plan set) | failed (error set). A live run silent for two minutes, or older than ten, reads as failed"},"stage":{"description":"The step under way: site, an evidence source's id, or planning; null once finished","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The source: site_audit, ranking_targets, ai_answers, search_console, …"},"label":{"type":"string","description":"What the member sees while it is read: \"Running the site audit\""},"status":{"type":"string","enum":["waiting","ready","empty","unavailable","failed"],"description":"waiting: still being read (an audit crawling, probes being asked); ready: read; empty: read, nothing to say yet; unavailable: not there (`need` says what supplies it); failed: couldn't be read"},"line":{"type":"string","description":"What was read, in one line"},"detail":{"type":"array","items":{"type":"string"},"description":"The detail behind the line, a line each: what the planner was handed"},"need":{"description":"Unavailable: what supplies it, in a line; the plan's row for it is the move","type":["string","null"]}},"required":["id","label","status","line","detail","need"]},"description":"What was read, one entry per source, as it moves"},"plan":{"anyOf":[{"type":"object","properties":{"domain":{"type":"string"},"company_name":{"type":"string"},"description":{"type":"string"},"chapters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"summary":{"type":"string"}},"required":["id","title","summary"]}},"actions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable across reads, e.g. blog:collection, instances:stack"},"chapter":{"type":"string","enum":["blog","visitors","proofs","instances","outbound","site","strategy"]},"title":{"type":"string"},"detail":{"type":"string","description":"What doing it does"},"why":{"type":"string"},"impact":{"type":"number","description":"1–10"},"status":{"type":"string","enum":["ready","blocked","running","done"]},"blocked_by":{"type":"array","items":{"type":"object","properties":{"need":{"type":"string","description":"github, site_repo, visitors_script, or an onboarding fact (positioning_set, …)"},"label":{"type":"string","description":"What is missing, as a member reads it"},"href":{"description":"The app page where a person supplies it","type":["string","null"]},"tool":{"description":"The tool that supplies it, for an agent","type":["string","null"]}},"required":["need","label","href","tool"]}},"tool":{"type":"string","description":"The tool that performs it"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"The exact arguments, when the account determines them; null when a person supplies input"},"batch":{"description":"The array argument that merges across actions calling the same tool (one call for several)","type":["string","null"]},"prepare":{"type":"array","items":{"type":"string"},"description":"Read-only tools worth calling first"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"The primitives doing it stands up; empty when it creates none. A goal's `bound` holds the pick"},"callable":{"type":"boolean","description":"This key may call `tool` itself; false means a person does it at `href`"},"href":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"},"value":{"type":"string"}},"required":["label"]}},"words":{"description":"On an action that does what the member's words describe (a cold-email campaign, a list of people): the argument their ask fills (`text`) and the one the number of people it names fills (`count`, at most `max`). Started from start_growth_build with the build's `prompt`","type":"object","properties":{"text":{"type":"string"},"chars":{"type":"number"},"count":{"type":"string"},"max":{"type":"number"}},"required":["text","chars"]},"letterbase":{"description":"The letterbase (collection) this row stands up or writes on, once it exists: pass `id` as collection_id to list_automations (what it writes) and list_collection_runs (its posts)","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},"follow":{"description":"Where what its call started is followed: `href` with `{id}` read from the answer at path `id`","type":"object","properties":{"id":{"type":"string"},"href":{"type":"string"}},"required":["id","href"]},"required":{"description":"On a goal's set-up: the results wait on this row (the GitHub connection); every other row is additive","type":"boolean"},"stage":{"description":"On a goal's plan: setup (connect, answer, launch; done first and all at once), launched (what the set-up stood up, with what it writes) or result (the strategies that fit, in staff's order)","type":"string","enum":["setup","launched","result"]}},"required":["id","chapter","title","detail","why","impact","status","blocked_by","tool","args","batch","prepare","primitives","callable","href","evidence"]}},"dismissed":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"dismissed_by":{"description":"The user who dismissed it","type":["string","null"]},"dismissed_at":{"type":"string"},"kind":{"description":"later: for the dismissal window; never: at any age.","type":"string","enum":["later","never"]}},"required":["action_id","dismissed_by","dismissed_at"]},"description":"Actions a member dismissed on this site (dismiss_plan_action); Set up leaves them out"},"selection":{"type":"object","properties":{"by":{"type":"string","enum":["everything","recipe","prompt"],"description":"Who chose what comes first"},"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}],"description":"The first build's recipe"},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The goal the pick moves"},"intent":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"Every pick in order, build by build; empty for everything"},"setup":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"A goal's set-up in its order, uncapped: every open row to connect, answer or launch before the recommendations are read (the Plan shows these first, with one Approve). Empty outside a goal"},"launched":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]},"description":"What the set-up stood up, each with what it writes (a phantom's automations, the main blog's); outside the cap. Empty outside a goal"},"setup_open":{"type":"boolean","description":"The set-up still has a row to do: the picks were planned without what it reads. Do the set-up, then plan again"},"goal_done":{"type":"number","description":"How many of the goal's own moves are done; 0 outside a goal"},"ground":{"anyOf":[{"type":"string","enum":["answers","search"]},{"type":"null"}],"description":"What the goal's starter rows read from: the assistants' answers (GEO) or Search Console (SEO); null otherwise"},"bound":{"description":"What the goal lets the plan stand up: picks hold to it; pass it to propose_primitive_plan as `primitives`. Null or absent: anything","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"builds":{"type":"array","items":{"type":"object","properties":{"recipe":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","description":"Pass it to start_growth_build as `recipe`; recipes themselves are Letter's, not listed"},"reason":{"type":"string","description":"Why this approach fits the company, to the customer"}},"required":["id","reason"]},{"type":"null"}]},"picks":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"changes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"What it builds"},"instructions":{"type":"string","description":"For the build sandbox: what to build or fix, where, with what content"},"why":{"type":"string"},"host":{"description":"The site it changes; null: the organization's main site","type":["string","null"]},"primitives_access":{"type":"boolean","description":"The planner judged the sandbox must read Letter primitives (read-only) to build it"},"reference_files":{"description":"Of the files attached to the ask (`files`), the ones this change only reads (a screenshot or mockup to follow, a brief): pass them to its start_growth_build change with reference: true. The rest go on the site","type":"array","items":{"type":"string"}}},"required":["title","instructions","why","host","primitives_access"]},"description":"Site changes the plan proposes for what no action does; built by a sandbox"}},"required":["recipe","picks","changes"]},"description":"The growth builds, one per recipe chosen (one for a prompt), each with its picks and site changes; start one with start_growth_build (action_ids + changes)"},"unmet":{"description":"What was asked for that no action does","type":["string","null"]},"unmet_kind":{"description":"With unmet: code when a change to their own code would do it, non_code when nothing in code does","anyOf":[{"type":"string","enum":["code","non_code"]},{"type":"null"}]},"more":{"description":"Valid picks past the cap: chosen, not shown","type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"why":{"type":"string"}},"required":["action_id","why"]}},"fit_waiting":{"description":"A new account's strategies, not decided yet: what their first fit still waits on, in plain words. Ask again in a minute; null or absent once decided","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"stand_up":{"description":"What the member's own words asked to stand up now (phantom sites, say); null when they asked for none. start_build passes it to start_growth_build as `stand_up`, so the stack goes up without waiting for its review in Plan","anyOf":[{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},{"type":"null"}]},"model":{"type":["string","null"]},"fallback":{"description":"Set when the pick fell back to the catalog's ranking, and why","type":["string","null"]}},"required":["by","recipe","prompt","goal","intent","picks","setup","launched","setup_open","goal_done","ground","builds","unmet","model","fallback"]},"generated_at":{"type":"string"}},"required":["domain","company_name","description","chapters","actions","dismissed","selection","generated_at"]},{"type":"null"}],"description":"The plan (propose_account_plan's shape), once the run is ready"},"error":{"description":"Why it failed, in a line","type":["string","null"]},"started_by":{"description":"The member who asked; null for an agent's key with no person","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string","description":"The runner's last write: its heartbeat"},"finished_at":{"type":["string","null"]}},"required":["id","domain","goal","prompt","state","stage","evidence","plan","error","started_by","created_at","updated_at","finished_at"]}},"required":["run"]}},{"name":"dismiss_plan_action","description":"DISMISS A PLAN ACTION — what the ✕ on a row of Instances → Plan → Set up does. Takes an action id from propose_account_plan and records that a member doesn't want it on that site (`domain`, default the organization's own): Set up stops offering it and shows the next task instead. Nothing is undone or deleted; the action stays in propose_account_plan's `actions`, and `dismissed` lists who dismissed what and when. `dismissed: false` brings it back. Idempotent. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":200,"description":"The action's id from propose_account_plan (e.g. blog:collection, blog:series)."},"domain":{"description":"The site whose plan it is. Defaults to the organization's own domain.","type":"string","minLength":1},"dismissed":{"description":"false brings a dismissed action back. Default true.","type":"boolean"},"kind":{"description":"How long the no stands: later (default) for the dismissal window, then it may come back when the evidence changed; never at any age, and it is written to the organization's memory in words.","type":"string","enum":["later","never"]}},"required":["action_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":"string"},"action_id":{"type":"string"},"dismissed":{"anyOf":[{"type":"object","properties":{"action_id":{"type":"string"},"dismissed_by":{"description":"The user who dismissed it","type":["string","null"]},"dismissed_at":{"type":"string"},"kind":{"description":"later: for the dismissal window; never: at any age.","type":"string","enum":["later","never"]}},"required":["action_id","dismissed_by","dismissed_at"]},{"type":"null"}],"description":"Who dismissed it and when; null once it is brought back"}},"required":["domain","action_id","dismissed"]}},{"name":"start_growth_build","description":"START (APPROVE) A GROWTH BUILD — what Instances → Plan's Approve, Build's Plan & Build / Plan & Send to Slack and Fix run. Takes action ids from propose_account_plan (a build's `picks`, or any actions), its site `changes` and/or `fixes` (observations from get_site_tree / list_site_observations), records them as one build, and runs each one's call in order through this key's own tools, so it does nothing this key couldn't call itself: the actions first, then each site change as start_site_change (a build sandbox that only builds; `delivery` says preview first, straight to a PR, or Slack or Linear for the org's own agent, which also works with no repo connected), then each fix as fix_site_observations (Fix's per-row Fix and its Fix all / Fix selected are a build of `fixes` with kind fix: one fix_site_observations per host, so one pull request per site however many observations). Each step's `lane` in list_growth_builds says where its change stands: building, review (built; its preview waits on a person, the agent has stopped), pull_request, merged, or closed (closed unmerged: nothing reached the site). `goal` / `kind` (build or fix) are recorded; `notify_slack` posts the build and each change's preview and PR in one thread in the org's Slack channel. With `recipe` (a build's recipe.id) the steps are held to what that recipe may stand up; `prompt` records the member's words it answers. A step that needs a person (no arguments, blocked, or the common stack, which is reviewed in Plan first) is recorded as needs_input with where they do it. Returns the build once every call has gone out; started means the call went out and the work it began (an onboarding, a launch) carries on in its own primitive. Watch it with list_growth_builds. Requires capability build:start.","capability":"build:start","inputSchema":{"type":"object","properties":{"action_ids":{"description":"What to build: action ids from propose_account_plan (a build's `picks`, or any action). Each runs as its own call, in order; one that needs a person is recorded as needs_input. Omitted: none (a build of site changes only).","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":400}},"domain":{"description":"The site the plan was read for. Defaults to the organization's own domain.","type":"string","minLength":1},"recipe":{"description":"The recipe the build carries out (a build's recipe.id): recorded, and its primitives bound the steps.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"prompt":{"description":"The member's words the build answers (the Build tab), recorded on it.","type":"string","minLength":1,"maxLength":2000},"title":{"description":"What to call it, in three words (four at most), as Build's list shows it. Longer, or omitted, it is named in three words from this title or the prompt (a goal's build: \"<goal> plan\").","type":"string","minLength":1,"maxLength":120},"changes":{"description":"Omitted: none. Site changes to make after the actions (a build's `changes` from propose_account_plan): each runs as start_site_change, in order, so the primitives the actions set up exist before the sandbox builds on them.","maxItems":3,"type":"array","items":{"type":"object","properties":{"instructions":{"type":"string","minLength":10,"maxLength":8000,"description":"What to build or fix on the site, in plain words: what, where (the URL path, following the site's existing patterns), and what it should say. e.g. 'Add an index page at /blog/guides listing every guide post above the blog's post list.'"},"title":{"description":"A short title; it becomes the pull request's first line.","type":"string","minLength":1,"maxLength":120},"host":{"description":"The site to change (a mapped host). Defaults to the organization's own domain.","type":"string","minLength":1,"maxLength":253},"files":{"description":"Files the change needs (a brief, a screenshot, a CSV, an image to place), as start_repo_change takes them. The sandbox gets each at /data/run/files/<name> beside the site's brand.md and site.md; refer to them by name in the instructions. A `reference` file is read, never put in the repo. Only a change built in a repo gets them: slack, linear and handoff send the instructions alone.","maxItems":18,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"harness":{"description":"The coding agent that makes the change: claude-code (Claude Code) or letterpress-1 (Letter's own harness). Omitted: this deployment's default. Recorded on the run (run.harness), and a revision keeps it.","type":"string","enum":["claude-code","letterpress-1"]},"primitives_access":{"description":"Give the build sandbox READ-ONLY access to the organization's Letter primitives (its blog and posts, writing kernel, phantoms) through the MCP server, for changes that build on them. They must already be configured; the sandbox can never write one. Default false.","type":"boolean"},"again":{"description":"Start it even when an open change on the repo already asks for the same thing (otherwise that change comes back, to follow up on).","type":"boolean"},"covers":{"description":"The observation keys this change fixes (quick.missing-llms-txt), when it fixes ones Fix knows. While it runs Fix shows them as being fixed instead of offering a second fix, and once it merges the site is checked again for them. Omitted: a change Fix knows nothing about.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9._-]+$"}}},"required":["instructions"]}},"fixes":{"description":"Omitted: none. Problems with the site to fix after the actions and changes: each runs as fix_site_observations (one pull request for its observations), in order. What Fix runs.","maxItems":3,"type":"array","items":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"observation_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","minLength":1,"maxLength":600},"description":"The observations to fix, all on this host: stored ids or derived `key@path` ids from list_site_observations / get_site_tree. A group item's `members` are its ids."},"harness":{"description":"The coding agent that makes the change: claude-code (Claude Code) or letterpress-1 (Letter's own harness). Omitted: this deployment's default. Recorded on the run (run.harness), and a revision keeps it.","type":"string","enum":["claude-code","letterpress-1"]},"title":{"description":"What the fix is called. Defaults to the build's title.","type":"string","minLength":1,"maxLength":120}},"required":["host","observation_ids"]}},"stand_up":{"description":"What the member's own words asked to stand up now (propose_account_plan's selection.stand_up, e.g. [\"phantom\"] for \"launch 2-3 phantom sites\"): the common stack's step is held to those parts and stands them up in the build with stand_up_stack, spending what launching them costs but sending nothing (mailers still wait for their review in Plan). Omitted: the stack waits for its review in Plan.","maxItems":4,"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]}},"goal":{"description":"The Plan goal the build answers, recorded on it.","type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"kind":{"description":"build (default): something new. fix: a problem with what exists (the Fix tab).","type":"string","enum":["build","fix"]},"delivery":{"description":"How its site changes are made: build (preview first), build_pr (straight to a pull request) slack (posted to the org's Slack channel for its own agent) or linear (filed as a Linear issue for the agent that builds from Linear). With no repo that builds the site, slack and linear still work (the change is sent, nothing is built) and the others refuse. Defaults to the org's default action.","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"subjects":{"description":"The pages the build is about, so the views they came from show it in progress: `host/path` (no scheme, e.g. acme.com/blog/guides) `draft:<id>` for a suggested page (list_site_page_drafts), or `idea:<id>` for a page idea (list_page_ideas), which the build then accepts. Omitted: none.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":600}},"notify_slack":{"description":"Post the build and its progress (each change's preview, pull request, merge) in the org's Slack channel, in one thread, so the team can follow it without logging in.","type":"boolean"},"again":{"description":"Start its site changes even when an open change already asks for the same thing (start_site_change's `again`): the member said to build it again. Default false.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]}},"required":["build"]}},{"name":"list_growth_builds","description":"The organization's growth builds, newest first — what the Instances console's Build tab shows: each with how it was chosen (a Plan recipe, the member's words, or actions picked directly), its steps (each a plan action with its call and what it came back with) and a status read from them: building, failed, needs_input (a step waits on a person; its href says where), or started. `id` reads one. `domain` keeps one site's (what the console shows when the org has several). `archived: false` is Build's Open tab, `archived: true` its Archived tab (archive_growth_build); omitted, both. `id` also reads each started catalog step's `outcome`: whether what it began is live, waits on a person (`next` says what and where) or waits on something outside Letter, from the Plan's own reading of the account — a started step means the call went out, an outcome that is live means it works. Poll it to watch a build started with start_growth_build. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"description":"One build.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"Newest first; default 20.","type":"integer","minimum":1,"maximum":50},"kind":{"description":"Only builds (build) or only fixes (fix); default both.","type":"string","enum":["build","fix"]},"domain":{"description":"Only builds for this site (www. and a scheme are dropped); default every site.","type":"string","minLength":1,"maxLength":253},"archived":{"description":"false: only the open builds (Build's Open tab); true: only the archived ones (its Archived tab); default both.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"builds":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]}}},"required":["builds"]}},{"name":"archive_growth_build","description":"ARCHIVE A GROWTH BUILD — what Build's Archive (on a build's page, or swiping a build in the sidebar) does: the build leaves the Open list for Archived, and `archived: false` brings it back. Nothing archives a build on its own, not even a merged pull request: a person (or their agent) decides, and the build records when (`archived_at`) and who (`archived_by`). Archiving stops nothing: a change still building carries on, and its pull request stays where it is. Returns the build as list_growth_builds shows it. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The build (list_growth_builds)."},"archived":{"description":"true (or omitted) archives it; false brings it back to Open.","type":"boolean"}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]}},"required":["build"]}},{"name":"cancel_growth_build","description":"CANCEL A GROWTH BUILD BEFORE IT MERGES — what Cancel on a build's page and on a pending change in Letter Edit (⌘L on the organization's own site) does, for when the member changed their mind: each of its changes still being built is stopped, each preview discarded, and each pull request still open is closed unmerged with a comment saying who cancelled it (a merge asked for, auto-merge, a remake or a checks fix under way on it stop with it). Nothing of it merges after this. A change that already merged stays merged and is reported (`merged`): undoing it is a new change. A pull request the organization's own agent opened for a change sent to Slack or Linear is theirs to close. The build is not archived (archive_growth_build). Returns the build as list_growth_builds shows it, what happened to each change, and a sentence to say. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The build (list_growth_builds)."}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"],"description":"The build after cancelling, as list_growth_builds shows it"},"changes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"run_id":{"description":"The repo change cancelled (get_repo_change); null when it hadn't started","type":["string","null"]},"outcome":{"type":"string","enum":["stopped","closed","merged","starting","not_cancelled","ended"],"description":"stopped: it was being built or its preview was up, and no PR opens; closed: its pull request was closed unmerged; merged: it had already merged (undoing it is a new change); starting: its call hadn't gone out yet, cancel again in a moment; not_cancelled: see message; ended: nothing of it was pending"},"message":{"type":["string","null"]}},"required":["title","run_id","outcome","message"]},"description":"What cancelling did to each of its changes"},"message":{"type":"string","description":"What happened, in a sentence a person reads"}},"required":["build","changes","message"]}},{"name":"start_build","description":"BUILD FROM WORDS — what Letterpress-1's Build box and the letterpress terminal run when a member says what to build. One call: plans it from `prompt` (propose_account_plan, intent build, reading the site and account first) and starts the plan's first build (start_growth_build; when the plan picks nothing, the words themselves go to one change: Build never refuses words, delivered straight to a pull request unless `delivery` says otherwise), both through this key's own tools. `files` (a brief, a screenshot, a CSV; as start_repo_change takes them) shape the plan — it reads the start of each text file — and go to every site change the build makes, at /data/run/files/<name>: the ones the plan reads as something to follow (a screenshot, a mockup, a brief) as `reference`, never put in the repo, and the rest (an image to place) into the repo. Answers status started with the build (watch it with list_growth_builds and each change's get_repo_change_activity), pending while the site is read for the first time (call again in a few seconds), or duplicate with the open build that already asks the same (follow up on it with revise_repo_change, or pass again: true to build it anyway); `message` says why in a sentence a member can read. With `recipe` (a Plan row's), nothing is planned: the recipe is started as it stands, as one change to the site's code from `prompt` (the recipe's directions) or as the catalog action the recipe's `runs` names. Whatever did not start answers with `reason` (no_repo, already_running, site_not_ready, recipe_missing, recipe_parked, no_domain, action_refused, not_started, planner_nothing, planner_unmet) and `owner` (recipe, system, account or model). To talk it through first (questions about the account, Letterpress asking back), use send_build_message. Requires capability build:start.","capability":"build:start","inputSchema":{"type":"object","properties":{"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"What to build, in the member's words. Refer to attached files by name."},"domain":{"description":"The site the plan was read for. Defaults to the organization's own domain.","type":"string","minLength":1},"subjects":{"description":"The pages the build is about, so the views they came from show it in progress: `host/path` (no scheme, e.g. acme.com/blog/guides) `draft:<id>` for a suggested page (list_site_page_drafts), or `idea:<id>` for a page idea (list_page_ideas), which the build then accepts. Omitted: none.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":600}},"recipe":{"description":"The recipe the build carries out (a Plan row's): it is started as it stands, with no planner reading it again. `prompt` is its instruction (a Plan row sends the recipe's own directions); the recipe's `runs` is the catalog action it starts, else one change to the site's code. Recorded on the build.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"files":{"description":"Files the change needs (a brief, a screenshot, a CSV, an image to place), as start_repo_change takes them. The sandbox gets each at /data/run/files/<name> beside the site's brand.md and site.md; refer to them by name in the instructions. A `reference` file is read, never put in the repo. Only a change built in a repo gets them: slack, linear and handoff send the instructions alone.","maxItems":18,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"delivery":{"description":"How its site changes are made, as start_growth_build takes it. Default build_pr: built, then the pull request opened (Build's own).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"notify_slack":{"description":"Post the build and its progress in the org's Slack channel, in one thread. Default false.","type":"boolean"},"harness":{"description":"The coding agent that makes every site change of the build (the terminal's /model, the Build box's model picker): claude-code (Claude Code) or letterpress-1 (Letter's own harness). Omitted: this deployment's default.","type":"string","enum":["claude-code","letterpress-1"]},"again":{"description":"Build it even though an open build already asks for the same thing (status duplicate). Default false: a repeat of an open build's ask answers duplicate with that build, to follow up on instead.","type":"boolean"}},"required":["prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["started","pending","unmet","nothing","duplicate","failed"],"description":"started: the build is running (`build`); pending: the site is being read for the first time, call again in a few seconds; unmet: what was asked doesn't fit this site or isn't something a build does (`message` says what was noticed); nothing: say more about what to build; duplicate: an open build already asks for this (`build`): follow up on it (revise_repo_change), or call again with again: true; failed: the build was made (`build`) but every step failed as it went out (`message` says why, e.g. no connected repo): nothing is being built, and asking again won't help until that's fixed"},"build":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]},{"type":"null"}],"description":"The build it started (or, for duplicate, the open one asking the same; for failed, the one that started nothing); watch it with list_growth_builds"},"message":{"description":"Why nothing started, in a sentence a member can read","type":["string","null"]},"reason":{"description":"Why nothing started, as a fixed code: no_repo, already_running, site_not_ready, recipe_missing, recipe_parked, no_domain, action_refused, not_started, planner_nothing, planner_unmet, is_setting (the words ask for one of the account's settings, set where it lives, not built). Absent when it started.","type":"string","enum":["no_repo","already_running","site_not_ready","recipe_missing","recipe_parked","no_domain","action_refused","not_started","planner_nothing","planner_unmet","unmet_premise","is_setting","is_question"]},"owner":{"description":"Whose it is to fix: the recipe, the system, the account or the model. Absent when it started.","type":"string","enum":["recipe","system","account","model"]}},"required":["status","build","message"]}},{"name":"start_build_benchmark","description":"Staff only. BENCHMARK BUILD — one prompt, three agents, side by side (docs/architecture/build-benchmarks.md): Letterpress-1 as Build runs it (start_build: planned with the account and the site read, its site changes only, built as previews with the site's context and Letter's brand check and review), and stock Claude Code and stock Codex in the repo that builds the site, each given the prompt alone (no Letter rules, Letter MCP, brand check, review or site context) and served as a preview too. Nothing is merged: compare the previews (each arm's `runs[].preview_url`), the summaries, files, cost and agent time, and make a PR from any arm with publish_repo_change. `agents` runs fewer arms. Every arm is billed to the organization like any build. Watch it with list_build_benchmarks. Refused unless the key was created by Letter staff; the same as the Build box's Benchmark box and `letterstory build benchmark`. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"What to build, in the member's words: every arm gets exactly these. Refer to attached files by name."},"domain":{"description":"The site: the repo that builds it is where every arm works. Defaults to the organization's own domain.","type":"string","minLength":1},"files":{"description":"Files that go with the words (a brief, a screenshot), handed to every arm alike, as start_build takes them.","maxItems":18,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"agents":{"description":"Which arms to run: letterpress-1 (as Build runs it: planned, with Letter's context, brand check and review), claude-code and codex (each stock: the prompt alone, no Letter rules, MCP, brand check or review). Default all three.","minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["letterpress-1","claude-code","codex"]}}},"required":["prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"benchmark":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string","description":"What every arm was given"},"domain":{"description":"The site; its repo is where every arm worked","type":["string","null"]},"created_by":{"description":"The staff member who ran it","type":["string","null"]},"created_at":{"type":"string"},"status":{"type":"string","enum":["building","done"],"description":"building while any arm is; done once every arm stopped"},"arms":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","enum":["letterpress-1","claude-code","codex"]},"label":{"type":"string"},"stock":{"type":"boolean","description":"Ran as the agent ships: the prompt alone, no Letter rules, MCP, brand check, review or site context"},"build_id":{"description":"Letterpress-1's build (list_growth_builds)","type":["string","null"]},"run_id":{"description":"A stock arm's repo change","type":["string","null"]},"message":{"description":"Why it started nothing, or why its build started nothing","type":["string","null"]},"state":{"type":"string","enum":["building","review","pull_request","merged","closed","failed","done","not_started"],"description":"building; then the furthest a change got: review (a preview to look at), pull_request, merged, closed; failed; done (ended with nothing); not_started"},"build":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]},{"type":"null"}]},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The repo change (get_repo_change, get_repo_change_activity)"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"]},"model":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"]},"lane":{"anyOf":[{"type":"string","enum":["review","building","sent","pull_request","merged","closed"]},{"type":"null"}],"description":"Where it stands: building, review (its preview is up to look at), a pull request, merged, closed"},"preview_url":{"description":"The changed site, while it's served; opens on its first change","type":["string","null"]},"preview_state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"pr_url":{"description":"Its pull request, once someone made one (publish_repo_change)","type":["string","null"]},"cost_usd":{"type":"number","description":"Model spend, at list price"},"summary":{"description":"The agent's own summary of what it changed","type":["string","null"]},"files_changed":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"error":{"type":["string","null"]},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"agent_ms":{"description":"From the agent starting to its change collected: a warm or cold sandbox doesn't count","type":["number","null"]}},"required":["id","harness","model","status","phase","lane","preview_url","preview_state","pr_url","cost_usd","summary","files_changed","error","attention","started_at","completed_at","agent_ms"]},"description":"Its changes: a stock arm's one, or each site change of Letterpress-1's build"},"cost_usd":{"type":"number"},"agent_ms":{"description":"The longest of its changes' agent times","type":["number","null"]},"files_changed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["agent","label","stock","build_id","run_id","message","state","build","runs","cost_usd","agent_ms","files_changed"]}}},"required":["id","prompt","domain","created_by","created_at","status","arms"]}},"required":["benchmark"]}},{"name":"list_build_benchmarks","description":"Staff only. The organization's build benchmarks (start_build_benchmark), newest first, each arm read live: where it stands (building, review with its preview to look at, a PR, failed, not_started with why), its changes with their preview links, the agent's summary, the files, cost at list price and agent time, and for Letterpress-1 its build. `id` reads one. Refused unless the key was created by Letter staff; the same as the Build box's benchmark page and `letterstory build benchmarks`. Requires capability org_config:read.","capability":"org_config:read","inputSchema":{"type":"object","properties":{"id":{"description":"One benchmark.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"Newest first; default 10.","type":"integer","minimum":1,"maximum":20}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"benchmarks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prompt":{"type":"string","description":"What every arm was given"},"domain":{"description":"The site; its repo is where every arm worked","type":["string","null"]},"created_by":{"description":"The staff member who ran it","type":["string","null"]},"created_at":{"type":"string"},"status":{"type":"string","enum":["building","done"],"description":"building while any arm is; done once every arm stopped"},"arms":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","enum":["letterpress-1","claude-code","codex"]},"label":{"type":"string"},"stock":{"type":"boolean","description":"Ran as the agent ships: the prompt alone, no Letter rules, MCP, brand check, review or site context"},"build_id":{"description":"Letterpress-1's build (list_growth_builds)","type":["string","null"]},"run_id":{"description":"A stock arm's repo change","type":["string","null"]},"message":{"description":"Why it started nothing, or why its build started nothing","type":["string","null"]},"state":{"type":"string","enum":["building","review","pull_request","merged","closed","failed","done","not_started"],"description":"building; then the furthest a change got: review (a preview to look at), pull_request, merged, closed; failed; done (ended with nothing); not_started"},"build":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"Its name, in three words (four at most), as Build's list shows it; the words asked are `prompt`"},"domain":{"type":"string"},"source":{"type":"string","enum":["recipe","prompt","plan"],"description":"recipe: a Plan goal's pick; prompt: the member's words; plan: actions chosen directly"},"recipe_id":{"type":["string","null"]},"prompt":{"type":["string","null"]},"goal":{"anyOf":[{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},{"type":"null"}],"description":"The Plan goal it answers"},"kind":{"type":"string","enum":["build","fix"],"description":"build: something new; fix: a problem with what exists"},"delivery":{"anyOf":[{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},{"type":"null"}],"description":"How its site changes were made; null: the default"},"notify_slack":{"type":"boolean","description":"Its progress is posted in the org's Slack channel"},"subjects":{"type":"array","items":{"type":"string"},"description":"The pages it is about (host/path, or draft:<id>)"},"steps":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","description":"The propose_account_plan action it carries out; change:N for the build's Nth site change"},"title":{"type":"string"},"detail":{"type":"string"},"tool":{"type":"string"},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"What it was called with; null when a person supplies it"},"primitives":{"type":"array","items":{"type":"string","enum":["kernel","phantom","mailer","sequence"]},"description":"What it stands up"},"status":{"type":"string","enum":["scheduled","queued","running","started","needs_input","failed"],"description":"queued → running → started (the call went out) or failed; needs_input waits on a person at href"},"note":{"description":"What the call said back, or what it needs","type":["string","null"]},"href":{"description":"Where a person follows it up","type":["string","null"]},"scheduled_for":{"description":"Legacy: a page of a series scheduled by a build started before builds lost series (recurring posts are a collection automation now) runs at this time; new builds schedule nothing","type":["string","null"]},"calls":{"description":"Every call the step made driving its action, in order (an action is read again after each call and its next call made, so one approval is the whole action)","type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"at":{"type":"string"},"ok":{"type":"boolean","description":"false until it lands, and when it was refused"},"note":{"type":["string","null"]},"run_id":{"description":"The repo change it started, when it started one","type":"string"}},"required":["tool","args","at","ok","note"]}},"watch_at":{"description":"When its action is read again (it waits on a merge, a job or a person); null: not watched","type":["string","null"]},"watch_until":{"description":"When it stops waiting and says so","type":["string","null"]},"notes":{"description":"Each thing the step said while it was driven, with when, oldest first (a launch's stages as it went); `note` is the latest","type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"note":{"type":"string"}},"required":["at","note"]}},"landed":{"description":"Once its pull request merged, what the check of the live site made of it","anyOf":[{"type":"object","properties":{"state":{"type":"string","description":"waiting or checking (the live site not read yet), verified (it did what it was for), partial, still_there, timed_out, not_checked"},"detail":{"type":["string","null"]}},"required":["state","detail"]},{"type":"null"}]},"outcome":{"description":"Where what a started catalog step began stands now, read live from the account (the Plan's reading of the action) when one build is read with `id`; absent in a list. A build whose steps are all started is not live until every outcome is: started means the call went out, nothing more","anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["live","yours","waiting"],"description":"live: it is doing its job; yours: it waits on a person (next says what and where); waiting: it waits on Letter or on something outside it (a pull request, a first visit)"},"says":{"type":"string","description":"What stands now, in a sentence"},"why":{"type":"string","description":"What it is for"},"next":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"What a person does next, and where; only when state is yours"},"where":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"Where its result shows once it is live"},"letterbase":{"description":"The letterbase it stood up or writes on (a third-party site's, the main blog's), once it exists: its collection id for list_automations and list_collection_runs","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name"]},{"type":"null"}]}},"required":["state","says","why","next","where"]},{"type":"null"}]},"lane":{"description":"Where the step's repo change stands, read live: being built, PR open, merged, closed unmerged, or failed before it opened a PR (the build then reads failed; get_repo_change_activity ends with why)","anyOf":[{"type":"string","enum":["building","review","sent","pull_request","merged","closed","failed"]},{"type":"null"}]},"error":{"description":"Why the step's change failed (its run's error), read live with the lane when the lane is failed","type":["string","null"]},"run_id":{"description":"The step's repo change as it stands now (get_repo_change, get_repo_change_activity, send_repo_change_message): the run it started, or after a follow-up (revise_repo_change) the latest preview revision, or the follow-up on its open PR while that is built","type":["string","null"]},"pr_url":{"description":"That repo change's pull request, once it has one; watch the build with get_repo_change_activity","type":["string","null"]},"verifier":{"description":"What the verifier made of that repo change (clean, open_issues, error, …), read live with the lane; open_issues: its preview or pull request has issues to look at before it merges","type":["string","null"]},"attention":{"description":"Set when the review left that repo change with blocking issues: \"Needs attention: …\" with the first issue's fix (get_repo_change's attention has every issue). A step with it is not ready to merge.","type":["string","null"]},"preview_url":{"description":"That repo change's preview while it's served (opened on the first change), read live with the lane: up next to its PR until it expires","type":["string","null"]},"merge":{"description":"Its pull request's merge, read live with the lane: null until someone asked or auto-merge took it up","anyOf":[{"type":"object","properties":{"requested_at":{"description":"When a person asked to merge it (merge_repo_change); null when only auto-merge took it up","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What merging it is doing (get_repo_change's autoMerge.state): waiting, syncing, fixing, rebuilding, blocked, merged, …"},"detail":{"description":"What it's doing, in a sentence","type":["string","null"]}},"required":["requested_at","state","detail"]},{"type":"null"}]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["action_id","title","detail","tool","args","primitives","status","note","href","started_at","finished_at"]}},"status":{"type":"string","enum":["building","needs_input","failed","scheduled","started"],"description":"Read from the steps: building, then failed, then needs_input, then scheduled (legacy: an older build's series still has pages due), else started. A started build whose steps have no `lane` made no repo change (a phantom launched, the blog onboarded): there is nothing to merge, and the app shows it as Done"},"archived_at":{"description":"When a person archived it (archive_growth_build); null while it is open. Nothing archives a build on its own","type":["string","null"]},"archived_by":{"description":"Who archived it; null while it is open","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"reading":{"description":"Where it stands, the same reading the Plan, Build and Fix show (status alone stays `started` once every call went out)","type":"object","properties":{"state":{"type":"string","enum":["scheduled","building","preview","sent","pr_open","merged","closed","needs_you","failed","ended","done"],"description":"Where its work stands, read from its steps and their repo changes: scheduled, building, preview, sent, pr_open, merged, closed, needs_you, failed, ended, or done (nothing to merge)"},"kind":{"type":"string","enum":["running","review","done","attention","again"],"description":"What that means for a person: running (being made), review (waiting on a person: a preview or a PR), done (landed), attention (needs a look: it failed, needs the member, the review left issues, or the merge is blocked), again (it ended without landing and can be started again)"},"detail":{"type":"string","description":"The sentence the app shows for it"}},"required":["state","kind","detail"]},"needs_you":{"description":"What it waits on a person for (a choice only they make, a PR to merge, something to connect); null when nothing. On every row, as the Plan shows it","anyOf":[{"type":"object","properties":{"says":{"type":"string","description":"What the member does, in the words the Plan shows"},"link":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"href":{"type":"string"}},"required":["label","href"]},{"type":"null"}],"description":"The one place to do it"}},"required":["says","link"]},{"type":"null"}]}},"required":["id","title","domain","source","recipe_id","prompt","goal","kind","delivery","notify_slack","subjects","steps","status","archived_at","archived_by","created_by","created_at","updated_at"]},{"type":"null"}]},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The repo change (get_repo_change, get_repo_change_activity)"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"]},"model":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"]},"lane":{"anyOf":[{"type":"string","enum":["review","building","sent","pull_request","merged","closed"]},{"type":"null"}],"description":"Where it stands: building, review (its preview is up to look at), a pull request, merged, closed"},"preview_url":{"description":"The changed site, while it's served; opens on its first change","type":["string","null"]},"preview_state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"pr_url":{"description":"Its pull request, once someone made one (publish_repo_change)","type":["string","null"]},"cost_usd":{"type":"number","description":"Model spend, at list price"},"summary":{"description":"The agent's own summary of what it changed","type":["string","null"]},"files_changed":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"error":{"type":["string","null"]},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"agent_ms":{"description":"From the agent starting to its change collected: a warm or cold sandbox doesn't count","type":["number","null"]}},"required":["id","harness","model","status","phase","lane","preview_url","preview_state","pr_url","cost_usd","summary","files_changed","error","attention","started_at","completed_at","agent_ms"]},"description":"Its changes: a stock arm's one, or each site change of Letterpress-1's build"},"cost_usd":{"type":"number"},"agent_ms":{"description":"The longest of its changes' agent times","type":["number","null"]},"files_changed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["agent","label","stock","build_id","run_id","message","state","build","runs","cost_usd","agent_ms","files_changed"]}}},"required":["id","prompt","domain","created_by","created_at","status","arms"]}}},"required":["benchmarks"]}},{"name":"send_build_message","description":"TALK TO BUILD — one turn of a conversation with Letterpress-1: what the console's Build box and a build's page, ⌘L on the customer's site, Slack, the letterpress terminal and an agent all send. `message` is what the member says: anything at all (Build answers every question, about the account or not, reading the account with this key's read tools before it states a fact about it), what to build, a change to a build the conversation holds, or the answer to what it asked. Letterpress answers in `reply`; asks back in `questions` when it can't act without it (answer with the next message); starts a build with start_build — the same Build, planned from the whole conversation, built as a preview first — and returns it in `build` (follow it with list_growth_builds and get_repo_change_activity); or follows up on a build it holds with the same tools the review panel uses (send_repo_change_message while it's built, revise_repo_change on a preview or an open PR, publish_repo_change to open the PR, cancel_repo_change), listed in `actions`. Asked to set up Proofs, it opens the install PR and lists `set_up_proofs` in `actions` with the install's run_id; with no Proofs vendor yet it returns `setup` (kind proofs, a `url` where the member creates it in the browser: open it, poll letterprove_get_vendor_link until linked, then ask again and the install PR opens); `repo` (owner/name, the repo of the folder the member works in, which the terminal sends) is where that install goes. `files` attach to this message (as start_repo_change takes them; upload big ones with create_repo_change_upload first) and every file attached in the conversation goes to the build it starts. Omit `conversation_id` to start a conversation (with `build_id`, it continues the one holding that build, or starts one about it); pass the returned one to continue it. A turn takes from a few seconds to a couple of minutes. Read conversations back with get_build_conversation and list_build_conversations. Requires capability build:start.","capability":"build:start","inputSchema":{"type":"object","properties":{"conversation_id":{"description":"The conversation to continue (from an earlier send_build_message). Omitted: start a new one.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"What the member says: what to build, a question about their account or a build, or the answer to what Letterpress asked."},"files":{"description":"Files attached to this message (a brief, a screenshot, a CSV), as start_repo_change takes them; upload big ones with create_repo_change_upload first. Every file attached in the conversation goes to the build it starts, at /data/run/files/<name>.","maxItems":18,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"domain":{"description":"The site the conversation is about. Defaults to the conversation's site, else the organization's own domain.","type":"string","minLength":1},"subjects":{"description":"The pages this message is about (a suggested page, the page ⌘L was pressed on): a build it starts records them, so the Site shows it on their rows.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":600}},"recipe":{"description":"The recipe this message carries out (a Plan row's, Show Suggestions'): recorded on the build it starts.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"build_id":{"description":"The build this message is about (a build's own page): with no conversation_id, it continues the conversation that holds that build, or starts one about it, so follow-ups (revise, steer, open the PR) act on it.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"repo":{"description":"The repo of the folder the member is working in (the terminal sends it from its git remote), whether or not it builds a site: \"owner/name\" on GitHub, \"gitlab:group/subgroup/project\" on GitLab. Kept with the conversation; an install into the member's product (Proofs) goes there. A repo Letter can't reach (the GitHub App, or the GitLab connection) is said back plainly when it's used, never an error here.","type":"string","pattern":"^(?:[A-Za-z0-9][A-Za-z0-9-]{0,38}\\/[A-Za-z0-9._-]{1,100}|gitlab:[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){1,20})$"},"harness":{"description":"The coding agent a build this message starts uses (the terminal's /model): claude-code or letterpress-1. Omitted: this deployment's default.","type":"string","enum":["claude-code","letterpress-1"]},"delivery":{"description":"How a build this message starts makes its site changes: build (default: a preview first; follow-ups revise it and the pull request opens when the member says), build_pr (straight to a pull request), or build_merge (a pull request merged once it's ready: up to date, the repo's checks passing; cancel_growth_build stops it before then). Letter Edit (⌘L) sends build_merge when the organization turned its auto-merge on (set_repo_change_settings editor_auto_merge), else build_pr.","type":"string","enum":["build","build_pr","build_merge"]}},"required":["message"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"conversation_id":{"type":"string","description":"Pass it back to continue the conversation"},"reply":{"type":"string","description":"What Letterpress says"},"question":{"description":"What Letterpress asks back, only when it can't start a build without it; answer with the next send_build_message","type":["string","null"]},"questions":{"type":"array","items":{"type":"object","properties":{"header":{"type":"string","description":"A short label for the question (a tab or step name)"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":["string","null"]}},"required":["label","description"]},"description":"2–4 answers to pick, the recommended one first; [] for an open question"}},"required":["header","question","options"]},"description":"Everything Letterpress asks back (1–4), to answer one at a time and send together in the next send_build_message; `question` is the first. [] when nothing is asked"},"build":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}},"required":["id","title"]},{"type":"null"}],"description":"The build this turn started; follow it with list_growth_builds and get_repo_change_activity"},"actions":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string","description":"send_repo_change_message (steered), revise_repo_change (asked for more on a preview or an open PR), publish_repo_change (opened the PR), cancel_repo_change, or set_up_proofs (started the Proofs install; run_id is its repo change)"},"run_id":{"description":"The change it acted on; follow it with get_repo_change","type":["string","null"]},"ok":{"type":"boolean"}},"required":["tool","run_id","ok"]},"description":"What this turn did to the conversation's builds; [] when nothing"},"setup":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"proofs"},"url":{"type":"string","description":"The Proofs crossing for this org (/crossing/proofs?from=build&org=…), absolute: open it in the browser"}},"required":["kind","url"]},{"type":"null"}],"description":"A step only the member can take, in the browser: with no Proofs vendor yet, set_up_proofs returns where they create it. Open `url`, poll letterprove_get_vendor_link until `linked`, then send a message asking for the install in this conversation; it opens the install PR. Null when none"}},"required":["conversation_id","reply","question","questions","build","actions","setup"]}},{"name":"get_build_conversation","description":"One conversation with Build (send_build_message), every message oldest first: who said it (member or letterpress), what was said, what Letterpress asked back, the build a reply started (`build_id`, for list_growth_builds) and the files attached. Name it by `conversation_id`, or by a `build_id` it holds (what a build's page shows: the conversation that started the build, follow-ups and all). Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"conversation_id":{"description":"The conversation (from send_build_message or list_build_conversations). Give this or build_id.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"build_id":{"description":"A build: the conversation that holds it (the one a reply of which started it, else one opened on its page). Give this or conversation_id.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"conversation":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"The first line of the first message"},"domain":{"description":"The site it is about; null: the organization's own","type":["string","null"]},"repo":{"description":"The GitHub repo (\"owner/name\") the member is working in; null when none was sent","type":["string","null"]},"build_id":{"description":"The build it was opened on (send_build_message build_id), when that build was started elsewhere","type":["string","null"]},"created_by":{"description":"Whose key started it: the member for the app, the terminal and the Build box; for a Slack thread, whoever connected Slack (see from_slack)","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string","description":"When it was last spoken in"},"from_slack":{"description":"A Slack thread holds it, so created_by isn't the person who wrote in it","type":"boolean"},"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"role":{"type":"string","enum":["member","letterpress"],"description":"member: the person or their agent; letterpress: the reply"},"body":{"type":"string"},"question":{"description":"What Letterpress asked back before building","type":["string","null"]},"build_id":{"description":"The growth build this reply started (list_growth_builds)","type":["string","null"]},"files":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"},"upload_path":{"type":"string"},"reference":{"type":"boolean"}},"required":["name"]},"description":"Files the member attached"},"created_at":{"type":"string"}},"required":["id","role","body","question","build_id","files","created_at"]},"description":"Oldest first"}},"required":["id","title","domain","repo","build_id","created_by","created_at","updated_at","messages"]}},"required":["conversation"]}},{"name":"list_build_conversations","description":"The organization's conversations with Build (send_build_message, the letterpress terminal), most recently active first: each with its title (its first line), the site it is about and when it was last spoken in. Read one with get_build_conversation. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"limit":{"description":"Most recently active first; default 20.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"conversations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","description":"The first line of the first message"},"domain":{"description":"The site it is about; null: the organization's own","type":["string","null"]},"repo":{"description":"The GitHub repo (\"owner/name\") the member is working in; null when none was sent","type":["string","null"]},"build_id":{"description":"The build it was opened on (send_build_message build_id), when that build was started elsewhere","type":["string","null"]},"created_by":{"description":"Whose key started it: the member for the app, the terminal and the Build box; for a Slack thread, whoever connected Slack (see from_slack)","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string","description":"When it was last spoken in"},"from_slack":{"description":"A Slack thread holds it, so created_by isn't the person who wrote in it","type":"boolean"}},"required":["id","title","domain","repo","build_id","created_by","created_at","updated_at"]}}},"required":["conversations"]}},{"name":"list_recipes","description":"Staff only. Letter's recipes: direction for Letterpress, written by staff and never shown to customers, one library read on every lens (Plan, Build, Fix). Each has a name, a description (for a person), the recipe (the directions Letterpress reads), when_to_use, when_not_to_use, weight (1..10), `approaches` (what it applies to, tagged by staff: seo, geo, agent_conviction, inbound_conversion, outbound), `runs` (the catalog action it runs when started, for example visitors:site; empty: one change to the site's code from its own directions), `status` (live, or parked: an idea that cannot run yet, never handed to the fit and refused if asked for by id) with `needs` (one line: what is missing before a parked recipe can run) and `variations` (its other variations, each its own name, description, recipe, when_to_use and when_not_to_use; Letterpress follows one, never two). Pass `approach` for the recipes tagged with one approach (other: the recipes tagged with none), `status` for only the live or only the parked ones (the parked list is the backlog of what to build). Heaviest first. Pass a recipe's id as propose_account_plan's `recipe` to ground a pick in it alone. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"kind":{"description":"One list: recipe (the strategies) or known_issue (Fix's list); all (the default): every row.","type":"string","enum":["recipe","known_issue","all"]},"status":{"description":"Only the live recipes, or only the parked ones (ideas that cannot run yet); all (the default): both.","type":"string","enum":["live","parked","all"]},"approach":{"description":"Only the recipes tagged with one approach (a Plan goal: seo, geo, agent_conviction, inbound_conversion, outbound); other: the recipes tagged with none; all (the default): every recipe.","type":"string","enum":["all","seo","geo","agent_conviction","inbound_conversion","outbound","other"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","description":"What it does, for a person; empty until staff write it"},"recipe":{"type":"string","description":"The directions, in plain words: what Letterpress reads"},"when_to_use":{"type":"string"},"when_not_to_use":{"type":"string"},"weight":{"type":"number","description":"1..10: the one score's prior for a proposal that follows it"},"approaches":{"type":"array","items":{"type":"string","enum":["seo","geo","agent_conviction","inbound_conversion","outbound"]},"description":"What it applies to, tagged by staff (the Plan goals); empty: untagged"},"reads":{"type":"array","items":{"type":"string"},"description":"The facts its conditions rest on, by key (lib/recipes/fact-sources.ts); empty: not written yet"},"runs":{"type":"string","description":"The catalog action it runs when started (visitors:site); empty: one change to the site's code"},"status":{"type":"string","enum":["live","parked"],"description":"live: the fit reads it; parked: an idea that cannot run yet, never handed to the fit"},"needs":{"type":"string","description":"What is missing before a parked recipe can run, one line; empty when live"},"variations":{"type":"array","items":{"type":"object","properties":{"name":{"description":"The variation's title; empty or absent: the recipe's name","type":"string"},"description":{"description":"What the variation does, for a person","type":"string"},"recipe":{"type":"string","description":"The variation's directions, in plain words"},"when_to_use":{"type":"string"},"when_not_to_use":{"type":"string"}},"required":["recipe","when_to_use","when_not_to_use"]},"description":"Its other variations, each its own title and description; its own fields are the first"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","description","recipe","when_to_use","when_not_to_use","weight","approaches","reads","runs","status","needs","variations","created_by","updated_by","created_at","updated_at"]}}},"required":["recipes"]}},{"name":"save_recipe","description":"Staff only. Create a recipe (no id; name and recipe required) or change one (id + the fields to change). A recipe is: name (unique), description (what it does, for a person), recipe (the directions Letterpress reads, in plain words), when_to_use, when_not_to_use, weight (1..10, default 5), approaches (tags: seo, geo, agent_conviction, inbound_conversion, outbound), runs (the catalog action it runs when started, for example visitors:site; empty: one change to the site's code from its own directions), status (live, the default, or parked: an idea that cannot run yet, never handed to the fit) with needs (one line: what is missing before a parked recipe can run; say it whenever parking) and variations (its other variations, each its own name, description, recipe, when_to_use and when_not_to_use; a save replaces the list). Records which staff member saved it. Refused unless the key was created by Letter staff; the same as the /superadmin/recipes page. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"kind":{"type":"string","enum":["recipe","known_issue"],"description":"Which list it is on: recipe (a strategy, the Plan's rows; the default) or known_issue (a common mistake on live pages, Fix's list). Build is handed neither."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"What staff call it, unique within its kind, e.g. 'Set-up LLMs.txt file' or 'A mention with no link'."},"description":{"type":"string","maxLength":1000,"description":"What the recipe does, for a person, in a sentence or two: the recipes page shows it in place of the recipe, which only Letterpress reads."},"recipe":{"type":"string","maxLength":8000,"description":"The directions of its first variation, in plain words: what to look at (on the site, in the account, in what was read) and what to do. Name no tool."},"when_to_use":{"type":"string","maxLength":2000,"description":"When Letterpress should reach for it, in plain words: context, not a rule."},"when_not_to_use":{"type":"string","maxLength":2000,"description":"When it should not, and the look-alikes, so Letterpress and the judge pass them by."},"weight":{"type":"integer","minimum":1,"maximum":10,"description":"How much a proposal that follows it tends to matter, 1..10 (the one score's prior). Default 5."},"approaches":{"maxItems":5,"type":"array","items":{"type":"string","enum":["seo","geo","agent_conviction","inbound_conversion","outbound"]},"description":"What it applies to, tagged by staff: the Plan goals (seo, geo, agent_conviction, inbound_conversion, outbound). Empty: untagged. Filters the library; Letterpress reads them as context."},"reads":{"maxItems":26,"type":"array","items":{"type":"string","enum":["llms_txt","homepage_vendors","repo_dependencies","robots_sitemap","homepage_signin","homepage_claims","blog_author_bios","ranking_targets","site_folders","content_split","top_page_visits","variational_running","site_blog","blog_source","github","visitors","platform","blog_kernel","cms","instances","rivals","memory","evidence","pricing_page","comparison_pages","locale_pages"]},"description":"The facts its when-to-use and when-not-to-use rest on, by key (llms_txt, homepage_vendors, homepage_signin, homepage_claims, blog_author_bios, ranking_targets, repo_dependencies, robots_sitemap, site_folders, content_split, site_blog, blog_source, blog_kernel, platform, github, visitors, top_page_visits, variational_running, cms, instances, rivals, memory, evidence; a known issue reads the crawl: pricing_page, comparison_pages, locale_pages, and Fix is handed it only where each holds). Staff declare them; the lint says when a condition names one that is not listed, and the probes an account runs are the ones its recipes read. Empty: not written yet (read as every probe)."},"runs":{"type":"string","maxLength":80,"description":"The catalog action the recipe runs when started, by id (for example visitors:site, blog:kernel); empty: one change to the site's code from the recipe's own directions. Use an action only when it already does what the recipe says."},"primitives_access":{"type":"boolean","description":"Whether a start of the recipe hands its build Letter's own tools beside the site's code, for a recipe whose change is more than code (creating an A/B test, an automation). Default false: the change is the site's code and nothing else."},"immediate":{"type":"boolean","description":"Whether its result is live within a day of its start, with nothing for the team to fill in (llms.txt served, author bios on the posts). The Plan puts one such row in every top three, so a new account sees a quick win beside the setup. Default false."},"status":{"type":"string","enum":["live","parked"],"description":"live (the default): the fit reads it. parked: an idea that cannot run yet; the fit is never handed it and an explicit ask for it is refused. Say in `needs` what is missing."},"needs":{"type":"string","maxLength":200,"description":"One line naming what is missing before a parked recipe can run (for example \"page-level conversion data\"); only meaningful when parked, and the parked list reads as a backlog of what to build. Empty otherwise."},"variations":{"maxItems":9,"type":"array","items":{"type":"object","properties":{"name":{"description":"The variation's title; empty or omitted: the recipe's name.","type":"string","maxLength":120},"description":{"description":"What the variation does, for a person; never in a prompt.","type":"string","maxLength":1000},"recipe":{"type":"string","minLength":1,"maxLength":8000,"description":"The directions, in plain words: what to look at (on the site, in the account, in what was read) and what to do. Name no tool."},"when_to_use":{"type":"string","maxLength":2000,"description":"When Letterpress should reach for it, in plain words: context, not a rule."},"when_not_to_use":{"type":"string","maxLength":2000,"description":"When it should not, and the look-alikes, so Letterpress and the judge pass them by."}},"required":["recipe","when_to_use","when_not_to_use"]},"description":"The recipe's other variations, each its own name (title), description, recipe, when_to_use and when_not_to_use; the recipe's own fields are the first. Letterpress follows one variation, never two, and their when to use / when not to use say which. A save replaces the whole list; empty: one way to run it."},"id":{"description":"The recipe to change; omit to create one (name and recipe then required).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"recipe":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","description":"What it does, for a person; empty until staff write it"},"recipe":{"type":"string","description":"The directions, in plain words: what Letterpress reads"},"when_to_use":{"type":"string"},"when_not_to_use":{"type":"string"},"weight":{"type":"number","description":"1..10: the one score's prior for a proposal that follows it"},"approaches":{"type":"array","items":{"type":"string","enum":["seo","geo","agent_conviction","inbound_conversion","outbound"]},"description":"What it applies to, tagged by staff (the Plan goals); empty: untagged"},"reads":{"type":"array","items":{"type":"string"},"description":"The facts its conditions rest on, by key (lib/recipes/fact-sources.ts); empty: not written yet"},"runs":{"type":"string","description":"The catalog action it runs when started (visitors:site); empty: one change to the site's code"},"status":{"type":"string","enum":["live","parked"],"description":"live: the fit reads it; parked: an idea that cannot run yet, never handed to the fit"},"needs":{"type":"string","description":"What is missing before a parked recipe can run, one line; empty when live"},"variations":{"type":"array","items":{"type":"object","properties":{"name":{"description":"The variation's title; empty or absent: the recipe's name","type":"string"},"description":{"description":"What the variation does, for a person","type":"string"},"recipe":{"type":"string","description":"The variation's directions, in plain words"},"when_to_use":{"type":"string"},"when_not_to_use":{"type":"string"}},"required":["recipe","when_to_use","when_not_to_use"]},"description":"Its other variations, each its own title and description; its own fields are the first"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","description","recipe","when_to_use","when_not_to_use","weight","approaches","reads","runs","status","needs","variations","created_by","updated_by","created_at","updated_at"]},"created":{"type":"boolean"},"warnings":{"type":"array","items":{"type":"string"},"description":"What staff should know about the recipe as saved (a placeholder, no when-to-use, untagged, no description, directions that get cut, a malformed `runs`), one sentence each; empty when clean"}},"required":["recipe","created","warnings"]}},{"name":"delete_recipe","description":"Staff only. Delete a recipe for good: retiring a recipe is deleting it. Refused unless the key was created by Letter staff. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The recipe to delete."}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"deleted":{"type":"boolean","const":true}},"required":["id","deleted"]}},{"name":"import_recipes","description":"Staff only. Write a library of recipes in one call: the bulk form of save_recipe, for an agent that keeps Letter's recipes as a file. Each recipe is matched to a saved one by `id`, else by `name`; a match is changed only in the fields that differ, no match is created. The output of list_recipes can be edited and passed back as is. `dry_run` says what would be created, changed and left alone, and writes nothing: run it first. Each row comes back with its action (create / update / unchanged / failed) and the fields it touched; a failed row doesn't stop the rest. Refused unless the key was created by Letter staff; the same as `letterstory admin recipes import`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"recipes":{"minItems":1,"maxItems":300,"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["recipe","known_issue"],"description":"Which list it is on: recipe (a strategy, the Plan's rows; the default) or known_issue (a common mistake on live pages, Fix's list). Build is handed neither."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"What staff call it, unique within its kind, e.g. 'Set-up LLMs.txt file' or 'A mention with no link'."},"description":{"type":"string","maxLength":1000,"description":"What the recipe does, for a person, in a sentence or two: the recipes page shows it in place of the recipe, which only Letterpress reads."},"recipe":{"type":"string","maxLength":8000,"description":"The directions of its first variation, in plain words: what to look at (on the site, in the account, in what was read) and what to do. Name no tool."},"when_to_use":{"type":"string","maxLength":2000,"description":"When Letterpress should reach for it, in plain words: context, not a rule."},"when_not_to_use":{"type":"string","maxLength":2000,"description":"When it should not, and the look-alikes, so Letterpress and the judge pass them by."},"weight":{"type":"integer","minimum":1,"maximum":10,"description":"How much a proposal that follows it tends to matter, 1..10 (the one score's prior). Default 5."},"approaches":{"maxItems":5,"type":"array","items":{"type":"string","enum":["seo","geo","agent_conviction","inbound_conversion","outbound"]},"description":"What it applies to, tagged by staff: the Plan goals (seo, geo, agent_conviction, inbound_conversion, outbound). Empty: untagged. Filters the library; Letterpress reads them as context."},"reads":{"maxItems":26,"type":"array","items":{"type":"string","enum":["llms_txt","homepage_vendors","repo_dependencies","robots_sitemap","homepage_signin","homepage_claims","blog_author_bios","ranking_targets","site_folders","content_split","top_page_visits","variational_running","site_blog","blog_source","github","visitors","platform","blog_kernel","cms","instances","rivals","memory","evidence","pricing_page","comparison_pages","locale_pages"]},"description":"The facts its when-to-use and when-not-to-use rest on, by key (llms_txt, homepage_vendors, homepage_signin, homepage_claims, blog_author_bios, ranking_targets, repo_dependencies, robots_sitemap, site_folders, content_split, site_blog, blog_source, blog_kernel, platform, github, visitors, top_page_visits, variational_running, cms, instances, rivals, memory, evidence; a known issue reads the crawl: pricing_page, comparison_pages, locale_pages, and Fix is handed it only where each holds). Staff declare them; the lint says when a condition names one that is not listed, and the probes an account runs are the ones its recipes read. Empty: not written yet (read as every probe)."},"runs":{"type":"string","maxLength":80,"description":"The catalog action the recipe runs when started, by id (for example visitors:site, blog:kernel); empty: one change to the site's code from the recipe's own directions. Use an action only when it already does what the recipe says."},"primitives_access":{"type":"boolean","description":"Whether a start of the recipe hands its build Letter's own tools beside the site's code, for a recipe whose change is more than code (creating an A/B test, an automation). Default false: the change is the site's code and nothing else."},"immediate":{"type":"boolean","description":"Whether its result is live within a day of its start, with nothing for the team to fill in (llms.txt served, author bios on the posts). The Plan puts one such row in every top three, so a new account sees a quick win beside the setup. Default false."},"status":{"type":"string","enum":["live","parked"],"description":"live (the default): the fit reads it. parked: an idea that cannot run yet; the fit is never handed it and an explicit ask for it is refused. Say in `needs` what is missing."},"needs":{"type":"string","maxLength":200,"description":"One line naming what is missing before a parked recipe can run (for example \"page-level conversion data\"); only meaningful when parked, and the parked list reads as a backlog of what to build. Empty otherwise."},"variations":{"maxItems":9,"type":"array","items":{"type":"object","properties":{"name":{"description":"The variation's title; empty or omitted: the recipe's name.","type":"string","maxLength":120},"description":{"description":"What the variation does, for a person; never in a prompt.","type":"string","maxLength":1000},"recipe":{"type":"string","minLength":1,"maxLength":8000,"description":"The directions, in plain words: what to look at (on the site, in the account, in what was read) and what to do. Name no tool."},"when_to_use":{"type":"string","maxLength":2000,"description":"When Letterpress should reach for it, in plain words: context, not a rule."},"when_not_to_use":{"type":"string","maxLength":2000,"description":"When it should not, and the look-alikes, so Letterpress and the judge pass them by."}},"required":["recipe","when_to_use","when_not_to_use"]},"description":"The recipe's other variations, each its own name (title), description, recipe, when_to_use and when_not_to_use; the recipe's own fields are the first. Letterpress follows one variation, never two, and their when to use / when not to use say which. A save replaces the whole list; empty: one way to run it."},"id":{"description":"The recipe to change; omit to create one (name and recipe then required).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"description":"The recipes, each matched to a saved one by `id`, else by `name`: a match is changed (only the fields given), no match is created. Fields a recipe leaves out keep their saved value. The shape list_recipes returns is accepted as is."},"dry_run":{"description":"True: say what would be created, changed and left alone, and write nothing.","type":"boolean"}},"required":["recipes"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"dry_run":{"type":"boolean","description":"True: nothing was written; the rows say what would happen"},"results":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"id":{"description":"Null for a recipe a dry run would create, or a create that failed","type":["string","null"]},"action":{"type":"string","enum":["create","update","unchanged","failed"]},"changed":{"type":"array","items":{"type":"string"},"description":"The fields it sets (create) or changes (update)"},"warnings":{"type":"array","items":{"type":"string"},"description":"What staff should know about the recipe as this row leaves it (lint), one sentence each"},"error":{"type":"string"}},"required":["name","id","action","changed","warnings"]}},"counts":{"type":"object","properties":{"created":{"type":"number"},"updated":{"type":"number"},"unchanged":{"type":"number"},"failed":{"type":"number"}},"required":["created","updated","unchanged","failed"]}},"required":["dry_run","results","counts"]}},{"name":"describe_recipe_vocabulary","description":"Staff only. What a recipe can say, for writing recipes without the staff page: how recipes are read, the lenses that read them, and every field with what it means. Read it before save_recipe or import_recipes. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"how_recipes_are_read":{"type":"array","items":{"type":"string"}},"lenses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reads":{"type":"string"}},"required":["id","reads"]}},"fields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}},"required":["name","description"]}}},"required":["how_recipes_are_read","lenses","fields"]}},{"name":"recipe_stats","description":"Staff only. How each recipe does across every org: per lens and recipe (and '(none)' for proposals that followed no recipe), how many suggestions the stored passes show and how many people dismissed, with their reasons; and `usage`, per recipe over the last `usage_days` days: how many runs had it to choose from (`offered`), how many followed it (`runs`: a Plan grounded in it, or an answered item that named it), the share selected, by lens, and per day. The feedback loop staff tune recipes from (`letterstory admin recipes stats`, the recipes page). Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"stats":{"type":"array","items":{"type":"object","properties":{"recipe":{"type":"string","description":"The recipe's name, or '(none)' for proposals that followed none"},"lens":{"type":"string","enum":["plan","build","fix"],"description":"The lens the suggestions were shown on"},"shown":{"type":"number"},"dismissed":{"type":"number"},"resolved":{"type":"number","description":"Fix: no longer open and not dismissed (the edit landed, or the page moved on)"},"built":{"type":"number","description":"Plan: growth builds started on the recipe"},"merged":{"type":"number","description":"Plan: of those, with a merged pull request"},"reasons":{"type":"array","items":{"type":"string"},"description":"Why people dismissed, newest first, at most 10"}},"required":["recipe","lens","shown","dismissed","resolved","built","merged","reasons"]}},"usage":{"type":"array","items":{"type":"object","properties":{"recipe_id":{"type":"string"},"name":{"type":"string"},"offered":{"type":"number","description":"The window's runs that had it to choose from"},"runs":{"type":"number","description":"Of those, the runs that followed it"},"selected_share":{"description":"runs / offered, 0..1; null when no run had it","type":["number","null"]},"by_lens":{"type":"object","properties":{"plan":{"type":"number"},"build":{"type":"number"},"fix":{"type":"number"},"other":{"type":"number"}},"required":["plan","build","fix","other"],"description":"The runs that followed it, by lens (other: a conversation turn, an ask with no lens)"},"items":{"type":"number","description":"The items those runs answered under it"},"ignored":{"type":"number","description":"Plan runs that showed it with no build for it since (shown and left)"},"fits":{"type":"object","properties":{"yes":{"type":"number"},"no":{"type":"number"},"unknown":{"type":"number"},"waiting":{"type":"number"}},"required":["yes","no","unknown","waiting"],"description":"The standing fit: on how many accounts it fits, does not, waits on a connection, or could not be decided"},"last_run_at":{"type":["string","null"]},"daily":{"type":"array","items":{"type":"number"},"description":"Runs that followed it per day, oldest first"}},"required":["recipe_id","name","offered","runs","selected_share","by_lens","items","ignored","fits","last_run_at","daily"]}},"usage_days":{"type":"number","description":"The window `usage` reads, in days"},"known_issues":{"type":"object","properties":{"idle":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"handed":{"type":"number"}},"required":["id","name","handed"]},"description":"Live known issues handed to many Fix passes that no kept fix named: the nightly review parks them"},"unnamed":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"count":{"type":"number"},"examples":{"type":"array","items":{"type":"string"}}},"required":["kind","count","examples"]},"description":"Kept fixes that named no known issue, by edit kind, most first: candidates for a new one"},"named":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Per known issue, the kept fixes that named it"},"cut":{"type":"array","items":{"type":"string"},"description":"Known issues past the finder's budget: the list is longer than Fix reads"}},"required":["idle","unnamed","named","cut"],"description":"Fix's known issues read against what Fix kept in the window (lib/recipes/known-issues.ts)"}},"required":["stats","usage","usage_days","known_issues"]}},{"name":"list_recipe_runs","description":"Staff only. The runs of the last `days` (default 30) that had one recipe to choose from, newest first: each with what it was asked, the company it ran for (organization and site), who asked (null for a scheduled pass), and whether it used the recipe (`used`: its Plan grounded in it, or an item it answered named it), with the items it answered under it and the variations they named. `offered` and `used` count the whole window, by the same rule as recipe_stats' `usage`. The recipes page's right column; `letterstory admin recipes runs`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The recipe."},"days":{"description":"How many days back to read, 1..90. Default 30, the window the recipes page counts.","type":"integer","minimum":1,"maximum":90},"limit":{"description":"How many runs to list, newest first. Default 100.","type":"integer","minimum":1,"maximum":500}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"recipe":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"days":{"type":"number","description":"The window read, in days"},"offered":{"type":"number","description":"The window's runs that had the recipe to choose from"},"used":{"type":"number","description":"Of those, the runs that used it"},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string"},"lens":{"type":["string","null"]},"ask":{"type":"string","description":"What the run was asked, in a few words"},"company":{"type":"object","properties":{"org_id":{"type":"string"},"name":{"description":"The organization's name","type":["string","null"]},"domain":{"type":"string","description":"The site it ran on"}},"required":["org_id","name","domain"]},"started_by":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Who asked; null for a scheduled pass"},"used":{"type":"boolean","description":"It used the recipe: its Plan grounded in it, or an item it answered named it"},"items":{"type":"number","description":"The items it answered under the recipe"},"variations":{"type":"array","items":{"type":"number"},"description":"The variations of the recipe it followed (1, 2, …)"}},"required":["id","created_at","lens","ask","company","started_by","used","items","variations"]},"description":"The newest `limit` of them"}},"required":["recipe","days","offered","used","runs"]}},{"name":"list_recipe_fits","description":"Staff only. The standing fit: which of staff's recipes fit which account's site, as kept (docs/architecture/one-recipe-engine.md, The standing fit). One verdict per account, site and recipe: fits, does not, or could not be decided, with the one fact it rests on (or the one fact it needs: the next probe to write) and when it was decided. Filter by `recipe_id`, `org_id`, `domain`, `fits`. A dismissal, a shown-and-left count and a build's does-not-apply are applied when the Plan reads these, never stored here. Refused unless the key was created by Letter staff; `letterstory admin letterpress fits`. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"recipe_id":{"description":"One recipe's verdict on every account that has one.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"org_id":{"description":"One organization's verdicts on every recipe.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"domain":{"description":"One site's verdicts.","type":"string","minLength":1,"maxLength":253},"fits":{"description":"Only the verdicts that fit, do not, or could not be decided.","type":"string","enum":["yes","no","unknown","waiting"]},"limit":{"description":"How many, newest first. Default 100.","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"fits":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"host":{"type":"string","description":"The site the verdict is for"},"recipe_id":{"type":"string"},"recipe":{"type":"string","description":"The recipe's name"},"fits":{"type":"string","enum":["yes","no","unknown","waiting"],"description":"Whether the recipe fits this account's site; waiting: it applies and waits on something the member connects or installs"},"why":{"type":"string","description":"The one fact the verdict rests on, in the owner's words"},"needs":{"description":"With unknown: the one fact that would decide it (the next probe to write)","type":["string","null"]},"waiting_on":{"description":"With waiting: what it waits on (github, visitors, cms): connect it and the verdict is decided again","type":["string","null"]},"decided_at":{"type":"string"},"fit_version":{"type":"number"}},"required":["org_id","host","recipe_id","recipe","fits","why","needs","waiting_on","decided_at","fit_version"]}}},"required":["fits"]}},{"name":"refit_accounts","description":"Staff only. Decide the standing fit again now instead of on the hourly sweep's turn (docs/architecture/one-recipe-engine.md, The standing fit): each account's facts gathered, every goal's evidence read and its recipes decided, the same path as the sweep, recorded as a standing_fit run. `org_ids`: those accounts, every recipe decided again. `recipe_id`: every account whose verdict on that live recipe is missing or stale (after an edit to it). Neither: every account with a verdict stale for the library (a recipe edited, or a new fit version). At most `limit` (default 10, max 25), the one decided longest ago first, within about four minutes; each outcome is refit, standing (nothing moved), failed with why, or not_reached (out of time), and `left` counts the accounts past the limit. Each account is a model call. Refused unless the key was created by Letter staff; `letterstory admin recipes refit`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"org_ids":{"description":"These accounts, every recipe decided again on their owned, mapped site.","minItems":1,"maxItems":25,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"recipe_id":{"description":"Without org_ids: every account whose verdict on this live Plan recipe is missing or stale (decided on another version of it, or by another fit version).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"How many accounts at most, the one decided longest ago first. Default 10.","type":"integer","minimum":1,"maximum":25}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["orgs","recipe","stale"],"description":"What chose the accounts: the org_ids given, one recipe's missing or stale verdicts, or every verdict stale for the library"},"picked":{"type":"number","description":"How many accounts this call took"},"left":{"type":"number","description":"How many more matched past the limit: call again to take them"},"outcomes":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"domain":{"description":"The site it was fitted on; null when the account has none mapped","type":["string","null"]},"status":{"type":"string","enum":["refit","standing","failed","not_reached"],"description":"refit: some verdict was decided again; standing: every verdict stood; failed: see reason; not_reached: the call ran out of time first (the sweep or the next call takes it)"},"reason":{"type":["string","null"]}},"required":["org_id","domain","status","reason"]}}},"required":["mode","picked","left","outcomes"]}},{"name":"list_letterpress_companies","description":"Staff only. The companies Letterpress holds a fit for: per company and site, how many recipes fit, do not, or could not be decided, and when it was last decided; the mapped ones still waiting for their first fit are listed last. The Companies tab's list; `letterstory admin letterpress companies`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"limit":{"description":"How many companies, newest decided first. Default 200.","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"name":{"type":["string","null"]},"host":{"type":"string"},"yes":{"type":"number","description":"Recipes that fit"},"no":{"type":"number"},"unknown":{"type":"number","description":"Recipes the facts could not decide"},"awaiting":{"type":"number","description":"Recipes waiting on something the member connects or installs"},"last_decided_at":{"type":["string","null"]},"waiting":{"type":"boolean","description":"Mapped, with no verdicts yet"}},"required":["org_id","name","host","yes","no","unknown","awaiting","last_decided_at","waiting"]}}},"required":["companies"]}},{"name":"get_letterpress_company","description":"Staff only. One company as Letterpress holds it: its verdicts (each recipe, fits or not, the one fact it rests on or needs, and the facts the recipe declares it reads), the facts those verdicts were decided on (every probe with its value and when it was read, the fact lines and evidence the newest fit was handed), what a person dismissed on the site and what was shown and left, its latest runs, and how many of its asks went past the library. Never shown to a customer. `letterstory admin letterpress company <org>`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The organization."},"domain":{"description":"The site; the one its newest verdicts are for when omitted.","type":"string","minLength":1,"maxLength":253},"runs":{"description":"How many of its latest runs to list. Default 40.","type":"integer","minimum":1,"maximum":200}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"org":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"domain":{"type":["string","null"]}},"required":["id","name","domain"]},"host":{"type":"string"},"verdicts":{"type":"array","items":{"type":"object","properties":{"recipe_id":{"type":"string"},"recipe":{"type":"string"},"fits":{"type":"string","enum":["yes","no","unknown","waiting"]},"why":{"type":"string"},"needs":{"type":["string","null"]},"waiting_on":{"description":"With waiting: what it waits on (github, visitors, cms)","type":["string","null"]},"reads":{"type":"array","items":{"type":"string"},"description":"The facts the recipe declares it reads"},"decided_at":{"type":"string"},"fit_version":{"type":"number"}},"required":["recipe_id","recipe","fits","why","needs","waiting_on","reads","decided_at","fit_version"]}},"facts":{"type":"object","properties":{"host":{"type":"string"},"stored":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"A probe's key (llms_txt, homepage_signin, …) or a build's does-not-apply"},"value":{"description":"What was read; null when the probe has not run for this site yet"},"source":{"type":"string"},"read_at":{"type":["string","null"]},"live":{"type":"boolean","description":"Read within its life (a probe a day)"}},"required":["key","value","source","read_at","live"]},"description":"account_facts: every probe, read or not, and what else was kept"},"lines":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"line":{"type":"string"},"detail":{"type":"array","items":{"type":"string"}}},"required":["key","line"]}},{"type":"null"}],"description":"The fact lines the newest standing fit was handed: what its verdicts were decided on; null before the first"},"lines_at":{"type":["string","null"]},"evidence":{"anyOf":[{"type":"array","items":{}},{"type":"null"}],"description":"What was read for the outcomes, as that fit saw it"}},"required":["host","stored","lines","lines_at","evidence"]},"dismissed":{"type":"array","items":{"type":"object","properties":{"recipe":{"type":"string"},"kind":{"type":"string"},"at":{"type":"string"}},"required":["recipe","kind","at"]}},"left":{"type":"array","items":{"type":"object","properties":{"recipe":{"type":"string"},"times":{"type":"number"}},"required":["recipe","times"]},"description":"Recipes shown and left, by times"},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string"},"lens":{"type":["string","null"]},"surface":{"type":["string","null"]},"ask":{"type":"string"},"standing":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string"},"recipes":{"type":"number"}},"required":["reason","recipes"]},{"type":"null"}]},"beyond_library":{"type":"boolean"},"looks_outside":{"type":"boolean"}},"required":["id","created_at","lens","surface","ask","standing","beyond_library","looks_outside"]}},"beyond_library":{"type":"number","description":"Turns whose answer went past the library, over its whole history"}},"required":["org","host","verdicts","facts","dismissed","left","runs","beyond_library"]}},{"name":"list_account_facts","description":"Staff only. The facts one company's recipe verdicts rest on: every probe's stored value (llms_txt, homepage_vendors, homepage_signin, homepage_claims, blog_author_bios, ranking_targets, repo_dependencies, robots_sitemap) with when it was read and whether it is still live, a build's does-not-apply facts, and the fact lines and evidence the newest standing fit was handed. A wrong verdict is traced to a wrong or stale fact here. `letterstory admin letterpress facts <org>`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The organization."},"domain":{"description":"The site; the one its newest verdicts are for when omitted.","type":"string","minLength":1,"maxLength":253}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"facts":{"type":"object","properties":{"host":{"type":"string"},"stored":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"A probe's key (llms_txt, homepage_signin, …) or a build's does-not-apply"},"value":{"description":"What was read; null when the probe has not run for this site yet"},"source":{"type":"string"},"read_at":{"type":["string","null"]},"live":{"type":"boolean","description":"Read within its life (a probe a day)"}},"required":["key","value","source","read_at","live"]},"description":"account_facts: every probe, read or not, and what else was kept"},"lines":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"line":{"type":"string"},"detail":{"type":"array","items":{"type":"string"}}},"required":["key","line"]}},{"type":"null"}],"description":"The fact lines the newest standing fit was handed: what its verdicts were decided on; null before the first"},"lines_at":{"type":["string","null"]},"evidence":{"anyOf":[{"type":"array","items":{}},{"type":"null"}],"description":"What was read for the outcomes, as that fit saw it"}},"required":["host","stored","lines","lines_at","evidence"]}},"required":["facts"]}},{"name":"list_letterpress_runs","description":"Staff only. Every run of Letterpress across every org, newest first: a Plan run (the goal, its evidence, its plan), a Build pass or a Fix pass, each with the recipe it grounded in (`direction`) and its sequence step by step (what was handed, which recipes it saw, what Letterpress proposed, what each check kept or dropped and why, what Jev read, what is shown). Filter by `lens`, `org_id`, `domain`; `beyond_library` reads the turns whose answer went past what the library holds (what members ask that Letter has no strategy or tool for yet), `before` pages back through history. The record staff tune Letterpress from (docs/architecture/one-recipe-engine.md §12c); a wrong run's `sequence` is a regression case (evals/letterpress). Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"lens":{"description":"One lens's runs (next: an ask with nothing in it, ranked across the stores, or an ask in words, ranked against the words); omitted: every lens.","type":"string","enum":["plan","build","fix","next"]},"org_id":{"description":"One organization's runs.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"domain":{"description":"One site's runs (the plan's domain or the pass's host).","type":"string","minLength":1,"maxLength":253},"limit":{"description":"How many, newest first. Default 50.","type":"integer","minimum":1,"maximum":200},"beyond_library":{"description":"Only the conversation turns whose answer went past what the library holds: the door found no strategy that fits, or the message pointed outside the account (what members ask that Letter has no strategy or tool for yet).","type":"boolean"},"before":{"description":"Only runs made before this time (ISO 8601): the page of history before the last one read.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"lens":{"anyOf":[{"type":"string","enum":["plan","build","fix","next"]},{"type":"null"}],"description":"Null: a conversation turn that ran no lens (a question answered, a build started)"},"domain":{"type":"string"},"host":{"type":["string","null"]},"goal":{"type":["string","null"]},"prompt":{"type":["string","null"]},"ask":{"description":"The ask as the engine received it: { outcome } | { lens } | { words }"},"surface":{"type":["string","null"]},"state":{"type":"string"},"stage":{"type":["string","null"]},"direction":{"anyOf":[{"type":"object","properties":{"recipe_id":{"type":["string","null"]},"name":{"type":["string","null"]},"by":{"type":"string","enum":["default","model","caller","pin","none"]},"reason":{"type":["string","null"]}},"required":["recipe_id","name","by","reason"]},{"type":"null"}]},"sequence":{"type":"object","properties":{"context":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"direction":{"type":"object","properties":{"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["recipes"]},"proposals":{"type":"array","items":{}},"checks":{"type":"object","properties":{"kept":{"type":"array","items":{"type":"string"}},"dropped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"}},"required":["id","reason"]}}},"required":["kept","dropped"]},"readings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"judged":{}},"required":["id","judged"]}},"shown":{"type":"array","items":{}},"model":{"type":["string","null"]},"prompt_hash":{"type":"string"},"timings":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"ms":{"type":"number"}},"required":["step","ms"]},"description":"Plan: where the run's time went, step by step"}},"description":"The run step by step; empty for a plan run still gathering"},"evidence":{"type":"array","items":{}},"plan":{"anyOf":[{},{"type":"null"}]},"error":{"type":["string","null"]},"started_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"finished_at":{"type":["string","null"]},"marks":{"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string"},"verdict":{"type":"string","enum":["right","weak","wrong","lead","gap","recipe"]},"note":{"type":["string","null"]},"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["item_id","verdict","note","by","at"]},"description":"Staff marks per shown item (the blind read); empty when unread"}},"required":["id","org_id","lens","domain","host","goal","prompt","ask","surface","state","stage","direction","sequence","evidence","plan","error","started_by","created_at","updated_at","finished_at","marks"]}}},"required":["runs"]}},{"name":"get_engine_settings","description":"Staff only. What steers Letterpress beside the recipes, as it applies now (docs/architecture/one-recipe-engine.md §5.2): per ask (Fix's, Build's, a Plan goal's, words, what next) the weight of each of the one judgement's five readings (obvious, specific, carries, one, serves) and of the item's prior; per lens its bar and how much it may read; per surface its ask and staff's guidance; how many days a dismissal blocks. The defaults in code are today's values; `version` 0 means no row yet. Refused unless the key was created by Letter staff; the same as `letterstory admin letterpress settings`. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"settings":{"type":"object","properties":{"asks":{"type":"object","properties":{"build":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]},"fix":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]},"plan":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]}},"required":["build","fix","plan"]},"weights":{"type":"object","properties":{"fix":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"build":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"next":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"words":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]}},"required":["fix","build","next","words"]},"lenses":{"type":"object","properties":{"build":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]},"fix":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]},"plan":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]}},"required":["build","fix","plan"]},"dismissals":{"type":"object","properties":{"block_days":{"type":"number","description":"How many days a person's dismissal blocks the thing dismissed"}},"required":["block_days"]},"explore":{"type":"object","properties":{"ask":{"type":"string","description":"The standing exploration ask, run per account on a schedule"},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"]},"every_days":{"type":"number"}},"required":["ask","research","every_days"]}},"required":["asks","weights","lenses","dismissals","explore"]},"version":{"type":"number","description":"0: no row yet, the defaults in code"},"updated_by":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"defaults_only":{"type":"boolean"}},"required":["settings","version","updated_by","updated_at","defaults_only"]}},{"name":"save_engine_settings","description":"Staff only. Edit what steers Letterpress beside the recipes: a weight of an ask (`weights.fix.prior: 0.3`), a lens's bar, how much Letterpress may read before it answers there (`lenses.fix.research: { turns, ms }`), a surface's ask or guidance (`asks.plan.guidance`), or how many days a dismissal blocks (`dismissals.block_days`). Only what is named changes; the whole body is stored, the version bumps and the previous body is kept with who changed it, so a run that recorded a settings version reads back exactly. Setting a version back is another edit. Refused unless the key was created by Letter staff; the same as `letterstory admin letterpress set`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"weights":{"description":"Per ask, the weight of each reading (0..1) and of the item's prior.","type":"object","properties":{"fix":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"build":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"plan":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"words":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"next":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}}}},"lenses":{"type":"object","properties":{"build":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}},"fix":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}},"plan":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}}}},"asks":{"description":"Per lens, its ask in words and the guidance staff give Letterpress on how to choose there.","type":"object","properties":{"build":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}},"fix":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}},"plan":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}}}},"explore":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The standing exploration ask, in words."},"research":{"description":"How much the exploration may read: a budget, mostly reads.","type":"object","properties":{"turns":{"type":"integer","minimum":0,"maximum":20},"ms":{"type":"integer","minimum":10000,"maximum":900000}}},"every_days":{"description":"How often it runs per account.","type":"integer","minimum":1,"maximum":90}}},"dismissals":{"type":"object","properties":{"block_days":{"type":"integer","minimum":1,"maximum":365,"description":"How many days a person's dismissal blocks the thing dismissed; past them it is dated history."}}},"preflight":{"type":"object","properties":{"off":{"type":"array","items":{"type":"string","enum":["wrong_repo","not_in_repo","already_done","no_input","not_applicable","needs_info","not_a_page"]},"description":"The blocker codes (wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page) switched OFF: not enforced, so what they withheld is suggested and started again. The way back from a wrong check, with no deploy. Replaces the list; empty turns every code back on."}}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"settings":{"type":"object","properties":{"asks":{"type":"object","properties":{"build":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]},"fix":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]},"plan":{"type":"object","properties":{"ask":{"type":"string"},"guidance":{"type":"string"}},"required":["ask","guidance"]}},"required":["build","fix","plan"]},"weights":{"type":"object","properties":{"fix":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"build":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"next":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"words":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]}},"required":["fix","build","next","words"]},"lenses":{"type":"object","properties":{"build":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]},"fix":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]},"plan":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"],"description":"How much Letterpress may read before it answers on this surface"}},"required":["bar","research"]}},"required":["build","fix","plan"]},"dismissals":{"type":"object","properties":{"block_days":{"type":"number","description":"How many days a person's dismissal blocks the thing dismissed"}},"required":["block_days"]},"explore":{"type":"object","properties":{"ask":{"type":"string","description":"The standing exploration ask, run per account on a schedule"},"research":{"type":"object","properties":{"turns":{"type":"number"},"ms":{"type":"number"}},"required":["turns","ms"]},"every_days":{"type":"number"}},"required":["ask","research","every_days"]}},"required":["asks","weights","lenses","dismissals","explore"]},"version":{"type":"number","description":"0: no row yet, the defaults in code"},"updated_by":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"defaults_only":{"type":"boolean"},"changed":{"type":"array","items":{"type":"string"},"description":"The paths that changed, e.g. weights.fix.prior; empty: nothing did"}},"required":["settings","version","updated_by","updated_at","defaults_only","changed"]}},{"name":"describe_letterpress","description":"Staff only. Letterpress as it runs right now (docs/architecture/one-recipe-engine.md §12f): the settings' version, the judgement's questions' version, the weights per ask, and per lens its mode (off, shadow, live), its bar, its recipes (enabled, retired, conditioned), its runs over the last days with the model and prompt hash they ran on, how often the shadow agreed with what was shown, and how runs grounded (a recipe followed in full, in part, none); the surfaces the asks came from. Read it before changing anything; `letterstory admin letterpress status`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"customer_only":{"description":"Leave out test and demo orgs: the customers' reading. Default false (everything).","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"as_of":{"type":"string"},"days":{"type":"number"},"headline":{"type":"object","properties":{"sentence":{"type":"string","description":"The week in one sentence, as Today and the digest say it"},"right_first":{"type":"object","properties":{"runs":{"type":"number"},"right":{"type":"number"},"prev_runs":{"type":"number"},"prev_right":{"type":"number"}},"required":["runs","right","prev_runs","prev_right"]},"merged":{"type":"object","properties":{"builds":{"type":"number"},"merged":{"type":"number"},"prev_builds":{"type":"number"},"prev_merged":{"type":"number"}},"required":["builds","merged","prev_builds","prev_merged"]},"cannot":{"type":"object","properties":{"asks":{"type":"number"},"accounts":{"type":"number"}},"required":["asks","accounts"]},"open":{"type":"object","properties":{"items":{"type":"number"},"runs":{"type":"number"},"prev_items":{"type":"number"}},"required":["items","runs","prev_items"],"description":"Ideas Letterpress said that code could not ship: kept on the runs, never shown"},"waiting":{"type":"number","description":"Runs that showed something and nobody has marked"},"to_do":{"type":"number"}},"required":["sentence","right_first","merged","cannot","open","waiting","to_do"]},"customer_only":{"type":"boolean","description":"True when test and demo orgs are left out"},"plan_runs":{"type":"object","properties":{"runs":{"type":"number"},"ready":{"type":"number"},"failed":{"type":"number"},"stalled":{"type":"number","description":"Still going but silent for two minutes: the page says it didn't finish"},"speed":{"anyOf":[{"type":"object","properties":{"runs":{"type":"number","description":"Runs that recorded how long they took"},"p50_ms":{"type":"number"},"p95_ms":{"type":"number","description":"The slowest tenth took at least this long"},"slow":{"type":"number","description":"Runs over a minute"}},"required":["runs","p50_ms","p95_ms","slow"]},{"type":"null"}],"description":"How long requests took over the window; null when none recorded it"}},"required":["runs","ready","failed","stalled","speed"],"description":"Plan runs started from the Plan page over the window: how they ended and how long they took"},"turn_speed":{"anyOf":[{"type":"object","properties":{"runs":{"type":"number","description":"Runs that recorded how long they took"},"p50_ms":{"type":"number"},"p95_ms":{"type":"number","description":"The slowest tenth took at least this long"},"slow":{"type":"number","description":"Runs over a minute"}},"required":["runs","p50_ms","p95_ms","slow"]},{"type":"null"}],"description":"How long conversation turns took"},"harness_evals":{"type":"array","items":{"type":"object","properties":{"batch":{"type":"string","description":"The eval script run that stored the pass (its start time), or the day for older rows"},"at":{"type":"string"},"version":{"type":"string","description":"The task set (evals/harness/<version>)"},"pairs":{"type":"number","description":"Task pairs in the pass"},"harnesses":{"type":"array","items":{"type":"object","properties":{"harness":{"type":"string"},"ours":{"type":"boolean"},"tasks":{"type":"number"},"succeeded":{"type":"number"},"checks_passed":{"type":"number"},"checks_total":{"type":"number"},"median_cost_usd":{"type":["number","null"]},"median_agent_minutes":{"type":["number","null"]},"verified":{"type":"number","description":"Runs the verifier reviewed"}},"required":["harness","ours","tasks","succeeded","checks_passed","checks_total","median_cost_usd","median_agent_minutes","verified"]}},"judge":{"type":"object","properties":{"wins":{"type":"number"},"losses":{"type":"number"},"ties":{"type":"number"}},"required":["wins","losses","ties"],"description":"The blind paired judge's decisive reads, ours against the other harness"}},"required":["batch","at","version","pairs","harnesses","judge"]},"description":"The coding harness's weekly eval: the newest pass first, then the one before it"},"withheld":{"anyOf":[{"type":"object","properties":{"active":{"type":"number","description":"Suggestions withheld right now because Letter cannot do them there"},"orgs":{"type":"number"},"recipe_bugs":{"type":"number","description":"Recipes blocked in 3 or more organizations for one reason: their conditions are wrong"},"cleared_by_staff":{"type":"number","description":"Cleared by staff in the window ('Show anyway'): a possible wrong withhold"},"off":{"type":"array","items":{"type":"string"},"description":"Blocker codes switched off in the engine settings"}},"required":["active","orgs","recipe_bugs","cleared_by_staff","off"]},{"type":"null"}],"description":"What Letter withheld because it cannot do it here (list_blockers has the list); null when it could not be read"},"turns":{"type":"object","properties":{"runs":{"type":"number","description":"Conversation turns over the window, every surface"},"by_action":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"runs":{"type":"number"}},"required":["action","runs"]}},"merge":{"type":"object","properties":{"builds":{"type":"number","description":"Builds the conversation turns started"},"merged":{"type":"number","description":"Of those, with a merged pull request"},"closed":{"type":"number"},"by_action":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"builds":{"type":"number"},"merged":{"type":"number"}},"required":["action","builds","merged"]}},"by_org":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"builds":{"type":"number"},"merged":{"type":"number"}},"required":["org_id","builds","merged"]}},"by_model":{"type":"array","items":{"type":"object","properties":{"harness":{"type":["string","null"]},"model":{"type":["string","null"]},"builds":{"type":"number"},"merged":{"type":"number"}},"required":["harness","model","builds","merged"]},"description":"By what built the change: the harness and model (Claude Code, letterpress-1 on ballpoint-1, …)"}},"required":["builds","merged","closed","by_action","by_org","by_model"],"description":"How the turns' builds fared: the merge rate is the quality signal"}},"required":["runs","by_action","merge"]},"settings":{"type":"object","properties":{"version":{"type":"number"},"updated_by":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"defaults_only":{"type":"boolean"}},"required":["version","updated_by","updated_at","defaults_only"]},"judgement_version":{"type":"number"},"weights":{"type":"object","properties":{"fix":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"build":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"next":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]},"words":{"type":"object","properties":{"obvious":{"type":"number"},"specific":{"type":"number"},"carries":{"type":"number"},"one":{"type":"number"},"serves":{"type":"number"},"prior":{"type":"number","description":"The item's prior: a fix's rung, how much of a composition runs now; 0 where it has none"}},"required":["obvious","specific","carries","one","serves","prior"]}},"required":["fix","build","next","words"]},"lenses":{"type":"array","items":{"type":"object","properties":{"lens":{"type":"string","enum":["plan","build","fix","next"]},"bar":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},{"type":"null"}]},"recipes":{"type":"number","description":"The recipes in the library (one library, read on every lens)"},"runs":{"type":"number"},"last_run_at":{"type":["string","null"]},"model":{"type":["string","null"]},"prompt_hash":{"type":["string","null"]},"followed":{"type":"object","properties":{"recipe":{"type":"number"},"none":{"type":"number"}},"required":["recipe","none"],"description":"Runs grounded in a recipe Letterpress said it followed, and in none"},"rests_on":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"cited":{"type":"number"}},"required":["source","cited"]},"description":"What the lens's shown items rested on over the window: citations by the source they were found in, most first"},"drift":{"type":"object","properties":{"pairs":{"type":"number"},"changed":{"type":"number"}},"required":["pairs","changed"],"description":"Of consecutive runs on the same host, how many showed a different list"},"marked":{"type":"object","properties":{"runs":{"type":"number"},"right":{"type":"number"},"weak":{"type":"number"},"wrong":{"type":"number"},"lead":{"type":"number"},"gap":{"type":"number"},"recipe":{"type":"number"},"top_right":{"type":"number"}},"required":["runs","right","weak","wrong","lead","gap","recipe","top_right"],"description":"The blind read: runs staff marked, items by verdict, and how often the first shown item was right"},"speed":{"anyOf":[{"type":"object","properties":{"runs":{"type":"number","description":"Runs that recorded how long they took"},"p50_ms":{"type":"number"},"p95_ms":{"type":"number","description":"The slowest tenth took at least this long"},"slow":{"type":"number","description":"Runs over a minute"}},"required":["runs","p50_ms","p95_ms","slow"]},{"type":"null"}],"description":"How long requests took over the window; null when none recorded it"}},"required":["lens","bar","recipes","runs","last_run_at","model","prompt_hash","followed","rests_on","drift","marked","speed"]}},"surfaces":{"type":"array","items":{"type":"object","properties":{"surface":{"type":"string"},"runs":{"type":"number"}},"required":["surface","runs"]}},"explore":{"type":"object","properties":{"runs":{"type":"number","description":"Exploration runs over the window"},"items":{"type":"number","description":"What they proposed, in all"},"open":{"type":"number","description":"Of those, moves no call makes yet: read in Review, the signal for what to build"},"last_run_at":{"type":["string","null"]}},"required":["runs","items","open","last_run_at"],"description":"The standing exploration ask (engine_settings.explore) over the window"},"standing":{"type":"object","properties":{"accounts":{"type":"number","description":"Accounts with their recipes decided"},"verdicts":{"type":"number"},"yes":{"type":"number","description":"Verdicts that fit"},"unknown":{"type":"number","description":"Verdicts the facts could not decide"},"last_decided_at":{"type":["string","null"]},"waiting":{"type":"number","description":"Mapped sites with no verdicts yet"}},"required":["accounts","verdicts","yes","unknown","last_decided_at","waiting"],"description":"The standing fit (recipe_fits): the verdicts kept per account, read by the Plan, Build and the door"}},"required":["as_of","days","headline","customer_only","plan_runs","turn_speed","harness_evals","withheld","turns","settings","judgement_version","weights","lenses","surfaces","explore","standing"]}},{"name":"list_letterpress_gaps","description":"Staff only. The gaps the numbers show, each with the one call that addresses it, pre-filled (docs/architecture/one-recipe-engine.md §12f): a recipe dismissed above a rate (edit it), proposals dying at one check (open the runs), a lens whose runs follow no recipe (write one), unmet asks (write a recipe or file the capability), a shadow that agrees enough to flip or disagrees enough to read first, a lens not yet in shadow. An agent does what the page's button does: make the call in `action`. `letterstory admin letterpress gaps`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"customer_only":{"description":"Leave out test and demo orgs: the customers' reading. Default false (everything).","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"gaps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"lens":{"anyOf":[{"type":"string","enum":["plan","build","fix"]},{"type":"null"}]},"gap":{"type":"string","description":"The gap, in a sentence"},"evidence":{"type":"array","items":{"type":"string"}},"action":{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"}},"required":["tool","args","label"]}},"required":["id","lens","gap","evidence","action"]}}},"required":["gaps"]}},{"name":"mark_letterpress_run","description":"Staff only. The blind read (docs/architecture/one-recipe-engine.md §12e): mark a run's shown items right (an owner says yes), weak (shown but not this company's), wrong (should not have been shown), lead (should have come first), gap (what was asked needs something Letter cannot do yet) or recipe (a play worth keeping as a recipe), each with a note. The marks live on the run, feed the status's agreement numbers and the gaps, and fill the expectation of the case replay_letterpress_run exports, so a wrong run is a guarding case in one step. The same as /superadmin/letterpress (Review) and `letterstory admin letterpress mark`. Refused unless the key was created by Letter staff. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run."},"marks":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string","minLength":1,"maxLength":300,"description":"The shown item's id, as the run's `shown` lists it."},"verdict":{"anyOf":[{"type":"string","enum":["right","weak","wrong","lead","gap","recipe"]},{"type":"null"}],"description":"right: an owner says yes; weak: shown but not this company's; wrong: should not have been shown; lead: should have come first; gap: what was asked needs something Letter cannot do yet; recipe: a play worth keeping as a recipe. null clears the mark."},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"Why, in a line; it goes into the case the run exports."}},"required":["item_id","verdict"]}}},"required":["id","marks"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"lens":{"anyOf":[{"type":"string","enum":["plan","build","fix","next"]},{"type":"null"}],"description":"Null: a conversation turn that ran no lens (a question answered, a build started)"},"domain":{"type":"string"},"host":{"type":["string","null"]},"goal":{"type":["string","null"]},"prompt":{"type":["string","null"]},"ask":{"description":"The ask as the engine received it: { outcome } | { lens } | { words }"},"surface":{"type":["string","null"]},"state":{"type":"string"},"stage":{"type":["string","null"]},"direction":{"anyOf":[{"type":"object","properties":{"recipe_id":{"type":["string","null"]},"name":{"type":["string","null"]},"by":{"type":"string","enum":["default","model","caller","pin","none"]},"reason":{"type":["string","null"]}},"required":["recipe_id","name","by","reason"]},{"type":"null"}]},"sequence":{"type":"object","properties":{"context":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"direction":{"type":"object","properties":{"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["recipes"]},"proposals":{"type":"array","items":{}},"checks":{"type":"object","properties":{"kept":{"type":"array","items":{"type":"string"}},"dropped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"}},"required":["id","reason"]}}},"required":["kept","dropped"]},"readings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"judged":{}},"required":["id","judged"]}},"shown":{"type":"array","items":{}},"model":{"type":["string","null"]},"prompt_hash":{"type":"string"},"timings":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"ms":{"type":"number"}},"required":["step","ms"]},"description":"Plan: where the run's time went, step by step"}},"description":"The run step by step; empty for a plan run still gathering"},"evidence":{"type":"array","items":{}},"plan":{"anyOf":[{},{"type":"null"}]},"error":{"type":["string","null"]},"started_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"finished_at":{"type":["string","null"]},"marks":{"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string"},"verdict":{"type":"string","enum":["right","weak","wrong","lead","gap","recipe"]},"note":{"type":["string","null"]},"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["item_id","verdict","note","by","at"]},"description":"Staff marks per shown item (the blind read); empty when unread"}},"required":["id","org_id","lens","domain","host","goal","prompt","ask","surface","state","stage","direction","sequence","evidence","plan","error","started_by","created_at","updated_at","finished_at","marks"]}},"required":["run"]}},{"name":"replay_letterpress_run","description":"Staff only. A stored Build or Fix run replayed through today's checks and, where it recorded the one judgement's readings, today's settings (or `settings` to try, unsaved): what it would keep, drop and rank now against what it showed, with no model and no network (docs/architecture/letterpress-iteration-audit.md §4, replay). The answer carries the run as an evals/letterpress case (status proposed, its expectation what it showed): a wrong run becomes a guarding case by editing the expectation and committing the file. `letterstory admin letterpress replay <id>`. Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run to replay."},"settings":{"description":"Settings to try, laid over the current ones (a weight, a bar, a mode); nothing is saved.","type":"object","properties":{"weights":{"description":"Per ask, the weight of each reading (0..1) and of the item's prior.","type":"object","properties":{"fix":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"build":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"plan":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"words":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}},"next":{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1},"prior":{"type":"number","minimum":0,"maximum":1}}}}},"lenses":{"type":"object","properties":{"build":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}},"fix":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}},"plan":{"type":"object","properties":{"bar":{"anyOf":[{"type":"object","properties":{"obvious":{"type":"number","minimum":0,"maximum":1},"specific":{"type":"number","minimum":0,"maximum":1},"carries":{"type":"number","minimum":0,"maximum":1},"one":{"type":"number","minimum":0,"maximum":1},"serves":{"type":"number","minimum":0,"maximum":1}}},{"type":"null"}],"description":"A threshold on a reading (Fix); null: a rank, no bar."},"research":{"description":"How much Letterpress may read before it answers on this surface: a budget.","type":"object","properties":{"turns":{"description":"Reads before the answer: 0..12.","type":"integer","minimum":0,"maximum":12},"ms":{"description":"Time before the answer, in ms.","type":"integer","minimum":10000,"maximum":600000}}}}}}},"asks":{"description":"Per lens, its ask in words and the guidance staff give Letterpress on how to choose there.","type":"object","properties":{"build":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}},"fix":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}},"plan":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The question the lens answers, in words; the one judgement's `serves` reads an item against it."},"guidance":{"type":"string","maxLength":4000,"description":"How to choose on this lens, in staff's words: the opinions; the guarantees stay in code."}}}}},"explore":{"type":"object","properties":{"ask":{"type":"string","minLength":1,"maxLength":4000,"description":"The standing exploration ask, in words."},"research":{"description":"How much the exploration may read: a budget, mostly reads.","type":"object","properties":{"turns":{"type":"integer","minimum":0,"maximum":20},"ms":{"type":"integer","minimum":10000,"maximum":900000}}},"every_days":{"description":"How often it runs per account.","type":"integer","minimum":1,"maximum":90}}},"dismissals":{"type":"object","properties":{"block_days":{"type":"integer","minimum":1,"maximum":365,"description":"How many days a person's dismissal blocks the thing dismissed; past them it is dated history."}}},"preflight":{"type":"object","properties":{"off":{"type":"array","items":{"type":"string","enum":["wrong_repo","not_in_repo","already_done","no_input","not_applicable","needs_info","not_a_page"]},"description":"The blocker codes (wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page) switched OFF: not enforced, so what they withheld is suggested and started again. The way back from a wrong check, with no deploy. Replaces the list; empty turns every code back on."}}}}}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string"},"lens":{"type":"string","enum":["build","fix"]},"shown":{"type":"array","items":{"type":"string"}},"replay":{"type":"object","properties":{"kept":{"type":"array","items":{"type":"string"}},"dropped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"}},"required":["id","reason"]}},"order":{"type":"array","items":{"type":"string"}},"unmet":{"type":["string","null"]},"judged":{"anyOf":[{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"}},"kept":{"type":"array","items":{"type":"string"}}},"required":["order","kept"]},{"type":"null"}]}},"required":["kept","dropped","order","unmet","judged"]},"diff":{"type":"object","properties":{"would_drop":{"type":"array","items":{"type":"string"}},"would_add":{"type":"array","items":{"type":"string"}},"order_same":{"type":"boolean"}},"required":["would_drop","would_add","order_same"]},"case":{"description":"The run as an evals/letterpress case (status proposed), its expectation what it showed"}},"required":["run_id","lens","shown","replay","diff","case"]}},{"name":"get_letterpress_run","description":"Staff only. One run of Letterpress with its whole sequence (see list_letterpress_runs). Refused unless the key was created by Letter staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run to read, with its whole sequence."}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"lens":{"anyOf":[{"type":"string","enum":["plan","build","fix","next"]},{"type":"null"}],"description":"Null: a conversation turn that ran no lens (a question answered, a build started)"},"domain":{"type":"string"},"host":{"type":["string","null"]},"goal":{"type":["string","null"]},"prompt":{"type":["string","null"]},"ask":{"description":"The ask as the engine received it: { outcome } | { lens } | { words }"},"surface":{"type":["string","null"]},"state":{"type":"string"},"stage":{"type":["string","null"]},"direction":{"anyOf":[{"type":"object","properties":{"recipe_id":{"type":["string","null"]},"name":{"type":["string","null"]},"by":{"type":"string","enum":["default","model","caller","pin","none"]},"reason":{"type":["string","null"]}},"required":["recipe_id","name","by","reason"]},{"type":"null"}]},"sequence":{"type":"object","properties":{"context":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"direction":{"type":"object","properties":{"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["recipes"]},"proposals":{"type":"array","items":{}},"checks":{"type":"object","properties":{"kept":{"type":"array","items":{"type":"string"}},"dropped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"}},"required":["id","reason"]}}},"required":["kept","dropped"]},"readings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"judged":{}},"required":["id","judged"]}},"shown":{"type":"array","items":{}},"model":{"type":["string","null"]},"prompt_hash":{"type":"string"},"timings":{"type":"array","items":{"type":"object","properties":{"step":{"type":"string"},"ms":{"type":"number"}},"required":["step","ms"]},"description":"Plan: where the run's time went, step by step"}},"description":"The run step by step; empty for a plan run still gathering"},"evidence":{"type":"array","items":{}},"plan":{"anyOf":[{},{"type":"null"}]},"error":{"type":["string","null"]},"started_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"finished_at":{"type":["string","null"]},"marks":{"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string"},"verdict":{"type":"string","enum":["right","weak","wrong","lead","gap","recipe"]},"note":{"type":["string","null"]},"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["item_id","verdict","note","by","at"]},"description":"Staff marks per shown item (the blind read); empty when unread"}},"required":["id","org_id","lens","domain","host","goal","prompt","ask","surface","state","stage","direction","sequence","evidence","plan","error","started_by","created_at","updated_at","finished_at","marks"]}},"required":["run"]}},{"name":"describe_demo_controls","description":"Staff only. How to run a demo on a demo org, and what every knob and seed does on screen (docs/architecture/demo-control.md): the steps in order (enroll, ask the first probes once, seed answers, Search Console and observations, compose and save a scenario, reset to it with a clean slate, walk through, reset again), each with the page control, the tool call and the CLI command, and for each knob (phantom, main blog, Visitors, probes, CMS, publish target, Search Console, GitHub, domain, Set-up, domain cap) and seed (answers, Search Console queries, observations, pins) where it shows in the product in the words the viewer sees, what brings it back on camera, and the thresholds that decide whether it shows at all. Read it before set_demo_state. Refused unless the key was created by Letterstory staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"steps":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"do":{"type":"string"},"page":{"type":"string"},"tool":{"type":"string"},"cli":{"type":"string"},"result":{"type":"string","description":"What it changes on screen"}},"required":["title","do","page","tool","cli","result"]},"description":"How to run a demo, in order, on each door"},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"what":{"type":"string"},"shows_as":{"type":"array","items":{"type":"string"},"description":"Where it shows in the product, in the words the viewer sees"},"values":{"type":"string"},"on_camera":{"description":"What brings it back on camera","type":["string","null"]},"mind":{"type":"array","items":{"type":"string"},"description":"Thresholds and limits"}},"required":["id","label","what","shows_as","values","on_camera","mind"]}}},"required":["steps","entries"]}},{"name":"list_demo_orgs","description":"Staff only. The demo orgs (docs/architecture/demo-control.md): test orgs whose state staff set, rehearse and reset for a demo, each with the scenario last applied and the items it shows differently from what it really has (a phantom parked, the main blog parked, Search Console hidden, …). include_candidates adds the test orgs (is_test) not enrolled yet. Refused unless the key was created by Letterstory staff; the same as the /superadmin/demo page and `letterstory admin demo list`. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"include_candidates":{"description":"Also list test orgs (is_test) not enrolled yet, which set_demo_state with enroll:true can enroll.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgs":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"]},"is_test":{"type":"boolean"},"enrolled":{"type":"boolean","description":"False: a test org set_demo_state with enroll:true can enroll"},"scenario":{"type":["string","null"]},"differs":{"type":"array","items":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console","github","domain","setup"]}},"applied_at":{"type":["string","null"]},"auto_reset":{"anyOf":[{"type":"object","properties":{"scenario":{"type":"string"},"every_minutes":{"type":"number"},"next_due_at":{"description":"When it is next due (it still waits for the org to be idle)","type":["string","null"]}},"required":["scenario","every_minutes","next_due_at"]},{"type":"null"}],"description":"Resetting on its own (set_demo_state auto_reset); null: only a person resets it"},"held":{"anyOf":[{"type":"object","properties":{"until":{"type":"string"},"by":{"type":["string","null"]}},"required":["until","by"]},{"type":"null"}],"description":"A person giving a demo holds the org until then (reset_demo_state hold_minutes); null: free"},"ready":{"description":"The last readiness check passed; null before the first","type":["boolean","null"]},"readiness":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"},{"type":"null"}],"description":"The last readiness check; null before the first"}},"required":["org_id","name","domain","is_test","enrolled","scenario","differs","applied_at","auto_reset","held","ready","readiness"]}}},"required":["orgs"]}},{"name":"get_demo_state","description":"Staff only. One demo org as the demo page reads it: every knob with what the org really has beside what it shows and who set it when (the phantom, the main blog, Visitors, the first probes' answers, the CMS, the repo publish target, Search Console, GitHub, the domain, Set-up, domain_cap; Slack and Linear read-only), the observations Fix ranks on its main site now (injected ones marked), the questions it was asked with how many answers name the brand, the scenarios it can be reset to, and its history. with_moves adds each goal's plan rows as the org sees them now (the ids a pin, set_demo_state state.pins, can name; which ones reveal a parked thing) and the standing lines a plan run's checklist will read first (GEO's targets and answers, SEO's Search Console), from the same functions the run calls. describe_demo_controls says what each knob shows. Refused unless the key was created by Letterstory staff. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The demo org."},"with_moves":{"description":"Also read each goal's rows as the org would see them now (for choosing a pin).","type":"boolean"}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"org":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"domain":{"description":"The real domain (the stash's while it is hidden)","type":["string","null"]},"is_test":{"type":"boolean"},"domain_cap":{"type":["number","null"]}},"required":["id","name","domain","is_test","domain_cap"]},"state":{"type":"object","properties":{"org_id":{"type":"string"},"scenario":{"description":"The scenario last applied (reset_demo_state), or null","type":["string","null"]},"state":{"type":"object","properties":{"ranking_targets":{"description":"parked: the company-wide ranking targets are left out (never deleted); \"Choose what to rank for\" offers them back one tap at a time","type":"string","enum":["shown","parked"]},"phantom":{"description":"parked: the org's phantoms are left out (never deleted)","type":"string","enum":["shown","parked"]},"main_blog":{"description":"parked: the main blog collection is left out","type":"string","enum":["shown","parked"]},"visitors":{"description":"parked: the Visitors site is left out","type":"string","enum":["shown","parked"]},"probes":{"description":"not_run: the first probes' answers are kept but not read","type":"string","enum":["shown","not_run"]},"cms":{"description":"hidden: the plan reads no CMS connected","type":"string","enum":["shown","hidden"]},"publish_target":{"description":"hidden: the plan reads no repo publish target","type":"string","enum":["shown","hidden"]},"search_console":{"description":"hidden: the Search Console property is stashed; the sign-in stays, so Connect opens at the picker","type":"string","enum":["shown","hidden"]},"github":{"description":"hidden: the GitHub App installations are marked removed","type":"string","enum":["shown","hidden"]},"domain":{"description":"hidden: the org's domain is stashed and emptied","type":"string","enum":["shown","hidden"]},"setup":{"description":"fresh: Set-up shows again on the next load","type":"string","enum":["finished","fresh"]},"domain_cap":{"description":"How many bought domains the org may hold (Fleet → Tiers' column); 0 lands a launch on a subdomain","anyOf":[{"type":"integer","minimum":0,"maximum":50},{"type":"null"}]},"observations":{"type":"object","properties":{"inject":{"description":"Keys opened as stored rows with source 'demo' (skipped when the site really has one open)","maxItems":40,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}},"hide":{"description":"Keys left out of Fix, the Site and the plan's fix row","maxItems":80,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}}}},"answers":{"description":"Per question (a probe's id): the brand written into its real answers so they read named (most answers), nearly (one of three or more) or absent (none; the rivals keep it). A question left out reads as it was really answered.","type":"object","propertyNames":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"additionalProperties":{"type":"string","enum":["named","nearly","absent"]}},"search":{"description":"Search Console's queries for a made-up site: written as the org's own rows over the last 28 days (re-dated on every reset), on its connection (one is made, marked, when it has none; the sync skips it). Empty queries take them out.","type":"object","properties":{"queries":{"maxItems":40,"type":"array","items":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"The page it lands on: a path on the org's site (/pricing) or a full URL; the home page when omitted","type":"string","maxLength":300},"position":{"type":"number","minimum":1,"maximum":100,"description":"Average position: 1-3 reads as a strength, 4-20 as a near win"},"impressions":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"},"clicks":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"}},"required":["query","position","impressions","clicks"]}}},"required":["queries"]},"pins":{"description":"A goal's plan, chosen exactly: used instead of the model","type":"object","properties":{"agent_conviction":{"description":"The agent_conviction plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"geo":{"description":"The geo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"seo":{"description":"The seo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"inbound_conversion":{"description":"The inbound_conversion plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"outbound":{"description":"The outbound plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"other":{"description":"The other plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]}}},"reveal_pace_seconds":{"description":"A revealed phantom reads as launching, revealed probes as being asked, for this long","type":"integer","minimum":0,"maximum":600},"main_blog_posts_per_week":{"description":"The main blog's automations seeded to this many posts a week in all (the starter set's own default is 3); re-seeded on every reset, alongside the phantom's own starter set","type":"integer","minimum":1,"maximum":7}},"description":"What the org shows: absent fields are the real thing"},"differs":{"type":"array","items":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console","github","domain","setup"]},"description":"The items the org shows differently from what it really has"},"marks":{"description":"Who set each knob, and when","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["by","at"]}},"parked":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}},"description":"The ids each kind keeps out of the readers"},"stashed":{"type":"object","properties":{"domain":{"description":"The real domain while it is hidden","type":["string","null"]},"search_console":{"description":"The Search Console property while it is hidden","type":["string","null"]},"github":{"type":"number","description":"GitHub App installations marked removed by the scenario"},"setup":{"type":"boolean","description":"Set-up's finish row is stashed (it shows again)"}},"required":["domain","search_console","github","setup"]},"scenarios":{"type":"array","items":{"type":"string"},"description":"Built-in scenarios, then the org's saved ones"},"history":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"by":{"type":["string","null"]},"name":{"type":["string","null"]},"change":{"type":"string"}},"required":["at","by","name","change"]}},"enrolled_by":{"type":["string","null"]},"applied_by":{"type":["string","null"]},"applied_at":{"type":["string","null"]},"updated_at":{"type":"string"},"auto_reset":{"anyOf":[{"type":"object","properties":{"scenario":{"type":"string"},"every_minutes":{"type":"number"},"next_due_at":{"description":"When it is next due (it still waits for the org to be idle)","type":["string","null"]}},"required":["scenario","every_minutes","next_due_at"]},{"type":"null"}],"description":"Resetting on its own (set_demo_state auto_reset); null: only a person resets it"},"held":{"anyOf":[{"type":"object","properties":{"until":{"type":"string"},"by":{"type":["string","null"]}},"required":["until","by"]},{"type":"null"}],"description":"A person giving a demo holds the org until then (reset_demo_state hold_minutes); null: free"},"readiness":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"},{"type":"null"}],"description":"The last readiness check; null before the first"},"repo_baseline":{"anyOf":[{"type":"object","properties":{"repo":{"type":"string"},"branch":{"type":"string"},"sha":{"type":"string"},"set_at":{"type":"string"},"set_by":{"type":["string","null"]}},"required":["repo","branch","sha","set_at","set_by"]},{"type":"null"}],"description":"The commit the org's repo is restored to by a reset with restore_repo; null: none recorded"}},"required":["org_id","scenario","state","differs","parked","stashed","scenarios","history","enrolled_by","applied_by","applied_at","updated_at","auto_reset","held","readiness","repo_baseline"]},"knobs":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"how":{"type":"string","enum":["overlay","stash","real","read_only"]},"real":{"type":"string","description":"What the org really has"},"shown":{"type":"string","description":"What it shows"},"set_by":{"type":["string","null"]},"set_at":{"type":["string","null"]}},"required":["item","how","real","shown","set_by","set_at"]}},"observations":{"type":"object","properties":{"open":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"summary":{"type":"string"},"injected":{"type":"boolean"}},"required":["key","summary","injected"]}},"injected":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"decided_by":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["key","decided_by","created_at"]}},"hidden":{"type":"array","items":{"type":"string"}}},"required":["open","injected","hidden"]},"answers":{"type":"array","items":{"type":"object","properties":{"prompt_id":{"type":"string"},"text":{"type":"string"},"answers":{"type":"number"},"named":{"type":"number"}},"required":["prompt_id","text","answers","named"]}},"search":{"type":"object","properties":{"connection":{"type":"string","enum":["demo_seed","real_grant","none"],"description":"demo_seed: a connection made for the demo, with no Google grant"},"queries":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"The page it lands on: a path on the org's site (/pricing) or a full URL; the home page when omitted","type":"string","maxLength":300},"position":{"type":"number","minimum":1,"maximum":100,"description":"Average position: 1-3 reads as a strength, 4-20 as a near win"},"impressions":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"},"clicks":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"}},"required":["query","position","impressions","clicks"]}}},"required":["connection","queries"]},"standing":{"anyOf":[{"type":"object","properties":{"geo":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"line":{"type":"string"},"detail":{"type":"array","items":{"type":"string"}}},"required":["status","line","detail"]}},"seo":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"line":{"type":"string"},"detail":{"type":"array","items":{"type":"string"}}},"required":["status","line","detail"]}}},"required":["geo","seo"]},{"type":"null"}],"description":"The lines a plan run's checklist reads first, as the org shows them now (with_moves)"},"moves":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"stage":{"type":"string","description":"setup (Set up), launched (What we set up for you), result (What we recommend)"},"reveals":{"type":"boolean","description":"Approving it brings back a parked or hidden thing (reveal_demo_item)"},"optional":{"type":"boolean"}},"required":["id","title","status","stage","reveals","optional"]}}},{"type":"null"}],"description":"Each goal's rows as the org sees them now, in order (with_moves); the plan shows up to three"},"recipe_runs":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The catalog row a live recipe runs (its `runs`), e.g. visitors:site"},"title":{"type":"string"},"status":{"type":"string"},"reveals":{"type":"boolean","description":"Starting the recipe brings back a parked or hidden thing (reveal_demo_item)"}},"required":["id","title","status","reveals"]}},{"type":"null"}],"description":"The catalog rows a live recipe runs, as the org sees them now (with_moves); null when not read"}},"required":["org","state","knobs","observations","answers","search","standing","moves","recipe_runs"]}},{"name":"set_demo_state","description":"Staff only. Set what a demo org shows (docs/architecture/demo-control.md), merged over what it shows now: phantom / main_blog / visitors parked (left out of the plan, the lists and the Visitors tools; never deleted, so the bought domain stays), probes not_run (the answers kept, not read), cms / publish_target hidden (the plan reads them unconnected), search_console hidden (the property stashed; the sign-in stays, so Connect opens at the property picker), github hidden (the App installations marked removed), domain hidden (stashed and emptied), setup fresh (Set-up shows again), domain_cap (the column Fleet → Tiers writes), observations.inject (opened as stored rows with source 'demo') and .hide (left out of Fix, the Site and the plan's fix row), answers (per question: the brand written into its real answers so they read named, nearly or absent), search.queries (Search Console queries for a made-up site, each with its 28-day position, impressions and clicks, written as the org's own rows on its connection — one is made, with no Google grant, when it has none — and re-dated on every reset), pins (a goal's plan chosen exactly, held to the catalog), reveal_pace_seconds. 'shown' puts a thing back. enroll:true enrolls a test org (is_test; any other org is refused); unenroll:true puts everything back and removes the org; save_as saves what it shows as a named scenario for reset_demo_state. copy_from sets an org up from one already set up (enroll it in the same call): the template's saved scenarios (or the ones named) are copied here with the answers re-keyed to this org's own questions, their shape kept; Search Console queries and pins belong to the template's site and are not carried; what the org shows now and its auto-reset are untouched, so a new org resets only when someone resets it. repo_baseline records the commit restore_repo puts the repo back to ({repo: 'owner/name', sha?}: the default branch as it is now when sha is omitted; null forgets it). auto_reset makes the org reset itself to a scenario every every_minutes (default 30) once nobody has acted in it for 15 minutes and no one holds it, with a readiness check after each and an alert when one fails; release_hold ends a hold. Every change records who and when. Refused unless the key was created by Letterstory staff. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The demo org."},"enroll":{"description":"Enroll a test org (is_test) as a demo org; refused for any other org.","type":"boolean"},"unenroll":{"description":"Put everything back as it really is and remove the org from the demo list.","type":"boolean"},"state":{"description":"The knobs to change, merged over what the org shows now. 'shown' puts a thing back.","type":"object","properties":{"ranking_targets":{"description":"parked: the company-wide ranking targets are left out (never deleted); \"Choose what to rank for\" offers them back one tap at a time","type":"string","enum":["shown","parked"]},"phantom":{"description":"parked: the org's phantoms are left out (never deleted)","type":"string","enum":["shown","parked"]},"main_blog":{"description":"parked: the main blog collection is left out","type":"string","enum":["shown","parked"]},"visitors":{"description":"parked: the Visitors site is left out","type":"string","enum":["shown","parked"]},"probes":{"description":"not_run: the first probes' answers are kept but not read","type":"string","enum":["shown","not_run"]},"cms":{"description":"hidden: the plan reads no CMS connected","type":"string","enum":["shown","hidden"]},"publish_target":{"description":"hidden: the plan reads no repo publish target","type":"string","enum":["shown","hidden"]},"search_console":{"description":"hidden: the Search Console property is stashed; the sign-in stays, so Connect opens at the picker","type":"string","enum":["shown","hidden"]},"github":{"description":"hidden: the GitHub App installations are marked removed","type":"string","enum":["shown","hidden"]},"domain":{"description":"hidden: the org's domain is stashed and emptied","type":"string","enum":["shown","hidden"]},"setup":{"description":"fresh: Set-up shows again on the next load","type":"string","enum":["finished","fresh"]},"domain_cap":{"description":"How many bought domains the org may hold (Fleet → Tiers' column); 0 lands a launch on a subdomain","anyOf":[{"type":"integer","minimum":0,"maximum":50},{"type":"null"}]},"observations":{"type":"object","properties":{"inject":{"description":"Keys opened as stored rows with source 'demo' (skipped when the site really has one open)","maxItems":40,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}},"hide":{"description":"Keys left out of Fix, the Site and the plan's fix row","maxItems":80,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}}}},"answers":{"description":"Per question (a probe's id): the brand written into its real answers so they read named (most answers), nearly (one of three or more) or absent (none; the rivals keep it). A question left out reads as it was really answered.","type":"object","propertyNames":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"additionalProperties":{"type":"string","enum":["named","nearly","absent"]}},"search":{"description":"Search Console's queries for a made-up site: written as the org's own rows over the last 28 days (re-dated on every reset), on its connection (one is made, marked, when it has none; the sync skips it). Empty queries take them out.","type":"object","properties":{"queries":{"maxItems":40,"type":"array","items":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"The page it lands on: a path on the org's site (/pricing) or a full URL; the home page when omitted","type":"string","maxLength":300},"position":{"type":"number","minimum":1,"maximum":100,"description":"Average position: 1-3 reads as a strength, 4-20 as a near win"},"impressions":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"},"clicks":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"}},"required":["query","position","impressions","clicks"]}}},"required":["queries"]},"pins":{"description":"A goal's plan, chosen exactly: used instead of the model","type":"object","properties":{"agent_conviction":{"description":"The agent_conviction plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"geo":{"description":"The geo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"seo":{"description":"The seo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"inbound_conversion":{"description":"The inbound_conversion plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"outbound":{"description":"The outbound plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"other":{"description":"The other plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]}}},"reveal_pace_seconds":{"description":"A revealed phantom reads as launching, revealed probes as being asked, for this long","type":"integer","minimum":0,"maximum":600},"main_blog_posts_per_week":{"description":"The main blog's automations seeded to this many posts a week in all (the starter set's own default is 3); re-seeded on every reset, alongside the phantom's own starter set","type":"integer","minimum":1,"maximum":7}}},"save_as":{"description":"Save what the org shows after this change as a named scenario.","type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9][a-z0-9_-]*$"},"repo_baseline":{"description":"The commit the org's repo is put back to by a reset that asks for it (reset_demo_state restore_repo): record it once the repo is what the demo should open on. null forgets it. A setting, not part of what the org shows.","anyOf":[{"type":"object","properties":{"repo":{"type":"string","pattern":"^[\\w.-]+\\/[\\w.-]+$","description":"The org's repo the demo serves, owner/name."},"sha":{"description":"The commit to restore to; the default branch as it is now when omitted.","type":"string","pattern":"^[0-9a-f]{7,40}$"}},"required":["repo"]},{"type":"null"}]},"copy_from":{"description":"Set this org up from one already set up: its saved scenarios are copied here with the answers re-keyed to this org's own questions (their shape kept: how many read named, nearly, absent). Search Console queries and pins belong to the template's site and are not carried. Changes only what can be reset to; what the org shows now, and its auto-reset, stay as they are. Use with enroll for a new org.","type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The demo org to copy saved scenarios from."},"scenarios":{"description":"Which saved scenarios to copy; all of that org's when omitted.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9][a-z0-9_-]*$"}},"overwrite":{"description":"Replace a scenario of the same name already saved here (skipped by default).","type":"boolean"}},"required":["org_id"]},"auto_reset":{"description":"Reset the org on its own: every every_minutes after the last reset, unless a person holds it (reset_demo_state hold_minutes) or acted in it in the last 15 minutes. Each automatic reset runs the readiness check, and a failed one alerts.","type":"object","properties":{"scenario":{"anyOf":[{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9][a-z0-9_-]*$"},{"type":"null"}],"description":"The scenario to reset to when due and idle (built in or saved on the org); null turns it off."},"every_minutes":{"description":"Due this long after the last reset (default 30).","type":"integer","minimum":10,"maximum":1440}},"required":["scenario"]},"release_hold":{"description":"End a hold now: the org is free for the next automatic reset.","type":"boolean"}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"state":{"anyOf":[{"type":"object","properties":{"org_id":{"type":"string"},"scenario":{"description":"The scenario last applied (reset_demo_state), or null","type":["string","null"]},"state":{"type":"object","properties":{"ranking_targets":{"description":"parked: the company-wide ranking targets are left out (never deleted); \"Choose what to rank for\" offers them back one tap at a time","type":"string","enum":["shown","parked"]},"phantom":{"description":"parked: the org's phantoms are left out (never deleted)","type":"string","enum":["shown","parked"]},"main_blog":{"description":"parked: the main blog collection is left out","type":"string","enum":["shown","parked"]},"visitors":{"description":"parked: the Visitors site is left out","type":"string","enum":["shown","parked"]},"probes":{"description":"not_run: the first probes' answers are kept but not read","type":"string","enum":["shown","not_run"]},"cms":{"description":"hidden: the plan reads no CMS connected","type":"string","enum":["shown","hidden"]},"publish_target":{"description":"hidden: the plan reads no repo publish target","type":"string","enum":["shown","hidden"]},"search_console":{"description":"hidden: the Search Console property is stashed; the sign-in stays, so Connect opens at the picker","type":"string","enum":["shown","hidden"]},"github":{"description":"hidden: the GitHub App installations are marked removed","type":"string","enum":["shown","hidden"]},"domain":{"description":"hidden: the org's domain is stashed and emptied","type":"string","enum":["shown","hidden"]},"setup":{"description":"fresh: Set-up shows again on the next load","type":"string","enum":["finished","fresh"]},"domain_cap":{"description":"How many bought domains the org may hold (Fleet → Tiers' column); 0 lands a launch on a subdomain","anyOf":[{"type":"integer","minimum":0,"maximum":50},{"type":"null"}]},"observations":{"type":"object","properties":{"inject":{"description":"Keys opened as stored rows with source 'demo' (skipped when the site really has one open)","maxItems":40,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}},"hide":{"description":"Keys left out of Fix, the Site and the plan's fix row","maxItems":80,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}}}},"answers":{"description":"Per question (a probe's id): the brand written into its real answers so they read named (most answers), nearly (one of three or more) or absent (none; the rivals keep it). A question left out reads as it was really answered.","type":"object","propertyNames":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"additionalProperties":{"type":"string","enum":["named","nearly","absent"]}},"search":{"description":"Search Console's queries for a made-up site: written as the org's own rows over the last 28 days (re-dated on every reset), on its connection (one is made, marked, when it has none; the sync skips it). Empty queries take them out.","type":"object","properties":{"queries":{"maxItems":40,"type":"array","items":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"The page it lands on: a path on the org's site (/pricing) or a full URL; the home page when omitted","type":"string","maxLength":300},"position":{"type":"number","minimum":1,"maximum":100,"description":"Average position: 1-3 reads as a strength, 4-20 as a near win"},"impressions":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"},"clicks":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"}},"required":["query","position","impressions","clicks"]}}},"required":["queries"]},"pins":{"description":"A goal's plan, chosen exactly: used instead of the model","type":"object","properties":{"agent_conviction":{"description":"The agent_conviction plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"geo":{"description":"The geo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"seo":{"description":"The seo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"inbound_conversion":{"description":"The inbound_conversion plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"outbound":{"description":"The outbound plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"other":{"description":"The other plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]}}},"reveal_pace_seconds":{"description":"A revealed phantom reads as launching, revealed probes as being asked, for this long","type":"integer","minimum":0,"maximum":600},"main_blog_posts_per_week":{"description":"The main blog's automations seeded to this many posts a week in all (the starter set's own default is 3); re-seeded on every reset, alongside the phantom's own starter set","type":"integer","minimum":1,"maximum":7}},"description":"What the org shows: absent fields are the real thing"},"differs":{"type":"array","items":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console","github","domain","setup"]},"description":"The items the org shows differently from what it really has"},"marks":{"description":"Who set each knob, and when","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["by","at"]}},"parked":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}},"description":"The ids each kind keeps out of the readers"},"stashed":{"type":"object","properties":{"domain":{"description":"The real domain while it is hidden","type":["string","null"]},"search_console":{"description":"The Search Console property while it is hidden","type":["string","null"]},"github":{"type":"number","description":"GitHub App installations marked removed by the scenario"},"setup":{"type":"boolean","description":"Set-up's finish row is stashed (it shows again)"}},"required":["domain","search_console","github","setup"]},"scenarios":{"type":"array","items":{"type":"string"},"description":"Built-in scenarios, then the org's saved ones"},"history":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"by":{"type":["string","null"]},"name":{"type":["string","null"]},"change":{"type":"string"}},"required":["at","by","name","change"]}},"enrolled_by":{"type":["string","null"]},"applied_by":{"type":["string","null"]},"applied_at":{"type":["string","null"]},"updated_at":{"type":"string"},"auto_reset":{"anyOf":[{"type":"object","properties":{"scenario":{"type":"string"},"every_minutes":{"type":"number"},"next_due_at":{"description":"When it is next due (it still waits for the org to be idle)","type":["string","null"]}},"required":["scenario","every_minutes","next_due_at"]},{"type":"null"}],"description":"Resetting on its own (set_demo_state auto_reset); null: only a person resets it"},"held":{"anyOf":[{"type":"object","properties":{"until":{"type":"string"},"by":{"type":["string","null"]}},"required":["until","by"]},{"type":"null"}],"description":"A person giving a demo holds the org until then (reset_demo_state hold_minutes); null: free"},"readiness":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"},{"type":"null"}],"description":"The last readiness check; null before the first"},"repo_baseline":{"anyOf":[{"type":"object","properties":{"repo":{"type":"string"},"branch":{"type":"string"},"sha":{"type":"string"},"set_at":{"type":"string"},"set_by":{"type":["string","null"]}},"required":["repo","branch","sha","set_at","set_by"]},{"type":"null"}],"description":"The commit the org's repo is restored to by a reset with restore_repo; null: none recorded"}},"required":["org_id","scenario","state","differs","parked","stashed","scenarios","history","enrolled_by","applied_by","applied_at","updated_at","auto_reset","held","readiness","repo_baseline"]},{"type":"null"}],"description":"Null once unenrolled"},"notes":{"type":"array","items":{"type":"string"},"description":"What was done, in words"}},"required":["state","notes"]}},{"name":"reset_demo_state","description":"Staff only. Reset a demo org to a named scenario, for a rehearsal or the take: first undo the last take (the PRs it opened are closed on GitHub, its builds archived, the automations it applied removed, the main blog's planner put back, the plan runs and the plan's dismissals deleted; clean_slate closes every open PR the org's repo changes left and archives every open build — each can be restored — for a demo that starts from nothing), then show the scenario: built in (real: everything as it is; fresh_geo; fresh_seo; after_approve) or one saved on the org with set_demo_state save_as. Never deletes a phantom, a collection or a Visitors site: it parks them. hold_minutes starts a demo: the org is held that long so no automatic reset lands mid-take (0 ends a hold). restore_repo (revert | force) puts the org's repo back to its baseline commit (set_demo_state repo_baseline) and deletes the branches its repo changes left: revert merges a PR with the baseline's files (history grows), force moves the default branch back to the exact commit (history rewritten); it is the only way a merged PR is undone, and is refused without a baseline; the automatic reset never does either. reread_company starts from a new org's company state: it forgets what the company read filled (the manifesto, the positioning row, the inferred identity, the tracked competitors, the read's record; the cleared values are kept in the org's stash as company_before) and reads the company again from the site before the passes that use it; it implies rebuild, and leaves ranking targets, the probes' answers, phantoms and domains alone. rebuild reads the site again after the reset (re-map, re-audit, the signals, Jev's readings, the Build and Fix passes on each owned host; model credit and minutes; the seeded company read and the probes' answers are kept) and records when it finishes in the org's history. Every reset ends with the readiness check (run_demo_readiness_check), returned as readiness. Refused unless the key was created by Letterstory staff; the same as the page's Reset and `letterstory admin demo reset`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The demo org."},"clean_slate":{"description":"Start from nothing: close every open PR the org's repo changes left and archive every open build (Build → Archived; each can be restored), not only the last take's.","type":"boolean"},"scenario":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9][a-z0-9_-]*$","description":"A built-in scenario (real, fresh_geo, fresh_seo, after_approve) or one saved on the org."},"close_prs":{"description":"Which open PRs to close first: since_applied (default: those the last take opened), all_open (every open PR the org's repo changes left), none.","type":"string","enum":["since_applied","all_open","none"]},"hold_minutes":{"description":"Start a demo: hold the org this long after the reset so no automatic reset lands mid-take (0 ends a hold).","type":"integer","minimum":0,"maximum":180},"restore_repo":{"description":"Put the org's repo back to its baseline commit (set_demo_state repo_baseline) and delete the branches its repo changes left: revert (a new commit with the baseline's files, merged through a PR; history grows) or force (the default branch moved back to the exact commit; history rewritten). Refused without a baseline. Merged PRs are undone only this way.","type":"string","enum":["revert","force"]},"reread_company":{"description":"Start from a new org's company state: forget what the company read filled (the manifesto, the positioning row, the inferred identity, the tracked competitors, the read's record) and read the company again from the site, before the passes that use it. Implies rebuild. The cleared values are kept in the org's stash (company_before). Ranking targets, the probes' answers, phantoms and domains are untouched.","type":"boolean"},"rebuild":{"description":"Read the site again after the reset: re-map, re-audit, the signals, Jev's readings and the Build and Fix passes, on each owned host (refresh_site with force_map and force_audit). Spends model credit and takes minutes; runs after the reset returns, and says when it finishes in the org's history. The seeded company read and the probes' answers are kept.","type":"boolean"}},"required":["org_id","scenario"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"state":{"type":"object","properties":{"org_id":{"type":"string"},"scenario":{"description":"The scenario last applied (reset_demo_state), or null","type":["string","null"]},"state":{"type":"object","properties":{"ranking_targets":{"description":"parked: the company-wide ranking targets are left out (never deleted); \"Choose what to rank for\" offers them back one tap at a time","type":"string","enum":["shown","parked"]},"phantom":{"description":"parked: the org's phantoms are left out (never deleted)","type":"string","enum":["shown","parked"]},"main_blog":{"description":"parked: the main blog collection is left out","type":"string","enum":["shown","parked"]},"visitors":{"description":"parked: the Visitors site is left out","type":"string","enum":["shown","parked"]},"probes":{"description":"not_run: the first probes' answers are kept but not read","type":"string","enum":["shown","not_run"]},"cms":{"description":"hidden: the plan reads no CMS connected","type":"string","enum":["shown","hidden"]},"publish_target":{"description":"hidden: the plan reads no repo publish target","type":"string","enum":["shown","hidden"]},"search_console":{"description":"hidden: the Search Console property is stashed; the sign-in stays, so Connect opens at the picker","type":"string","enum":["shown","hidden"]},"github":{"description":"hidden: the GitHub App installations are marked removed","type":"string","enum":["shown","hidden"]},"domain":{"description":"hidden: the org's domain is stashed and emptied","type":"string","enum":["shown","hidden"]},"setup":{"description":"fresh: Set-up shows again on the next load","type":"string","enum":["finished","fresh"]},"domain_cap":{"description":"How many bought domains the org may hold (Fleet → Tiers' column); 0 lands a launch on a subdomain","anyOf":[{"type":"integer","minimum":0,"maximum":50},{"type":"null"}]},"observations":{"type":"object","properties":{"inject":{"description":"Keys opened as stored rows with source 'demo' (skipped when the site really has one open)","maxItems":40,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}},"hide":{"description":"Keys left out of Fix, the Site and the plan's fix row","maxItems":80,"type":"array","items":{"type":"string","minLength":3,"maxLength":120,"pattern":"^[a-z]+[.:][\\w.:-]+$"}}}},"answers":{"description":"Per question (a probe's id): the brand written into its real answers so they read named (most answers), nearly (one of three or more) or absent (none; the rivals keep it). A question left out reads as it was really answered.","type":"object","propertyNames":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"additionalProperties":{"type":"string","enum":["named","nearly","absent"]}},"search":{"description":"Search Console's queries for a made-up site: written as the org's own rows over the last 28 days (re-dated on every reset), on its connection (one is made, marked, when it has none; the sync skips it). Empty queries take them out.","type":"object","properties":{"queries":{"maxItems":40,"type":"array","items":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"The page it lands on: a path on the org's site (/pricing) or a full URL; the home page when omitted","type":"string","maxLength":300},"position":{"type":"number","minimum":1,"maximum":100,"description":"Average position: 1-3 reads as a strength, 4-20 as a near win"},"impressions":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"},"clicks":{"type":"integer","minimum":0,"maximum":1000000,"description":"Over the last 28 days"}},"required":["query","position","impressions","clicks"]}}},"required":["queries"]},"pins":{"description":"A goal's plan, chosen exactly: used instead of the model","type":"object","properties":{"agent_conviction":{"description":"The agent_conviction plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"geo":{"description":"The geo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"seo":{"description":"The seo plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"inbound_conversion":{"description":"The inbound_conversion plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"outbound":{"description":"The outbound plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]},"other":{"description":"The other plan; null unpins it","anyOf":[{"type":"object","properties":{"picks":{"maxItems":5,"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string","minLength":1,"maxLength":120,"description":"A catalog action id, e.g. instances:phantom"},"why":{"type":"string","maxLength":200,"description":"To the customer, why it comes first; past 12 words the catalog's own why"}},"required":["action_id","why"]},"description":"The rows, most important first. An id the catalog doesn't hold (or that is done) is dropped."},"changes":{"maxItems":3,"type":"array","items":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"instructions":{"type":"string","minLength":10,"maxLength":4000,"description":"For the coding agent: what to build"},"why":{"type":"string","maxLength":200}},"required":["title","instructions","why"]},"description":"Site changes the plan proposes beside the rows."}},"required":["picks","changes"]},{"type":"null"}]}}},"reveal_pace_seconds":{"description":"A revealed phantom reads as launching, revealed probes as being asked, for this long","type":"integer","minimum":0,"maximum":600},"main_blog_posts_per_week":{"description":"The main blog's automations seeded to this many posts a week in all (the starter set's own default is 3); re-seeded on every reset, alongside the phantom's own starter set","type":"integer","minimum":1,"maximum":7}},"description":"What the org shows: absent fields are the real thing"},"differs":{"type":"array","items":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console","github","domain","setup"]},"description":"The items the org shows differently from what it really has"},"marks":{"description":"Who set each knob, and when","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"by":{"type":["string","null"]},"at":{"type":"string"}},"required":["by","at"]}},"parked":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}},"description":"The ids each kind keeps out of the readers"},"stashed":{"type":"object","properties":{"domain":{"description":"The real domain while it is hidden","type":["string","null"]},"search_console":{"description":"The Search Console property while it is hidden","type":["string","null"]},"github":{"type":"number","description":"GitHub App installations marked removed by the scenario"},"setup":{"type":"boolean","description":"Set-up's finish row is stashed (it shows again)"}},"required":["domain","search_console","github","setup"]},"scenarios":{"type":"array","items":{"type":"string"},"description":"Built-in scenarios, then the org's saved ones"},"history":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"by":{"type":["string","null"]},"name":{"type":["string","null"]},"change":{"type":"string"}},"required":["at","by","name","change"]}},"enrolled_by":{"type":["string","null"]},"applied_by":{"type":["string","null"]},"applied_at":{"type":["string","null"]},"updated_at":{"type":"string"},"auto_reset":{"anyOf":[{"type":"object","properties":{"scenario":{"type":"string"},"every_minutes":{"type":"number"},"next_due_at":{"description":"When it is next due (it still waits for the org to be idle)","type":["string","null"]}},"required":["scenario","every_minutes","next_due_at"]},{"type":"null"}],"description":"Resetting on its own (set_demo_state auto_reset); null: only a person resets it"},"held":{"anyOf":[{"type":"object","properties":{"until":{"type":"string"},"by":{"type":["string","null"]}},"required":["until","by"]},{"type":"null"}],"description":"A person giving a demo holds the org until then (reset_demo_state hold_minutes); null: free"},"readiness":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"},{"type":"null"}],"description":"The last readiness check; null before the first"},"repo_baseline":{"anyOf":[{"type":"object","properties":{"repo":{"type":"string"},"branch":{"type":"string"},"sha":{"type":"string"},"set_at":{"type":"string"},"set_by":{"type":["string","null"]}},"required":["repo","branch","sha","set_at","set_by"]},{"type":"null"}],"description":"The commit the org's repo is restored to by a reset with restore_repo; null: none recorded"}},"required":["org_id","scenario","state","differs","parked","stashed","scenarios","history","enrolled_by","applied_by","applied_at","updated_at","auto_reset","held","readiness","repo_baseline"]},"notes":{"type":"array","items":{"type":"string"}},"undone":{"type":"object","properties":{"prs_closed":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"number":{"type":"number"}},"required":["repo","number"]}},"prs_failed":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"number":{"type":"number"},"error":{"type":"string"}},"required":["repo","number","error"]}},"automations_removed":{"type":"number"},"planner_restored":{"type":"boolean"},"builds_archived":{"type":"number","description":"Builds put away (Build → Archived); each can be restored"},"phantom_posts_removed":{"type":"number","description":"Posts deleted off a phantom's site, which is then rebuilt"},"phantom_posts_failed":{"type":"array","items":{"type":"object","properties":{"phantom_id":{"type":"string"},"error":{"type":"string"}},"required":["phantom_id","error"]},"description":"A phantom whose posts couldn't all be cleared, or whose site couldn't be rebuilt after"},"plan_runs_removed":{"type":"number"},"dismissals_removed":{"type":"number"}},"required":["prs_closed","prs_failed","automations_removed","planner_restored","builds_archived","phantom_posts_removed","phantom_posts_failed","plan_runs_removed","dismissals_removed"]},"readiness":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"},{"type":"null"}],"description":"The check run right after the reset"},"restore":{"anyOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["revert","force"]},"repo":{"type":"string"},"branch":{"type":"string"},"from_sha":{"type":"string","description":"The branch's head before"},"to_sha":{"type":"string","description":"And after; equal when it was already there"},"pr_number":{"description":"revert: the PR that was merged","type":["number","null"]},"branches_deleted":{"type":"array","items":{"type":"string"},"description":"Branches the org's repo changes left, deleted"},"branches_failed":{"type":"array","items":{"type":"string"}}},"required":["mode","repo","branch","from_sha","to_sha","pr_number","branches_deleted","branches_failed"]},{"type":"null"}],"description":"The repo put back to its baseline (restore_repo); null when not asked for or refused (notes say)"}},"required":["state","notes","undone","readiness","restore"]}},{"name":"run_demo_readiness_check","description":"Staff only. Is a demo org ready to show (docs/architecture/demo-control.md)? Reads it the way the demo will and records the result on the org (every reset runs it too): a scenario applied, competitors tracked, ranking targets to choose, answers that name the brand, a Plan row that brings back each parked or hidden thing, the injected issues open, no pull request left open, a GitHub token that mints, and the parked phantom's site answering. Each check says what it found. Refused unless the key was created by Letterstory staff; the same as the page's Check and `letterstory admin demo check`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The demo org."}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"readiness":{"type":"object","properties":{"ok":{"type":"boolean","description":"Every check passed: the org is ready to show"},"at":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"scenario, competitors, targets, answers, plan_rows, observations, open_prs, github, phantom_site (or read when the check couldn't run)"},"ok":{"type":"boolean"},"label":{"type":"string","description":"What is checked, in words"},"detail":{"type":"string","description":"What was found"}},"required":["key","ok","label","detail"]}}},"required":["ok","at","checks"],"description":"A readiness check: the org read the way the demo will"}},"required":["readiness"]}},{"name":"preview_demo_item","description":"On a demo org only (docs/architecture/demo-control.md): what a parked thing is, for its plan row's review before it is approved — the parked phantom's name, what it is about, where it lives (its domain), how many posts a week it writes and who sponsors it. The review on \"Launch a phantom\" reads this on a demo org where every other org reads plan_phantom's dry run; approving then brings this phantom back (reveal_demo_item). Reads only. Acts on the caller's own organization; any other org is refused. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"item":{"type":"string","enum":["phantom"],"description":"phantom: the parked phantom's name, what it is about, its domain, its cadence"}},"required":["item"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"item":{"type":"string","const":"phantom"},"proposed_name":{"type":["string","null"]},"description":{"type":["string","null"]},"domain_plan":{"type":"object","properties":{"site_domain":{"description":"Its domain, else its subdomain","type":["string","null"]}},"required":["site_domain"]},"defaults":{"type":"object","properties":{"cadence_per_week":{"type":["number","null"]}},"required":["cadence_per_week"]},"sponsored_by":{"type":"string","description":"The client, named on every page of the phantom (safe mode)"}},"required":["item","proposed_name","description","domain_plan","defaults","sponsored_by"]}},{"name":"reveal_demo_item","description":"On a demo org only (docs/architecture/demo-control.md): bring back the real thing its scenario parked or hid, which is what an approved plan row calls there instead of launching, onboarding or connecting a second one — the phantom (its real site and domain), the main blog, the Visitors site, the ranking targets (pass the chosen `phrase`: that parked target comes back with its answers; any other phrase is added as a new target), the first probes' answers, the CMS, the repo publish target, Search Console's property. propose_account_plan names this call on those rows; any other org is refused. Acts on the caller's own organization. Requires capability org_config:write.","capability":"org_config:write","inputSchema":{"type":"object","properties":{"item":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console"],"description":"What the approved row brings back: the parked or hidden real one."},"phrase":{"description":"ranking_targets: the phrase chosen. A parked target with this phrase comes back (with its answers); any other phrase is added as a new target, as add_ranking_target would.","type":"string","minLength":1,"maxLength":300}},"required":["item"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"item":{"type":"string","enum":["ranking_targets","phantom","main_blog","visitors","probes","cms","publish_target","search_console"]},"revealed":{"type":"boolean","description":"False when it was already shown"},"note":{"type":"string"}},"required":["item","revealed","note"]}},{"name":"get_onboarding_plan","description":"WHAT DOES THIS ORGANIZATION STILL NEED — the onboarding router. Reads plain facts about the org (domain set, positioning set, context applied, CMS connected, main blog onboarded, content areas, topic map, headlines, Planner on, phantom exists; for outbound, counts of mailers by state, prospect lists, sequences and active enrollments, never who) and evaluates every onboarding step against them: context, CMS (optional), main collection, Planner, Search Console (optional), ranking targets, first GEO probes (optional), phantom (optional), and the outbound chain (mailers → warmup → prospects and sequence → outbound, i.e. enroll and send; all optional). It does not cover Visitors (who visits) or Proofs (Letterprove); propose_account_plan does. Each step comes back done / running / ready / blocked, with blocked_by naming the missing facts and why naming the tool that provides them, the tool that performs the step (look its parameters up in tools/list), read-only tools to call first, the status tool to poll, the CLI command and the /docs guide. `next` is the first ready step Letter requires or recommends; optional steps are listed separately and need the customer's yes. Reads only; persists nothing. The main blog is the collection list_collections marks `is_main`; when the organization has several client collections and that is the wrong one, call set_main_collection first. Call it before starting any onboarding and again after each step finishes. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"description":"The plain facts the plan was computed from — booleans plus main_collection_id, running_job_id, cms_providers"},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["context","cms","collection","planner","search_console","ranking_targets","geo_probes","phantom","mailers","warmup","prospects","sequence","outbound"],"description":"The onboarding job"},"title":{"type":"string"},"purpose":{"type":"string","description":"What the step does, in one line"},"disposition":{"type":"string","enum":["required","recommended","optional"],"description":"Letter's labelled opinion: optional steps need the customer's yes"},"goals":{"type":"array","items":{"type":"string","enum":["agent_conviction","geo","seo","inbound_conversion","outbound","other"]},"description":"The Plan's goals this step moves (seo, geo, agent_conviction, inbound_conversion, outbound): answer a question about one goal with that goal's steps only"},"href":{"description":"The app page where a person does this step; null where it goes in place","type":["string","null"]},"status":{"type":"string","enum":["done","running","ready","blocked"],"description":"done = the step's primary fact holds; running = its job is in flight; ready = every required fact holds; blocked = a required fact is missing"},"requires":{"type":"array","items":{"type":"string"},"description":"Facts that must hold before this step can run"},"provides":{"type":"array","items":{"type":"string"},"description":"Facts this step establishes; the first is the completion signal"},"blocked_by":{"type":"array","items":{"type":"string"},"description":"Required facts that do not hold (empty unless blocked)"},"missing":{"type":"array","items":{"type":"string"},"description":"Provided facts that still do not hold (empty when fully done)"},"why":{"type":"string","description":"One sentence naming the tool that provides what is missing"},"tool":{"type":"string","description":"The tool that performs the step — look its parameters up in tools/list"},"prepare":{"type":"array","items":{"type":"string"},"description":"Read-only tools worth calling first (proposals, previews, current state)"},"poll":{"description":"The status tool to poll when the step runs as a durable job","type":["string","null"]},"cli":{"type":"string","description":"The letterstory CLI command for this step"},"docs":{"description":"The /docs guide that walks this step; null where none is written yet","type":["string","null"]},"args_hint":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Arguments the facts already determine (e.g. collection_id of the main blog)"}},"required":["id","title","purpose","disposition","goals","href","status","requires","provides","blocked_by","missing","why","tool","prepare","poll","cli","docs","args_hint"]},"description":"Every onboarding step, in recommended order, with its status"},"next":{"anyOf":[{"type":"string","enum":["context","cms","collection","planner","search_console","ranking_targets","geo_probes","phantom","mailers","warmup","prospects","sequence","outbound"]},{"type":"null"}],"description":"The first ready step Letter requires or recommends; null when none is ready"},"optional_ready":{"type":"array","items":{"type":"string","enum":["context","cms","collection","planner","search_console","ranking_targets","geo_probes","phantom","mailers","warmup","prospects","sequence","outbound"]},"description":"Ready steps the customer has to opt into"}},"required":["facts","steps","next","optional_ready"]}},{"name":"set_onboarding_step","description":"Update the caller's organization onboarding progress. complete_step / skip_step move a single step between the completed and skipped lists; current_step records where the user is; step_state saves small per-step scratch data (shallow-merged per step id, so one step's entry never clobbers another's); status overrides the overall state. Every field is optional but provide at least one. Status auto-advances to in_progress on first activity. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"current_step":{"description":"The step the user is currently on","type":"string","enum":["goals","domain","description","stances","import","flow"]},"complete_step":{"description":"Mark this step complete (removes it from the skipped list if present)","type":"string","enum":["goals","domain","description","stances","import","flow"]},"skip_step":{"description":"Mark this step skipped (removes it from the completed list if present)","type":"string","enum":["goals","domain","description","stances","import","flow"]},"step_state":{"description":"Per-step saved state, keyed by step id; each entry is shallow-merged into stored state","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"status":{"description":"Override the overall onboarding status","type":"string","enum":["not_started","in_progress","completed","dismissed"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"onboarding":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"status":{"type":"string","enum":["not_started","in_progress","completed","dismissed"]},"current_step":{"description":"Step id the user is currently on, or null","type":["string","null"]},"completed_steps":{"type":"array","items":{"type":"string"},"description":"Step ids marked complete"},"skipped_steps":{"type":"array","items":{"type":"string"},"description":"Step ids marked skipped"},"step_state":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Per-step scratch data keyed by step id; each entry is an object owned by that step"},"started_at":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","status","current_step","completed_steps","skipped_steps","step_state","started_at","completed_at","created_at","updated_at"]},"all_wired_resolved":{"type":"boolean","description":"True when every step is completed or skipped"}},"required":["onboarding","all_wired_resolved"]}},{"name":"import_from_cms","description":"Pull a client's existing posts from their connected CMS (Webflow / Framer / Sanity / Contentful, or a GitHub content repo — the markdown files under its publish target's path) into a Letter collection, RECONCILING against what's already there rather than duplicating: an item that matches an existing article by slug or URL is adopted (its article id — and so its clusters, series and GEO history — is kept), a linked item's content is refreshed (CMS wins; local edits are flagged, never silently lost), an unmatched item is created (origin:'imported'), and a linked article whose CMS item vanished is flagged orphaned, never deleted or unpublished. Dual key: pass `collection_id` to pull into an already CMS-bound collection (or an unbound one together with `provider`, which wires it first), OR pass `provider` alone to resolve/auto-create the provider's field mapping and wire the org's main blog (its client-brand default collection; a 'Blog' collection is created when it has none — a phantom is never used). This is the pull counterpart to publish_to_connector. Returns the reconcile summary (adopted/updated/created/unchanged/orphaned/conflicts) plus the collection/provider/mapping used. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Target collection UUID (from list_collections). Must be CMS-bound unless provider is also given.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"provider":{"description":"CMS to pull from. Required when collection_id is omitted; wires an unbound collection when both are given. github reads the markdown files under the org's publish target's path (set_github_publish_target).","type":"string","enum":["webflow","framer","sanity","contentful","github"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"],"description":"The CMS the posts were pulled from."},"collection_id":{"type":"string","description":"The collection the posts were reconciled into (wired/created if needed)."},"mapping_id":{"type":"string","description":"The field mapping used to reverse-map CMS fields onto articles."},"summary":{"type":"object","properties":{"adopted":{"type":"number","description":"Existing articles taken over (matched by slug/URL) instead of duplicated."},"updated":{"type":"number","description":"Already-linked articles whose content changed (CMS wins)."},"created":{"type":"number","description":"New articles created (origin:'imported'); nothing matched."},"unchanged":{"type":"number","description":"Items skipped — neither side changed since the last sync."},"orphaned":{"type":"number","description":"Linked articles whose CMS item the provider no longer returned (flagged, never deleted)."},"localEditsOverwritten":{"type":"number","description":"Updated articles that had local edits since the last sync (flagged)."},"baselineMissing":{"type":"number","description":"Updated articles with no prior sync baseline whose body changed — overwrite flagged for review."},"conflicts":{"type":"array","items":{"type":"object","properties":{"externalId":{"type":"string"},"slug":{"type":["string","null"]},"candidateArticleIds":{"type":"array","items":{"type":"string"}}},"required":["externalId","slug","candidateArticleIds"]},"description":"Items matching 2+ existing articles — skipped, never guessed."},"total":{"type":"number","description":"Live items considered from the CMS."},"truncated":{"type":"boolean","description":"True when the provider listing hit its runaway guard."}},"required":["adopted","updated","created","unchanged","orphaned","localEditsOverwritten","baselineMissing","conflicts","total","truncated"],"description":"The reconcile summary."}},"required":["provider","collection_id","mapping_id","summary"]}},{"name":"ensure_cms_mapping","description":"Make sure a connected CMS has a field mapping: the link between its blog and a Letter collection that lets onboard_collection (cms_provider) and import_from_cms read posts from the CMS instead of the sitemap, and publishing go back to it. Detects the CMS's blog collection and creates the mapping from the live connection for webflow, sanity and contentful; framer and github are mapped by a person (set_github_publish_target for a repo). Then binds the org's main blog to that connection when it has no destination yet, so a post can be published at once; for github (a repo the Letter app is installed on, no token needed) it finds the folder of Markdown posts that matches the live blog, saves it as the publish target and binds the blog to it (a publish opens a pull request). Idempotent: an existing mapping or binding is left as it is. What the Plan's connect dialog starts right after a CMS or GitHub is connected. Requires capability connector:publish.","capability":"connector:publish","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"],"description":"The connected CMS to map (webflow, sanity, contentful are detected for you)."}},"required":["provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string"},"mapping_id":{"description":"The provider's default field mapping; null when none exists and none could be detected.","type":["string","null"]},"created":{"type":"boolean","description":"True when this call detected the CMS's blog and created the mapping."},"note":{"type":"string","description":"What to do when no mapping could be made (Framer and GitHub are mapped by a person)."},"publishing":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["connected","letter_only"]},"provider":{"type":["string","null"]},"destination":{"type":"string"},"note":{"type":"string"}},"required":["state","provider","destination","note"]},{"type":"null"}],"description":"Where the main blog publishes after this call: connected to the provider (a post goes live there, or opens a pull request for github), or letter_only with why not. Null when the org has no main blog yet."}},"required":["provider","mapping_id","created","note","publishing"]}},{"name":"connect_cms","description":"Connect (save credentials for) one of the caller's CMS platforms so the org can pull from / publish to it: webflow (api_token), framer (project_url + api_token), sanity (project_id + dataset + api_token), contentful (space_id + environment_id + api_token), github (api_token: a personal access token with Contents and Pull requests read/write on the content repo — the same org-wide token set_github_connection stores). The credentials are validated against the provider before they're stored (encrypted) — invalid ones are rejected, not saved. One connection per org per provider (re-connecting replaces it). After connecting, import_from_cms can pull the existing blog (auto-detecting the field mapping; for github, add a publish target with set_github_publish_target first — it is GitHub's mapping) and publish_to_connector can push. api_token is always required; the other fields depend on the provider. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"api_token":{"type":"string","minLength":1,"description":"The CMS API token / key. github: a personal access token with Contents (read/write) and Pull requests (read/write) on the content repo — one org-wide token, shared with GitHub seers."},"project_url":{"description":"Framer: the project URL.","type":"string","minLength":1,"maxLength":500},"auto_deploy":{"description":"Framer: auto-publish the site after each push (default true).","type":"boolean"},"project_id":{"description":"Sanity: project id.","type":"string","minLength":1,"maxLength":100},"dataset":{"description":"Sanity: dataset (default 'production').","type":"string","minLength":1,"maxLength":100},"project_name":{"description":"Sanity: display name.","type":"string","maxLength":200},"workspace_name":{"description":"Sanity: studio workspace (default 'default').","type":"string","minLength":1,"maxLength":100},"space_id":{"description":"Contentful: space id.","type":"string","minLength":1,"maxLength":100},"environment_id":{"description":"Contentful: environment (default 'master').","type":"string","minLength":1,"maxLength":100},"space_name":{"description":"Contentful: display name.","type":"string","maxLength":200},"default_locale":{"description":"Contentful: default locale (default 'en-US').","type":"string","minLength":1,"maxLength":20}},"required":["provider","api_token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"connected":{"type":"boolean","const":true},"label":{"description":"The connected target's name (site/project/space), when known.","type":["string","null"]},"created_at":{"type":["string","null"]},"fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","boolean","null"]},"description":"The connection's public columns (never the token): webflow site_id/site_name; framer project_id/project_url/project_name/auto_deploy; sanity project_id/dataset/project_name/workspace_name; contentful space_id/environment_id/space_name/default_locale."},"applied":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"value":{"type":["string","boolean"]},"source":{"type":"string","enum":["caller","default","provider"],"description":"Who decided the value: you passed it, we applied our default, or the provider reported it."}},"required":["field","value","source"]},"description":"Every optional value that was decided on this connect, and by whom."}},"required":["provider","connected","label","created_at","fields","applied"]}},{"name":"disconnect_cms","description":"Remove the caller's stored connection for a CMS provider (webflow / framer / sanity / contentful / github). Idempotent. github's token is org-wide, so disconnecting it also stops private-repo GitHub seers. Does not touch already-imported articles or a collection's binding — it only forgets the credentials. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"],"description":"The CMS to disconnect."}},"required":["provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"disconnected":{"type":"boolean","const":true}},"required":["provider","disconnected"]}},{"name":"list_cms_connections","description":"List the caller org's CMS connection status — one entry per provider (webflow / framer / sanity / contentful / github), each with connected=true/false and the connected target's label. Use this to see what's set up before import_from_cms or publish_to_connector. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connections":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"connected":{"type":"boolean","description":"Whether the org has a connection for this provider."},"label":{"description":"The connected target's name (site/project/space), when known.","type":["string","null"]},"created_at":{"type":["string","null"]},"fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","boolean","null"]},"description":"The connection's public columns (never the token): webflow site_id/site_name; framer project_id/project_url/project_name/auto_deploy; sanity project_id/dataset/project_name/workspace_name; contentful space_id/environment_id/space_name/default_locale."}},"required":["provider","connected","label","created_at","fields"]},"description":"One entry per provider (connected or not)."}},"required":["connections"]}},{"name":"list_cms_mappings","description":"List the caller org's field mappings for a CMS provider (default first). A mapping ties CMS fields to article fields; import_from_cms/publish use one. For github the mappings are its publish targets (repo + branch + post path, from set_github_publish_target). Pass its id to bind_collection_cms. Empty when none exists yet (import_from_cms auto-creates one on first pull for webflow/sanity/contentful). Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"],"description":"The CMS whose field mappings to list. For github these are its publish targets (repo + branch + path)."}},"required":["provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"mappings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"is_default":{"type":"boolean"}},"required":["id","name","is_default"]},"description":"The org's field mappings for this provider, default first."}},"required":["provider","mappings"]}},{"name":"bind_collection_cms","description":"Wire a collection to a CMS provider + field mapping, so import_from_cms/publish target it — and so the Planner publishes there at each slot when the collection's publishing.cms is on (set_collection_config). For github the mapping is a publish target and each Planner publish opens a PR. A phantom (independent) collection cannot be wired to the org's own CMS. Pass mapping_id from list_cms_mappings, or omit it to use the provider's default/only mapping. Errors if no mapping exists yet — run import_from_cms (which auto-detects one) or create one first. This is the explicit form of what import_from_cms does implicitly when you pass it both a collection_id and a provider. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection to bind (from list_collections)."},"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"],"description":"The CMS to bind it to (must be connected)."},"mapping_id":{"description":"Field mapping id (for github, a publish target id from list_cms_mappings); omitted = the provider's default/only mapping.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["collection_id","provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"provider":{"type":"string","enum":["webflow","framer","sanity","contentful","github"]},"mapping_id":{"type":"string","description":"The field mapping the collection is now bound to."}},"required":["collection_id","provider","mapping_id"]}},{"name":"import_sitemap","description":"Import existing posts into one of the caller's collections by crawling a sitemap. Scrapes each matching page's title and body and files them as draft articles; re-importing the same URL updates the existing draft instead of duplicating. When a scraped page's URL doesn't match any existing article but its title does — exactly, and unambiguously — that article is adopted (its source_url stamped, content refreshed) instead of creating a duplicate; this is how a draft made with ingest_article gets its live URL registered on the first crawl that finds it. See `matched_by_title` in the response. Narrow the crawl with url_pattern (a glob with * or a plain substring) or by listing specific sub-sitemaps from an index. Caps at 200 posts per run. This crawls arbitrary external pages and can take a while — long enough that some MCP clients time out on the call before it returns; the crawl and everything below keep running server-side regardless. Returns created/updated/skipped plus `failed` + `failure_reasons` — `skipped` means the page had nothing readable, `failed` means the write itself broke; a run where every write failed is returned as an error, not a success. For a CLIENT collection's first-ever import (its sitemap_url isn't set yet), this also queues the full onboarding job in the background (clusters, series, topic map, headlines) — see `onboarding_queued` in the response, then call get_collection_onboard_status with THIS collection_id (its job_id isn't known yet) to check progress, even if this call itself timed out on your end. Already-onboarded collections (sitemap_url set) get a plain re-crawl with no side effects; use onboard_collection directly if you want the full pipeline up front instead of this two-step path. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Target collection UUID (from list_collections): must belong to the caller's org"},"sitemap_url":{"type":"string","format":"uri","description":"URL of the sitemap or sitemap index to crawl (http/https)"},"sitemap_urls":{"description":"Specific sub-sitemap URLs to crawl (chosen from an index) instead of auto-following sitemap_url; max 50","maxItems":50,"type":"array","items":{"type":"string","format":"uri"}},"url_pattern":{"description":"Optional URL filter: a glob with * (e.g. https://site.com/blog/*) or a plain substring match","type":"string","maxLength":500},"posts_only":{"description":"Classify URLs and import only blog POSTS, dropping product/landing/docs/listing pages","type":"boolean"}},"required":["collection_id","sitemap_url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created":{"type":"number","description":"New draft articles written"},"updated":{"type":"number","description":"Existing articles updated in place instead of duplicated — matched by exact source URL, or (see matched_by_title) by an unambiguous exact title match when no URL match was found"},"skipped":{"type":"number","description":"Pages with nothing readable, not imported"},"failed":{"type":"number","description":"Pages meant to import but whose write failed"},"failure_reasons":{"type":"array","items":{"type":"string"},"description":"First distinct reasons the failures happened"},"total":{"type":"number","description":"Total pages considered"},"truncated":{"type":"boolean","description":"True when the crawl hit the 200-post cap"},"healed":{"type":"number","description":"Already-imported posts that were missing metadata.published_url (the Search Console join key) and got it stamped from their source_url this run — no scrape"},"matched_by_title":{"type":"number","description":"Of `updated`, how many had no source-URL match but an exact, unambiguous title match against an existing article in the collection (e.g. a draft created via ingest_article) — adopted and stamped with this URL instead of creating a duplicate. Two-or-more same-titled candidates is treated as ambiguous and falls through to create, not counted here."},"onboarding_queued":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},{"type":"null"}],"description":"Set when this was this collection's first import — a full onboarding job (clusters, series, topic map, headlines) is being queued in the background against what was just imported. Its job_id isn't known yet (the job is created after this response is built, so the queuing survives an MCP client timing out on this call) — call get_collection_onboard_status with this collection_id instead of a job_id to check progress. Null when the collection was already onboarded (this was a plain re-crawl)."}},"required":["created","updated","skipped","failed","failure_reasons","total","truncated","healed","matched_by_title","onboarding_queued"]}},{"name":"onboard_collection","description":"One-shot onboard a CLIENT collection — the caller's own blog, under its own brand (the phantom recipe minus provisioning). Starts a durable job that reads the site into the org's strategy, discovers + imports the existing blog either from its sitemap (auto-discovered unless sitemap_url pins one; a site with no blog onboards from positioning alone) OR — when cms_provider is set — straight from a connected CMS (Webflow/Framer/Sanity/Contentful, or a GitHub content repo via its publish target; adopt-matching so it never duplicates), derives content areas + series from what was actually written, builds the topic map, infers the org's own table/Venn/generative-diagram styles from its site and binds them on the collection (the same org_derived set provision_org_visuals mints) with the guaranteed passes on — internal links off the blog's own sitemap, external sources, and comparison tables (drawn only where a post's own content template commissions one — most posts, including every untemplated one, have none to fill); free-form diagrams turn on too, but only when a real generative-diagram canvas landed in this blog's own colorway, never a silent fall-back to the house look — discovers + binds SEO demand, enables company GEO probing and runs one initial probe cycle, then stocks the Headlines tab — so the first headline batch can draw on GEO signal (brand mentions, answer gaps) from day one, not SEO alone. The Planner is configured but left OFF (manual approval) by default — nothing publishes on its own — unless enable_planner is set, which turns it on at cadence_per_week/approval_mode once headlines are stocked (the same effect as a follow-up configure_collection call, in this one call instead). Or set start_writing to start what the blog writes the moment the onboarding is done: the starter automations apply_starter_automations would apply, with every draft waiting for review (its outcome is result.writing on get_collection_onboard_status; a failure there never fails the onboarding). Omit collection_id to create a fresh collection (named collection_name, default 'Blog'). `direction` becomes the planner's steering prompt, shaping the topic map and every headline. Returns a job_id immediately — poll get_collection_onboard_status until status is 'done' (or 'failed'). Idempotent: re-running skips already-imported posts and keeps existing strategy/config. Requires capability collection:onboard.","capability":"collection:onboard","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Existing collection to onboard; omitted = a new one is created","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"collection_name":{"description":"Name for the new collection when collection_id is omitted (default: Blog)","type":"string","minLength":1,"maxLength":120},"direction":{"description":"Free-text editorial direction; becomes the planner steering prompt","type":"string","maxLength":2000},"sitemap_url":{"description":"Pin the sitemap to import from; omitted = discovered from robots.txt","type":"string","format":"uri"},"cms_provider":{"description":"Source the existing blog from this connected CMS instead of a sitemap (must be connected + mapped first; for github, a publish target from set_github_publish_target is the mapping and its path is where posts are read from). Mutually exclusive with sitemap_url.","type":"string","enum":["webflow","framer","sanity","contentful","github"]},"enable_planner":{"description":"Opt-in: once headlines are stocked, turn the Planner on (equivalent to a follow-up configure_collection call) instead of leaving it configured-but-off. Omit or false to keep today's default — a client blog never starts publishing on its own without this.","type":"boolean"},"cadence_per_week":{"description":"Posts-per-week when enable_planner is true. Default 3. Ignored otherwise.","type":"integer","minimum":1,"maximum":7},"approval_mode":{"description":"Approval posture when enable_planner is true. Default manual (never silently autopilot on a live brand). Ignored otherwise.","type":"string","enum":["autopilot","review_window","manual"]},"start_writing":{"description":"Opt-in: once the onboarding is done, start what the blog writes in the same go — the starter automations apply_starter_automations would apply, chosen from the blog's own results, with writing on and every draft waiting for review (nothing publishes until a person approves). Runs as the key's user; a letterbase that already has automations keeps them. Never fails the onboarding: how it went is in get_collection_onboard_status (result.writing and the review notes). Omit or false to leave writing to a separate apply_starter_automations call. Not with enable_planner.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string","description":"Poll target for get_collection_onboard_status."},"collection_id":{"type":"string","description":"The collection being onboarded (freshly created when collection_id was omitted)."},"status":{"type":"string","const":"running"},"stage":{"type":"string","const":"org_strategy"},"message":{"type":"string","description":"What to do next (poll get_collection_onboard_status)."}},"required":["job_id","collection_id","status","stage","message"]}},{"name":"get_collection_onboard_status","description":"Poll a collection-onboarding job started by onboard_collection (or auto-queued by import_sitemap on a first import). Returns status (running|done|failed), the stage reached (org_strategy → discover → import → coverage → map → visuals → seo/series/geo, concurrently → planner_headlines → done — the middle three run as one stage since none of them read each other's output), the running review notes (what each stage inferred/persisted — worth surfacing to a human), and — once done — the persisted result counts (posts_imported, coverage_areas, pillars, series, spokes_open, spokes_seo_bound, headlines_suggested). The GEO probe runs BEFORE headlines are sourced, so the first headline batch can already include GEO-sourced suggestions, not SEO-only. Polling also nudges the job forward, EXCEPT while another tick already holds it — a poll that lands mid-stage is then a no-op, so status can read byte-identical (including updated_at) for several minutes at a time; this is most visible on the map stage on a large corpus, which writes one 'Building the topic map…' note when it starts and nothing else until it finishes. A frozen response is not necessarily stalled — treat only a genuinely unmoving updated_at across many minutes past that stage's own checkpoint note as a sign of a stuck job. Pass job_id when known, or collection_id to look up that collection's most recent job (e.g. one import_sitemap queued in the background after its own response already went out — its job_id was never returned to the caller). Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"job_id":{"description":"Onboarding job UUID returned by onboard_collection.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"collection_id":{"description":"Look up the most recent onboarding job for this collection instead — for when job_id was never learned (e.g. import_sitemap queued one in the background after its own response already went out). Ignored when job_id is given.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string"},"collection_id":{"type":"string","description":"The collection this job onboards."},"status":{"type":"string","description":"One of: running | done | failed."},"stage":{"type":"string","description":"Stage the job has reached. One of: org_strategy | discover | import | coverage | map | visuals | seo | series | geo | planner_headlines | done | failed. seo/series/geo run concurrently as one stage — the job may report any of the three names while that stage is in flight."},"review":{"type":"array","items":{"type":"string"},"description":"Human-readable notes of what each stage inferred/persisted, in order."},"result":{"anyOf":[{},{"type":"null"}],"description":"Persisted readback once done: posts_imported, coverage_areas, pillars, series, spokes_open, spokes_seo_bound, headlines_suggested; and, when started with start_writing, writing { writing: on | unchanged | not_started | kept | failed, applied, weekly_total, note }."},"has_blog":{"description":"Whether an importable blog/sitemap was found; null until discovery has run.","type":["boolean","null"]},"sitemap_url":{"description":"The sitemap the import ran from (pinned or discovered).","type":["string","null"]},"detected_pattern":{"description":"URL pattern the importer detected for the blog's posts.","type":["string","null"]},"finish_review":{"type":"boolean","description":"True when the job is a review finish (the app's Continue after the Guided review): it runs only map → planner_headlines (topic map rebuilt, headlines seeded, Planner on). False for a full onboarding."},"error":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["job_id","collection_id","status","stage","review","result","has_blog","sitemap_url","detected_pattern","finish_review","error","created_at","updated_at"]}},{"name":"onboard_domain","description":"ONE-SHOT: a bare company domain in, a fully-configured organization out. Starts a durable PARENT job on the API key's org (its registered domain is the source) that composes the existing engines in order: (1) the MAIN BLOG via onboard_collection — strategy/positioning/identity/competitors/kernel, import the existing blog from its sitemap (auto-discovered unless sitemap_url) or a connected CMS (cms_provider), derive content areas + series, topic map, SEO demand, company GEO probing + one initial probe, headline pool; (2) ORG VISUALS — table/venn/generative canvases inferred from the client's own site (provenance org_derived, never a phantom's), bound on the main blog; (3) ENHANCE — the main blog's Enhance flow (internal links off its own sitemap, external sources, visuals) configured for every post written from now on (imported posts are NOT re-enhanced); (4) a PHANTOM via onboard_phantom — provisioned, configured (strategy, visuals, enhance, kernel, planner, topic map bound to the org's content areas) and seeded with posts_to_seed posts that are actually written, covered, enhanced and published; (6) READINESS — waits (bounded, default 2h) for the seed posts to publish and the first GEO probe to land, then persists the readback. The main blog failing fails the job; a phantom failure is recorded as a warning. Everything is visible in Measure without Search Console or a CMS connected. Returns a job_id immediately — poll get_domain_onboard_status until status is done (or failed). Needs the org to have a registered domain; to create the org itself headlessly use the staff key exchange (POST /api/superadmin/orgs) or the CLI `admin create-org`. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"direction":{"description":"Free-text editorial direction. Threads to BOTH the main blog and the phantom as their planner steering prompt, so it shapes both topic maps and every headline.","type":"string","maxLength":2000},"sitemap_url":{"description":"Pin where the main blog imports from; omitted = discovered from robots.txt / common paths.","type":"string","format":"uri"},"cms_provider":{"description":"Import the main blog from this connected CMS instead of a sitemap (connect_cms first). Mutually exclusive with sitemap_url.","type":"string","enum":["webflow","framer","sanity","contentful","github"]},"collection_name":{"description":"Name for the main collection that is created (default: Blog).","type":"string","minLength":1,"maxLength":120},"phantom":{"type":"object","properties":{"enabled":{"description":"Stand up a phantom publication. Default true.","type":"boolean"},"posts_to_seed":{"description":"First posts to write + publish on the phantom. Default 3 (each lands in ~30 min).","type":"integer","minimum":0,"maximum":10},"name":{"description":"Explicit masthead; omitted = inferred.","type":"string","minLength":1,"maxLength":100},"site_domain":{"description":"Custom domain for the phantom (bare). Nothing is bought unless domain_mode is buy_now.","type":"string","minLength":1,"maxLength":253},"domain_mode":{"description":"subdomain (default) | buy_now (SPENDS money, capped 2/org $30) | connect_later.","type":"string","enum":["subdomain","buy_now","connect_later"]},"cadence_per_week":{"type":"integer","minimum":1,"maximum":7}}},"seers":{"description":"suggest (default): create Seers in SUGGEST mode when the org's line of business supports them (a headline lands in Curate; nothing drafts unattended). skip: none.","type":"string","enum":["suggest","skip"]},"visuals":{"description":"Infer org-level table/venn/generative canvases from the client's own site and bind them on the main blog. Default true.","type":"boolean"},"enhance":{"description":"Configure the main blog's Enhance flow (internal links off its own sitemap, external sources, visuals) so new posts are enhanced. Never re-runs enhance over imported posts. Default true.","type":"boolean"},"is_test":{"description":"Throwaway/test org + phantom, off the fleet board and GEO probing, and never billed. Staff only: a customer's key always onboards real sites. Default FALSE — a domain onboarding is how a real client arrives.","type":"boolean"},"readiness_minutes":{"description":"How long the final readiness stage waits for the phantom's seed posts to publish before finishing with a warning. Default 120.","type":"integer","minimum":0,"maximum":360},"client_email":{"description":"The CLIENT's email. Their AI-visibility (lettertrace) project is created in that lettertrace account — adopted or created — so its free trial funds the first sweep and their own provider key takes over after. Absent: the org's first non-staff member; with neither, the geo stage is skipped and the project stays on the ops account.","type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string","description":"Poll target for get_domain_onboard_status."},"org_id":{"type":"string"},"domain":{"type":"string","description":"The bare company domain the onboarding runs against."},"status":{"type":"string","const":"running"},"stage":{"type":"string","const":"collection"},"message":{"type":"string","description":"What to do next (poll get_domain_onboard_status)."}},"required":["job_id","org_id","domain","status","stage","message"]}},{"name":"get_domain_onboard_status","description":"Poll a domain-onboarding job started by onboard_domain (or the org's most recent one when job_id is omitted). Returns status (running|done|failed), the stage reached (collection → visuals → enhance → phantom → readiness → done), the running review notes (including the main blog's own stage notes, prefixed 'Main blog:'), the child job ids (collection_job_id → get_collection_onboard_status, phantom_job_id → get_phantom_launch_status for their detail), and — once done — the persisted readback: collection counts, visuals, enhance flow, phantom site_url + seeds_published/seeds_total, seers, GEO probe runs/responses, and warnings (e.g. seeds still writing when the readiness window closed). Polling also nudges the job forward. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"job_id":{"description":"Domain onboarding job UUID returned by onboard_domain. Omitted = the organization's most recent domain onboarding.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"job_id":{"type":"string"},"org_id":{"type":"string"},"status":{"type":"string","description":"One of: running | done | failed."},"stage":{"type":"string","description":"Stage reached. One of: collection | visuals | enhance | phantom | seers | readiness | done | failed. collection and phantom wait on their child jobs; readiness waits (bounded) for the phantom's seed posts to publish."},"domain":{"type":"string"},"direction":{"type":["string","null"]},"sitemap_url":{"type":["string","null"]},"cms_provider":{"type":["string","null"]},"options":{"description":"The resolved onboarding options (phantom, seers, visuals, enhance, is_test, readiness_minutes)."},"collection_id":{"description":"The main blog collection, once created.","type":["string","null"]},"collection_job_id":{"description":"The child collection_onboard_jobs id (poll get_collection_onboard_status for its detail).","type":["string","null"]},"phantom_job_id":{"description":"The child phantom_onboard_jobs id (poll get_phantom_onboard_status for its detail).","type":["string","null"]},"seer_ids":{"type":"array","items":{"type":"string"},"description":"Seers created or reused in suggest mode."},"review":{"type":"array","items":{"type":"string"},"description":"Human-readable notes of what each stage did, in order — including the main blog's own notes."},"result":{"anyOf":[{},{"type":"null"}],"description":"Persisted readback: collection counts, visuals, enhance flow, phantom (site_url, seeds_total/seeds_published), seers, geo (the lettertrace project + account it was created in, first-sweep runs and how many ran on the free trial), probes (runs/responses), warnings."},"error":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["job_id","org_id","status","stage","domain","direction","sitemap_url","cms_provider","options","collection_id","collection_job_id","phantom_job_id","seer_ids","review","result","error","created_at","updated_at"]}},{"name":"ensure_collection_enhance_flow","description":"Configure a CLIENT collection's Enhance flow the way every phantom gets one: internal links crawled from the blog's OWN sitemap (collections.sitemap_url, else public_base_url, else the org domain), external sources, and comparison tables (drawn only where a post's own content template commissions one — most posts have none to fill) — bound as the collection's Auto-Pen Enhance step (auto, to completion) with the internal-link refresh on. Free-form diagrams turn on too, but ONLY when this collection already has a real generative-diagram canvas bound (see diagrams_enabled in the response) — call provision_org_visuals first if you want them and this comes back false; they never silently fall back to a generic house look. Idempotent: a flow already linking the blog's own host is kept (action ok); one pointing elsewhere is left alone and replaced with a dedicated flow (rebuilt). CONFIGURES ONLY — never runs enhance over existing/imported posts; new posts are enhanced as they are written. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The client-brand collection to configure. Its own sitemap/base URL (or the org domain) becomes the internal-link source."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"action":{"type":"string","description":"ok (already linked its own host) | built | rebuilt | no_host."},"flow_id":{"type":["string","null"]},"sitemap_url":{"description":"The sitemap the add_links stage crawls.","type":["string","null"]},"diagrams_enabled":{"type":"boolean","description":"Whether free-form diagrams ended this call turned ON. Gated on the collection having a REAL generative-diagram canvas bound — call provision_org_visuals first if this comes back false and you want diagrams; it never falls back to a generic house look silently."}},"required":["collection_id","action","flow_id","sitemap_url","diagrams_enabled"]}},{"name":"provision_org_visuals","description":"Infer the organization's OWN visual style from its website (palette, type, logo) and provision org-level table / venn / generative canvases from it — provenance org_derived, a separate set from any phantom's derived canvases, so the two never clobber each other — then bind them on a collection's EMPTY table/venn/generative slots (the org's primary collection by default). Idempotent: an org that already has org_derived canvases reuses them. Never touches custom or phantom-derived canvases. Style inference failing returns skipped_reason rather than an error. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Bind the inferred canvases on this collection's empty table/venn/generative slots. Omitted = the org's primary (is_org_default) collection.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"domain":{"description":"Site to infer the brand style from (bare domain). Omitted = the organization's registered domain.","type":"string","minLength":1,"maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"domain":{"type":"string"},"created":{"type":"array","items":{"type":"string"},"description":"canvas_types ids minted with source=org_derived."},"reused":{"type":"array","items":{"type":"string"},"description":"Existing org_derived canvases reused."},"bound":{"type":"array","items":{"type":"string"},"description":"Which slots were bound on the collection: table | venn | generative."},"skipped_reason":{"type":["string","null"]}},"required":["collection_id","domain","created","reused","bound","skipped_reason"]}},{"name":"sync_collection_sitemap","description":"Manually re-check a collection's sitemap right now, instead of waiting for its cadence (collections.sitemap_sync_cadence_days, default 14 — see /api/cron/sync-client-sitemaps). For a client blog onboarded from a sitemap with NO CMS connected: skips pages whose <lastmod> hasn't moved since the last check (cheap), imports net-new posts, and refreshes pages whose <lastmod> did move. Requires the collection to have sitemap_url set (from onboard_collection) and no cms_provider — once a CMS is connected, use the CMS pull/reconcile path instead. Bounded per run; a large backlog catches up over successive calls or cadence cycles. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection to re-check (must have sitemap_url configured and no CMS connected)"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created":{"type":"number","description":"Net-new posts found in the sitemap since the last check"},"updated":{"type":"number","description":"Previously-imported posts whose <lastmod> moved, refreshed"},"unchanged":{"type":"number","description":"Matched posts whose <lastmod> hadn't moved — skipped, no fetch"},"healed":{"type":"number","description":"Already-imported posts that were missing metadata.published_url (the Search Console join key) and got it stamped from their source_url this run — no scrape, counted across every matched post including skipped ones"},"skipped":{"type":"number","description":"Pages with nothing readable"},"failed":{"type":"number","description":"Writes that were attempted but failed"},"failure_reasons":{"type":"array","items":{"type":"string"},"description":"First distinct reasons the failures happened"},"checked":{"type":"number","description":"Matched post URLs considered this run, before the per-run cap"},"truncated":{"type":"boolean","description":"True when the backlog of new/changed pages exceeded this run's cap"}},"required":["created","updated","unchanged","healed","skipped","failed","failure_reasons","checked","truncated"]}},{"name":"get_webhook","description":"Get the webhook destination configured for this API key. `article.published`, `article.finished` (a draft is complete and awaiting review: the same fact list_articles's phase='finished' filter reads), `arena_run.paused` and `arena_run.settled` notifications are POSTed here. Each delivery is a notification carrying ids, not the article itself: fetch it with get_published. Full headers, payload fields, and retry behavior: /docs/webhooks. Requires capability webhook:read.","capability":"webhook:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"webhook_url":{"description":"Configured webhook destination, or null if disabled","type":["string","null"]},"has_webhook_secret":{"type":"boolean","description":"Whether an HMAC signing secret is set"}},"required":["webhook_url","has_webhook_secret"]}},{"name":"set_webhook","description":"Configure the webhook destination for this API key. Pass webhook_url=null to disable notifications. Pass a webhook_secret to enable HMAC-SHA256 signatures on outgoing deliveries (header: x-letterstory-signature). Deliveries carry an x-letterstory-event header (article.published, article.finished, arena_run.paused or arena_run.settled; arena events go to every key in the org with a webhook) plus x-letterstory-delivery-id for idempotency. Each delivery carries ids only, not the article: fetch it with get_published. Sent once with a 10s timeout and NOT retried on failure; pair with a periodic list_published check as a safety net. Full payload fields per event: /docs/webhooks. Requires capability webhook:manage.","capability":"webhook:manage","inputSchema":{"type":"object","properties":{"webhook_url":{"anyOf":[{"type":"string","maxLength":2048,"format":"uri"},{"type":"null"}],"description":"HTTPS URL that will receive article.published and article.finished events. Use null to disable."},"webhook_secret":{"description":"Optional shared secret used to sign deliveries. Omit to leave unchanged; null to clear.","anyOf":[{"type":"string","minLength":16,"maxLength":256},{"type":"null"}]}},"required":["webhook_url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"webhook_url":{"description":"Configured webhook destination, or null if disabled","type":["string","null"]},"has_webhook_secret":{"type":"boolean","description":"Whether an HMAC signing secret is set"}},"required":["webhook_url","has_webhook_secret"]}},{"name":"get_site_performance","description":"Search Console totals for the caller's published Letter posts only (pages matched to its articles, not the whole site) over a rolling period (clicks, impressions, CTR, average position), with the previous equal-length period for comparison. matched_articles = 0 means no post is matched yet, not that the site gets no search traffic. For whole-site traffic, AI agents or who visited use get_site_metrics / visitors_get_people. Pass collection_id to scope to one collection. Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (${SETTINGS_INTEGRATIONS_URL}). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the totals","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"],"description":"Immediately preceding equal-length window"},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous_totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"],"description":"Totals over the previous period, for deltas"},"matched_articles":{"type":"number","description":"Count of matched published articles in scope"},"matched_pages":{"type":"number","description":"Count of matched Search Console pages in scope"},"last_synced_at":{"description":"When Search Console data was last synced","type":["string","null"]}},"required":["period","previous_period","totals","previous_totals","matched_articles","matched_pages","last_synced_at"]}},{"name":"get_gsc_connection","description":"Report whether Google Search Console is connected for the caller's org: connected (a Google account has granted access), site_selected (a Search Console property is picked), site_url, google_email, last_synced_at, connected_at, and connect_url. Connecting is a browser OAuth step a human must do — an agent cannot: hand them connect_url (Settings → Integrations → Google Search Console, https://app.letterstory.com/settings?tab=integrations) and ask them to sign in with the Google account that owns the site's Search Console property. connected=true with site_selected=false means the grant exists but no property has been picked yet — the human finishes by choosing one on the same page. Until it's connected with a site, get_site_performance, get_post_performance and the get_seo_* tools return nothing. Same read as GET /api/gsc/connection; tokens are never returned. Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","description":"True when a human has completed the Google OAuth step for this org"},"site_selected":{"type":"boolean","description":"True when a Search Console property is picked; false while connected means no property chosen yet"},"site_url":{"description":"The selected Search Console property (e.g. sc-domain:example.com), or null","type":["string","null"]},"google_email":{"description":"Google account that granted access, or null","type":["string","null"]},"last_synced_at":{"description":"When Search Console data was last synced, or null if never","type":["string","null"]},"connected_at":{"description":"When the connection was made, or null when not connected","type":["string","null"]},"connect_url":{"type":"string","description":"Settings → Integrations link where a human connects Search Console (browser OAuth; an agent cannot)"}},"required":["connected","site_selected","site_url","google_email","last_synced_at","connected_at","connect_url"]}},{"name":"get_post_performance","description":"Answer \"how many clicks/impressions did post X get?\" — Search Console totals for one published article over a rolling period (clicks, impressions, CTR, average position), with the previous equal-length period for comparison. Returns matched=false with zeroed totals when the article has no Search Console pages yet (unpublished, or not matched). Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_articles or list_top_posts)"},"period":{"default":"14d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"matched":{"type":"boolean","const":false},"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous_totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"last_synced_at":{"type":["string","null"]}},"required":["article_id","title","matched","period","previous_period","totals","previous_totals","last_synced_at"]},{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"collection_id":{"type":["string","null"]},"collection_name":{"type":["string","null"]},"matched":{"type":"boolean","const":true},"pages":{"type":"array","items":{"type":"string"},"description":"Matched Search Console page URLs for this article"},"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous_totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"last_synced_at":{"type":["string","null"]}},"required":["article_id","title","collection_id","collection_name","matched","pages","period","previous_period","totals","previous_totals","last_synced_at"]}],"type":"object"}},{"name":"list_top_posts","description":"List the caller's top published posts by Search Console performance over a rolling period — each with clicks, impressions, CTR, average position, and the previous equal-length period for comparison. Ranked by clicks (or impressions), highest first. Pass collection_id to scope to one collection. Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the ranking","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"default":10,"description":"Maximum number of posts to return","type":"integer","minimum":1,"maximum":100},"sort":{"default":"clicks","description":"Metric to rank by (current period, highest first)","type":"string","enum":["clicks","impressions"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"sort":{"type":"string","enum":["clicks","impressions"],"description":"Metric the items were ranked by"},"total_matched":{"type":"number","description":"Total matched articles before the limit was applied"},"items":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"collection_id":{"type":["string","null"]},"collection_name":{"type":["string","null"]},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"],"description":"Totals over the current period"},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"],"description":"Totals over the previous period"}},"required":["article_id","title","collection_id","collection_name","current","previous"]}}},"required":["period","previous_period","sort","total_matched","items"]}},{"name":"list_article_performance","description":"Every published article's Search Console totals for a rolling period, with previous-period aggregates for deltas — zeros included for articles with no traffic, so this is the full report rather than a ranked top-N (use list_top_posts for that). A row's `pages` are the Search Console URLs matched to it; when `matched` is false no Search Console page maps to the post at all — `expected_url` is where we believe it's live, so the totals are zero because Search Console has never reported that URL, not because the post is missing. Posts we hold no live URL for (platform-published to a phantom site, say) are not listed here at all; they belong to a different property. When an unmatched post has traffic under a look-alike URL, `unjoined_pages` names it — that means the post moved and `expected_url` is stale. Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the list","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"articles":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"collection_id":{"type":["string","null"]},"collection_name":{"type":["string","null"]},"matched":{"type":"boolean","description":"Whether any Search Console page maps to this article. False means the totals are zero because nothing is joined to it — not necessarily because it earned nothing"},"expected_url":{"description":"Where we believe an unmatched article is live (its published_url override, else the source_url it was imported from). Always set when matched is false; always null once the article is matched, since its real pages are in `pages`","type":["string","null"]},"stale_url_match":{"anyOf":[{"type":"object","properties":{"matched_via":{"type":"string"},"page":{"type":"string"}},"required":["matched_via","page"]},{"type":"null"}],"description":"Set when the article IS matched, but on slug evidence rather than an exact URL: the live URL we hold is not the page Search Console reports, so the post moved and expected_url/published_url is stale. The metrics are correct; the stored URL still needs fixing, because citation matching and site-page probes resolve the post through it too"},"unjoined_pages":{"type":"array","items":{"type":"object","properties":{"page":{"type":"string"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["page","current"]},"description":"Only when matched is false: Search Console pages EARNING traffic whose URL looks like this article's (same slug, different URL) — almost always the post moved and expected_url is stale. Deliberately not auto-joined; set metadata.published_url to the real URL and it binds on the next sync. Empty when nothing look-alike is earning, which means Search Console has simply never reported this post's URL"},"pages":{"type":"array","items":{"type":"string"},"description":"Matched Search Console page URLs for this article"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["article_id","title","collection_id","collection_name","matched","expected_url","stale_url_match","unjoined_pages","pages","current","previous"]}}},"required":["period","previous_period","articles"]}},{"name":"list_topics","description":"List a collection's content-planner topic map: the pillars (clusters) it's building topical authority on, and each pillar's spokes (candidate subtopics) with their status. ONLY status 'open' is available to write: 'queued' means the Planner has already claimed it for a scheduled slot, 'covered' means a post already exists for it (see article_id), and 'dismissed' means it was rejected — writing any of those duplicates work the site has already done. Use an open spoke's topic_id with set_topic to queue it as the next piece, or with run_collection (topic_id) to write it now. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"cluster_id":{"type":"string"},"name":{"type":"string"},"display_name":{"description":"Short site-facing nav label, or null","type":["string","null"]},"is_priority":{"type":"boolean"},"is_muted":{"type":"boolean","description":"Held out of the active set even if it would otherwise activate"},"covered_count":{"type":"number","description":"Spokes already covered by a published post"},"spokes":{"type":"array","items":{"type":"object","properties":{"topic_id":{"type":"string"},"subtopic":{"type":"string"},"angle":{"type":["string","null"]},"status":{"type":"string","enum":["open","queued","covered","dismissed"],"description":"Only 'open' is writable; others are already claimed/done/rejected"},"client_relevance":{"type":"string","enum":["high","medium","low"]},"source":{"type":"string","enum":["ai","user","geo","measure"],"description":"Where this spoke came from: generated, added by hand, GEO mining, or placed from your measures (the ground finder)"},"brief":{"description":"Short editorial brief for the article","type":["string","null"]},"seo_keyword":{"description":"Targeted keyword, or null for a pure topical-authority play","type":["string","null"]},"seo_msv":{"description":"Monthly search volume snapshot","type":["number","null"]},"seo_kd":{"description":"Keyword difficulty snapshot","type":["number","null"]},"article_id":{"description":"Set when a post already covers this spoke","type":["string","null"]},"mention_offering_id":{"description":"The offering this spoke's post promotes (planner's choice, or an override via set_topic_offering). Null = the collection's primary offering, or the org itself","type":["string","null"]}},"required":["topic_id","subtopic","angle","status","client_relevance","source","brief","seo_keyword","seo_msv","seo_kd","article_id"]}}},"required":["cluster_id","name","display_name","is_priority","is_muted","covered_count","spokes"]}}},"required":["items"]}},{"name":"set_topic","description":"Queue a topic as the collection's next piece to write: the earliest open slot on the schedule, or a new slot extending the calendar if none is open. Pass topic_id (an open spoke from list_topics) to queue from the topic map, or suggestion_id to queue an already-sourced candidate directly — provide exactly one. The Planner must already be set up and turned on for the collection. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"},"topic_id":{"description":"Topic-map spoke UUID (a planner_topics id, from list_topics) to queue as the collection's next piece. Provide this or suggestion_id, not both.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"suggestion_id":{"description":"Sourced candidate suggestion UUID to queue directly. Provide this or topic_id, not both.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"type":"string","description":"The scheduled planner run the topic was queued onto"},"scheduled_for":{"type":"string","description":"ISO timestamp the run is scheduled to publish"}},"required":["run_id","scheduled_for"]}},{"name":"get_seo_query_performance","description":"Top search queries across the caller's matched published pages, with previous-period aggregates for deltas and the article(s) each query drove traffic to. Pass article_id to drill down to a single article's queries. Ranked by current+previous clicks (then impressions). Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the rows","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"article_id":{"description":"Optional article UUID to drill down to just that article's queries. Overlaps with collection_id (both narrow the same scope).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"queries":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"articles":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"}},"required":["article_id","title"]},"description":"Articles this query drove traffic to"}},"required":["query","current","previous","articles"]}}},"required":["period","previous_period","queries"]}},{"name":"get_seo_cluster_performance","description":"Cluster-level Search Console rollups over the caller's matched published pages, with previous-period aggregates for deltas. Articles belonging to several clusters count toward each; only clusters containing at least one matched article are returned. Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the rows","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"clusters":{"type":"array","items":{"type":"object","properties":{"cluster_id":{"type":"string"},"name":{"type":"string"},"is_priority":{"type":"boolean"},"article_count":{"type":"number","description":"Matched articles belonging to this cluster"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["cluster_id","name","is_priority","article_count","current","previous"]}}},"required":["period","previous_period","clusters"]}},{"name":"get_seo_performance_trend","description":"Search Console totals for the caller's matched published pages over a rolling period plus a zero-filled daily series (clicks/impressions/CTR/position per day) for charting the trend. Superset of get_site_performance — call this instead when a day-by-day line is needed. Pass collection_id to scope to one collection. Requires Google Search Console to be connected first, under Settings → Integrations → Google Search Console (https://app.letterstory.com/settings?tab=integrations). Requires capability insight:read.","capability":"insight:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the totals","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous_totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"series":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","description":"Day this point covers (YYYY-MM-DD)"},"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["date","clicks","impressions","ctr","position"]},"description":"Zero-filled daily series (settling tail trimmed) for charting"},"matched_articles":{"type":"number"},"matched_pages":{"type":"number"},"last_synced_at":{"type":["string","null"]}},"required":["period","previous_period","totals","previous_totals","series","matched_articles","matched_pages","last_synced_at"]}},{"name":"list_geo_opportunities","description":"Opportunities imported from a connected third-party GEO provider (Profound or AirOps, via sync_geo_opportunities): prompts where a competitor is mentioned and the brand isn't, highest gap score first. Empty unless the org connected one of those providers. Not Letter's own probes: for where the brand is named or missing in AI answers use get_share_of_voice_detail / get_share_of_voice_question. Requires capability geo:read.","capability":"geo:read","inputSchema":{"type":"object","properties":{"provider":{"description":"Filter to opportunities synced from a single provider","type":"string","enum":["airops","profound"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string"},"external_account_id":{"type":["string","null"]},"external_id":{"type":"string"},"prompt_text":{"type":"string","description":"The AI-assistant prompt this opportunity was observed on"},"topic":{"type":["string","null"]},"brand_mentioned":{"description":"Whether the caller's brand was mentioned","type":["boolean","null"]},"share_of_voice":{"type":["number","null"]},"monthly_searches":{"type":["number","null"]},"competitor_top":{"description":"Top competitor mentioned instead of the brand","type":["string","null"]},"gap_score":{"description":"Opportunity gap score; items are sorted by this desc","type":["number","null"]},"first_seen_at":{"type":"string"},"last_synced_at":{"type":"string"}},"required":["id","provider","external_account_id","external_id","prompt_text","topic","brand_mentioned","share_of_voice","monthly_searches","competitor_top","gap_score","first_seen_at","last_synced_at"]}}},"required":["items"]}},{"name":"get_phantom_seo_overview","description":"Portfolio-wide Search Console rollup across the caller's phantom blogs: site-wide clicks/impressions/position over a rolling period (plus the previous window and a zero-filled daily series) and published-vs-receiving post counts. Pass blog_id to scope to a single site. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"blog_id":{"description":"Optional phantom-blog (deployment) UUID to scope the rollup to a single site","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"First day of the window (YYYY-MM-DD, inclusive)"},"end":{"type":"string","description":"Last day of the window (YYYY-MM-DD, inclusive)"}},"required":["start","end"]},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous_totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Click-through rate 0..1; null when impressions are 0","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when impressions are 0","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"series":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","description":"Day this point covers (YYYY-MM-DD)"},"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["date","clicks","impressions","ctr","position"]},"description":"Zero-filled daily series for charting the trend"},"sites":{"type":"object","properties":{"total":{"type":"number","description":"Phantom blogs in scope"},"tracked":{"type":"number","description":"Blogs bound to a Search Console property"}},"required":["total","tracked"]},"posts":{"type":"object","properties":{"published":{"type":"number"},"receiving":{"type":"number","description":"Published posts that have received Search Console impressions"}},"required":["published","receiving"]},"connected":{"type":"boolean","description":"Whether the internal Search Console connection exists"},"last_synced_at":{"type":["string","null"]}},"required":["period","previous_period","totals","previous_totals","series","sites","posts","connected","last_synced_at"]}},{"name":"list_phantom_seo_sites","description":"Per-site Search Console rollup for the caller's phantom blogs: post counts (published vs receiving impressions) and site-wide metrics for a rolling period plus the previous window. Unbound sites are included with empty metrics so the gap is visible. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"sites":{"type":"array","items":{"type":"object","properties":{"blog_id":{"type":"string"},"name":{"type":"string"},"org_id":{"type":"string"},"org_name":{"type":"string"},"status":{"type":"string"},"url":{"description":"Public URL (custom domain, else subdomain); null while provisioning","type":["string","null"]},"bound":{"type":"boolean","description":"Whether the blog is bound to a Search Console property"},"last_synced_at":{"type":["string","null"]},"posts_published":{"type":"number"},"posts_receiving":{"type":"number","description":"Published posts already receiving GSC impressions"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"],"description":"Metrics for the immediately preceding equal-length window"}},"required":["blog_id","name","org_id","org_name","status","url","bound","last_synced_at","posts_published","posts_receiving","current","previous"]}},"connected":{"type":"boolean","description":"Whether the internal Search Console account is connected at all"}},"required":["period","previous_period","sites","connected"]}},{"name":"get_phantom_seo_signals","description":"The 'what does it mean' layer over the caller's phantom-blog Search Console data: pillar (topic-cluster) rollups with topic-map spoke progress, aggregated top queries, and deterministic insights — opinionated claims with the numbers spelled out. Pass blog_id to scope to a single site. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"blog_id":{"description":"Optional phantom-blog (deployment) UUID to scope the signals to a single site","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"clusters":{"type":"array","items":{"type":"object","properties":{"clusterId":{"type":"string","description":"Cluster id, or `unassigned:<blogId>` for the per-site bucket"},"name":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"blogId":{"type":"string"},"blogName":{"type":"string"},"collectionId":{"type":["string","null"]},"isPriority":{"type":"boolean"},"isRetired":{"type":"boolean","description":"Retired by a topic-map reset; kept here for its history"},"isUnassigned":{"type":"boolean"},"articleIds":{"type":"array","items":{"type":"string"},"description":"Published posts attributed to this pillar"},"postsPublished":{"type":"number"},"postsReceiving":{"type":"number","description":"Of those, posts already receiving impressions"},"spokes":{"anyOf":[{"type":"object","properties":{"open":{"type":"number"},"queued":{"type":"number"},"covered":{"type":"number"}},"required":["open","queued","covered"]},{"type":"null"}],"description":"Topic-map spoke progress; null for the unassigned bucket"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["clusterId","name","orgId","orgName","blogId","blogName","collectionId","isPriority","isRetired","isUnassigned","articleIds","postsPublished","postsReceiving","spokes","current","previous"]},"description":"Pillar rows sorted by current impressions (unassigned buckets last)"},"queries":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"articles":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"}},"required":["articleId","title"]},"description":"Posts this query surfaced (usually one)"}},"required":["query","current","previous","articles"]},"description":"Ranked by current+previous clicks so pure decliners still surface"},"insights":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["cluster_momentum","cluster_fading","post_breakout","query_opportunity","first_impressions","indexing_lag"]},"tone":{"type":"string","enum":["win","opportunity","watch"]},"headline":{"type":"string","description":"The claim itself"},"why":{"type":"string","description":"The numbers behind the claim, always spelled out"},"action":{"description":"Suggested next step, when there is one","type":["string","null"]},"blogId":{"type":["string","null"]},"clusterId":{"type":["string","null"]},"articleId":{"type":["string","null"]}},"required":["id","kind","tone","headline","why","action","blogId","clusterId","articleId"]}}},"required":["period","previous_period","clusters","queries","insights"]}},{"name":"list_phantom_seo_articles","description":"Every published post on the caller's phantom sites with its per-period Search Console metrics and indexing state — including posts GSC hasn't served yet ('awaiting'). Sorted oldest published first. Pass blog_id to scope to a single site. Pass standouts_only for just the posts standing out in search (the Top pages list in Measure → SEO) — the candidates for add_headline_from_phantom. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"},"blog_id":{"description":"Optional phantom-blog (deployment) UUID to scope the article list to a single site","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"standouts_only":{"description":"Only the standout posts Measure → SEO lists under Top pages: at least 50 impressions AND 5x the median of the posts Google showed this period (or 200+ impressions when fewer than 5 posts are showing), strongest first, at most 5. Each row gains `multiple` (over that median) and the response gains `baseline`. Default false: every post","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"previous_period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"articles":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"blogId":{"type":"string"},"blogName":{"type":"string"},"publishedAt":{"type":["string","null"]},"firstImpressionAt":{"description":"First day GSC reported an impression (the indexing proxy), or null","type":["string","null"]},"state":{"type":"string","enum":["awaiting","receiving"],"description":"'awaiting' = never served by GSC; 'receiving' = served at least once"},"pages":{"type":"array","items":{"type":"string"},"description":"Matched GSC page URLs (empty until the post appears in GSC)"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"multiple":{"description":"standouts_only: impressions over `baseline`; null when there was no baseline (fewer than 5 posts showing)","type":["number","null"]}},"required":["articleId","title","orgId","orgName","blogId","blogName","publishedAt","firstImpressionAt","state","pages","current","previous"]},"description":"Every published post on the scoped sites, oldest published first; with standouts_only, just the standouts, strongest first"},"baseline":{"description":"standouts_only: median impressions of the posts Google showed this period; null below 5 such posts","type":["number","null"]}},"required":["period","previous_period","articles"]}},{"name":"get_phantom_seo_article","description":"Drill-down for one phantom post: its daily impressions/clicks series over the period and the top search queries driving it. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID (from list_phantom_seo_articles)"},"period":{"default":"30d","type":"string","enum":["14d","30d","90d"],"description":"Rolling window of finalized Search Console days (data ends ~2 days ago)"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"published_at":{"type":["string","null"]},"first_impression_at":{"type":["string","null"]},"pages":{"type":"array","items":{"type":"string"},"description":"Matched GSC page URLs for this post"},"period":{"type":"object","properties":{"start":{"type":"string","description":"Inclusive range start, YYYY-MM-DD (UTC)"},"end":{"type":"string","description":"Inclusive range end, YYYY-MM-DD (UTC)"}},"required":["start","end"]},"series":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when zero impressions","type":["number","null"]}},"required":["date","clicks","impressions","ctr","position"]},"description":"One point per day in the current period (trailing settling tail trimmed)"},"queries":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string"},"current":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previous":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"description":"Clicks / impressions (0..1); null when the period has zero impressions","type":["number","null"]},"position":{"description":"Impression-weighted average position; null when the period has zero impressions","type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["query","current","previous"]},"description":"Top queries, ranked by current+previous clicks then impressions"}},"required":["article_id","title","published_at","first_impression_at","pages","period","series","queries"]}},{"name":"get_phantom_geo_signals","description":"Description: GEO detail for the caller's phantom sites (Lettertrace AI-answer probes, not site traffic): per-question mention rates on phantom questions, which of our posts get cited as sources, citation-without-mention opportunities, mention portrayal, how our own posts mention the client, the per-site ramp phase. blog_id scopes only those. pages, rate_series, named_rate_series and active_targets are always the whole org, the client's own probes included. For how often the org is named or its share of voice, read get_measure_summary / get_share_of_voice_detail. | input.blog_id: Optional phantom site UUID. Scopes summary, prompts, cited posts, opportunities, outbound mentions and ramp; the org-wide series ignore it. | output.summary.mentionRate: Share of answers to PHANTOM questions naming the client (0..1); null when the scoped phantoms have no answers, even if the org is named elsewhere (rate_series). | output.rate_series: Org-wide headline-rate curve over ~90d, every probe lane including the client's own; ignores blog_id. Requires capability phantom:read.","capability":"phantom:read","inputSchema":{"type":"object","properties":{"blog_id":{"description":"Optional phantom-blog (deployment) UUID to scope the GEO rollup to a single site","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the lettertrace executor is configured — the panel's gate"},"ramp":{"type":"array","items":{"type":"object","properties":{"blogId":{"type":"string"},"blogName":{"type":"string"},"phase":{"type":"string","enum":["authority","ramping","steady"],"description":"Target-steering ramp phase, driven by the Strategy regime"}},"required":["blogId","blogName","phase"]},"description":"Per-site steering phase (live blogs with a collection only)"},"active_targets":{"type":"number","description":"Active ranking targets the steering serves"},"pages":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"articleId":{"type":["string","null"]},"title":{"type":["string","null"]},"active":{"type":"boolean","description":"Still in the current probe selection (rotated-out pages keep history)"},"points":{"type":"array","items":{"type":"object","properties":{"runAt":{"type":"string"},"engine":{"type":"string"},"responsesCiting":{"type":"number"},"totalResponses":{"type":"number"},"citedRate":{"type":"number"}},"required":["runAt","engine","responsesCiting","totalResponses","citedRate"]},"description":"Oldest-first, one point per run that probed this page"},"totalResponses":{"type":"number"},"responsesCiting":{"type":"number"},"lastCitedRate":{"description":"Latest run's cited rate","type":["number","null"]},"lastInterval":{"anyOf":[{"type":"object","properties":{"low":{"type":"number"},"high":{"type":"number"}},"required":["low","high"]},{"type":"null"}],"description":"Latest run's Wilson interval (page samples are tiny)"}},"required":["url","articleId","title","active","points","totalResponses","responsesCiting","lastCitedRate","lastInterval"]},"description":"Pages under direct probe — per-URL cited-hit series (single-org scope)"},"rate_series":{"type":"object","properties":{"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","description":"Cycle day, YYYY-MM-DD (both engines aggregated)"},"citedGroundShare":{"type":"object","properties":{"hits":{"type":"number"},"asks":{"type":"number"},"point":{"type":"number","description":"hits / asks (0 when asks is 0)"},"lo":{"type":"number","description":"Wilson interval lower bound"},"hi":{"type":"number","description":"Wilson interval upper bound"}},"required":["hits","asks","point","lo","hi"],"description":"Our slice of answers that cited anyone"},"ownedCitationRate":{"type":"object","properties":{"hits":{"type":"number"},"asks":{"type":"number"},"point":{"type":"number","description":"hits / asks (0 when asks is 0)"},"lo":{"type":"number","description":"Wilson interval lower bound"},"hi":{"type":"number","description":"Wilson interval upper bound"}},"required":["hits","asks","point","lo","hi"],"description":"Distinct answers citing our posts / answers probed"},"mentionRate":{"type":"object","properties":{"hits":{"type":"number"},"asks":{"type":"number"},"point":{"type":"number","description":"hits / asks (0 when asks is 0)"},"lo":{"type":"number","description":"Wilson interval lower bound"},"hi":{"type":"number","description":"Wilson interval upper bound"}},"required":["hits","asks","point","lo","hi"],"description":"Answers naming the client / answers probed"},"breakthroughRate":{"type":"object","properties":{"hits":{"type":"number"},"asks":{"type":"number"},"point":{"type":"number","description":"hits / asks (0 when asks is 0)"},"lo":{"type":"number","description":"Wilson interval lower bound"},"hi":{"type":"number","description":"Wilson interval upper bound"}},"required":["hits","asks","point","lo","hi"],"description":"Answers that both cited us and named us / answers probed"},"answers":{"type":"number","description":"Answers probed that day"}},"required":["at","citedGroundShare","ownedCitationRate","mentionRate","breakthroughRate","answers"]}},"milestones":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["first_ai_citation","first_brand_mention","first_breakthrough"]},"label":{"type":"string"}},"required":["at","kind","label"]},"description":"The first cycle each state first appeared, derived from the series"}},"required":["points","milestones"],"description":"Headline-rate trend curve over ~90d (single-org scope)"},"named_rate_series":{"type":"object","properties":{"entities":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["brand","competitor"]}},"required":["name","type"]},"description":"Charted entities: the client (brand) first, then tracked competitors, capped"},"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","description":"Cycle day, YYYY-MM-DD (every engine that day pooled)"},"rate":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Entity name -> answers naming it / answers to naming questions that day (0..1)"},"sov":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Entity name -> share of the namings on naming questions (zero-sum, 0..1)"}},"required":["at","rate","sov"]},"description":"Oldest first, the whole history recomputed on one basis"},"basis":{"type":"string","const":"naming_questions","description":"Rates count only naming questions (probe_kind 'mention', questions that already name the client excluded); citation/page answers never dilute them"},"targetsLiveAt":{"description":"When ranking targets went live (earliest completed run answering a target question); from then on the naming questions are the ranking-target questions. Null before that","type":["string","null"]}},"required":["entities","points","basis","targetsLiveAt"],"description":"How often the client and each tracked competitor is named, over time (org-wide)"},"summary":{"type":"object","properties":{"promptCount":{"type":"number"},"responses":{"type":"number"},"mentions":{"type":"number"},"mentionRate":{"description":"Share of answers naming the client (0..1); null before any responses","type":["number","null"]},"ownedCitations":{"type":"number","description":"Per-URL citation rows of our own posts"},"ownedCitedAnswers":{"type":"number","description":"Distinct answers that cited one of our posts"},"anyCitedAnswers":{"type":"number","description":"Distinct answers that cited anyone"},"breakthroughs28d":{"type":"number","description":"Distinct breakthrough answers over the trailing 28d"},"citedPosts":{"type":"number","description":"Distinct published posts cited as sources"},"citedQuestions":{"type":"number","description":"Distinct questions whose answers cited an owned post"},"lastRunAt":{"type":["string","null"]},"sentiment":{"type":"object","properties":{"positive":{"type":"number"},"neutral":{"type":"number"},"negative":{"type":"number"},"unknown":{"type":"number","description":"The sentiment pass failed (never guessed)"}},"required":["positive","neutral","negative","unknown"],"description":"Portrayal breakdown of mentions"}},"required":["promptCount","responses","mentions","mentionRate","ownedCitations","ownedCitedAnswers","anyCitedAnswers","breakthroughs28d","citedPosts","citedQuestions","lastRunAt","sentiment"]},"prompts":{"type":"array","items":{"type":"object","properties":{"promptId":{"type":"string"},"text":{"type":"string"},"probeKind":{"type":"string","enum":["mention","citation","page"],"description":"'mention' = do answers name the client; 'citation' = cite our posts; 'page' = cite one specific post"},"targetPhrase":{"description":"Set when this is a target probe","type":["string","null"]},"clusterId":{"type":["string","null"]},"clusterName":{"type":["string","null"]},"responses":{"type":"number"},"mentions":{"type":"number"},"mentionRate":{"type":["number","null"]},"ownedCitations":{"type":"number"},"lastMentionAt":{"type":["string","null"]},"lastContextSnippet":{"description":"Sentence around the most recent mention","type":["string","null"]}},"required":["promptId","text","probeKind","targetPhrase","clusterId","clusterName","responses","mentions","mentionRate","ownedCitations","lastMentionAt","lastContextSnippet"]}},"citedPosts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"citedAnswers":{"type":"number","description":"Distinct answers that cited this post"},"citations":{"type":"number","description":"Per-URL citation rows (replicates included)"},"engines":{"type":"array","items":{"type":"string"}},"questions":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"tier":{"type":"string","enum":["focused","shortlist","category"],"description":"The question's reach"},"answers":{"type":"number","description":"Distinct answers to this question that cited the post"}},"required":["text","tier","answers"]},"description":"Distinct questions whose answers cited this post, strongest first"},"lastCitedAt":{"type":["string","null"]},"citationsWithMention":{"type":"number","description":"Of those citations, ones in answers that also named the client"},"clientMentionInPost":{"type":"boolean","description":"Whether the cited post's own body currently names the client"}},"required":["articleId","title","citedAnswers","citations","engines","questions","lastCitedAt","citationsWithMention","clientMentionInPost"]}},"opportunities":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":["string","null"]},"title":{"type":["string","null"]},"url":{"type":"string"},"promptText":{"type":"string"},"engine":{"type":"string"},"citedAt":{"type":"string"}},"required":["articleId","title","url","promptText","engine","citedAt"]},"description":"Our post was cited but the client wasn't named — add a mention"},"ownedCitationsByCluster":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Owned citations per cluster id over the window (feeds pillar chips)"},"outbound_mentions":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"mentioned":{"type":"boolean"},"mentionCount":{"type":"number"},"contextSnippet":{"type":["string","null"]}},"required":["articleId","title","mentioned","mentionCount","contextSnippet"]},"description":"How each of our published posts mentions the client (outbound portrayal)"}},"required":["enabled","ramp","active_targets","pages","rate_series","named_rate_series","summary","prompts","citedPosts","opportunities","ownedCitationsByCluster","outbound_mentions"]}},{"name":"update_competitor","description":"Update a tracked competitor's display name, domain, and/or whether it's blocked from being named in generated content. If domain changes, repoints to a different (or new) canonical competitor domain. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"competitor_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Competitor domain UUID (from list_competitors)"},"name":{"description":"New display name for the competitor","type":"string","minLength":1,"maxLength":100},"domain":{"description":"Repoint the competitor at a different domain, e.g. example.com","type":"string","pattern":"^(https?:\\/\\/)?(www\\.)?[a-zA-Z0-9][a-zA-Z0-9\\-\\.]*\\.[a-zA-Z]{2,}(\\/.*)?$"},"block_from_mentions":{"description":"Exclude this competitor from being named in generated content","type":"boolean"}},"required":["competitor_domain_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]},{"type":"object","properties":{"competitor":{"type":"object","properties":{"org_id":{"type":"string"},"name":{"type":"string","description":"The org's display label for this competitor"},"reason":{"description":"Why this competitor is tracked, if recorded","type":["string","null"]},"block_from_mentions":{"type":"boolean","description":"When true, this competitor is never named in generated content"},"created_at":{"type":"string"},"competitor_domains":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"created_at":{"type":"string"},"last_scraped_at":{"description":"When the canonical domain was last scraped","type":["string","null"]}},"required":["id","domain","created_at","last_scraped_at"],"description":"The shared canonical competitor domain this tracking row points at"}},"required":["org_id","name","reason","block_from_mentions","created_at","competitor_domains"]}},"required":["competitor"]}],"type":"object"}},{"name":"remove_competitor","description":"Stop tracking a competitor for the caller's organization. Does not delete the shared canonical domain record. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"competitor_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Competitor domain UUID (from list_competitors)"}},"required":["competitor_domain_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}},{"name":"get_competitor_topics","description":"Already-scraped topics for a tracked competitor, with the caller's org keyword_priority merged in. Does not trigger a new scrape — use scrape_competitor for that. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"competitor_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Competitor domain UUID (from list_competitors)"}},"required":["competitor_domain_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"source_url":{"type":"string"},"scraped_at":{"type":"string"},"inferred_keyword":{"description":"Keyword inferred for this topic, if any","type":["string","null"]},"description":{"type":["string","null"]},"published_at":{"description":"Original publish date of the source page, if known","type":["string","null"]},"keyword_priority":{"description":"The caller org's priority for this topic's keyword, if set. One of: high | medium | low.","type":["string","null"]}},"required":["id","title","source_url","scraped_at","inferred_keyword","description","published_at","keyword_priority"]}},"last_scraped_at":{"description":"When the competitor was last scraped","type":["string","null"]}},"required":["items","last_scraped_at"]}},{"name":"scrape_competitor","description":"Trigger a sitemap scrape for a tracked competitor to refresh its topics. Skips (returning the existing topics) if scraped within the last 24h — pass force to bypass that. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"competitor_domain_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Competitor domain UUID (from list_competitors)"},"force":{"default":false,"description":"Bypass the 24h freshness check and re-scrape now","type":"boolean"}},"required":["competitor_domain_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"source_url":{"type":"string"},"scraped_at":{"type":"string"},"inferred_keyword":{"description":"Keyword inferred for this topic, if any","type":["string","null"]},"description":{"type":["string","null"]},"published_at":{"description":"Original publish date of the source page, if known","type":["string","null"]},"keyword_priority":{"description":"The caller org's priority for this topic's keyword, if set. One of: high | medium | low.","type":["string","null"]}},"required":["id","title","source_url","scraped_at","inferred_keyword","description","published_at","keyword_priority"]}},"skipped":{"type":"boolean","description":"True when a fresh scrape was skipped and existing topics returned"},"last_scraped_at":{"type":["string","null"]}},"required":["items","skipped","last_scraped_at"]}},{"name":"list_ranking_target_posts","description":"The posts behind each active ranking target, and how they are doing in AI answers: first the posts written FOR it (`writtenForTarget`, with the buyer `subquery` each was written to answer), then the other published posts on the pillars it is bound to. Each post with its letterbase (`collectionId`) and the distinct questions AI answers cited it for (`citedQueries`) and won — cited it and named you (`breakthroughQueries`) — over the trailing 28 days; `queued` is how many posts for it are planned and not yet published. Pass `target_id` (from list_ranking_targets) for one target. The same list a target's query shows in Measure → Queries. Requires capability ranking_target:read.","capability":"ranking_target:read","inputSchema":{"type":"object","properties":{"target_id":{"description":"One target (from list_ranking_targets); omit for every active target.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"windowDays":{"type":"number","const":28,"description":"Citations are counted over the trailing 28 days, as every target scorecard."},"targets":{"type":"array","items":{"type":"object","properties":{"targetId":{"type":"string"},"phrase":{"type":"string"},"posts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"slug":{"type":["string","null"]},"collectionId":{"description":"The letterbase it is in.","type":["string","null"]},"firstPublishedAt":{"type":"string"},"writtenForTarget":{"type":"boolean","description":"Written for this target; false = another published post on a pillar it is bound to."},"subqueryId":{"type":["string","null"]},"subquery":{"description":"The buyer phrasing it was written to answer.","type":["string","null"]},"citedQueries":{"type":"number","description":"Distinct questions whose answers cited it (28 days)."},"breakthroughQueries":{"type":"number","description":"Of them, questions where the answer also named you."}},"required":["articleId","title","slug","collectionId","firstPublishedAt","writtenForTarget","subqueryId","subquery","citedQueries","breakthroughQueries"]},"description":"Written for it first, then most queries won, then most cited, then newest."},"queued":{"type":"number","description":"Posts for it planned on its bound pillars and not yet published."}},"required":["targetId","phrase","posts","queued"]}}},"required":["windowDays","targets"]}},{"name":"list_ranking_targets","description":"The caller's ranking targets (all statuses, each with its offering_id, priority and its own stances), their cluster/collection binding summaries, and `scopes`: the company layer and each active offering with its active cap, whether that cap is set there or inherited, and how many targets are active in it now. A target belongs to the COMPANY (offering_id null) or to ONE OFFERING (list_offerings). Each scope — the company layer and every offering — holds its own ACTIVE targets up to its cap: a preset of 5, adjustable per scope with set_ranking_target_cap (an offering without its own cap uses the company's). Inactive targets are never capped. Only active targets steer content and run probes. A phrase is unique across the whole org, so two of the company's offerings never compete for the same answer. Pass offering_id to list one scope's targets (null = the company layer's own). Requires capability ranking_target:read.","capability":"ranking_target:read","inputSchema":{"type":"object","properties":{"offering_id":{"description":"Only this scope's targets: an offering id (list_offerings), or null for the company layer's own. Omit for every target in the org. `scopes` always covers every scope.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"phrase":{"type":"string","description":"The phrase the org wants to rank for"},"mention_framing":{"description":"Optional guidance on how the target should be framed","type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"Points of view held specifically on this target's ground — used only on posts bound to it, ahead of the general stances; [] = none"},"offering_id":{"description":"The offering this target presses (list_offerings); null = the company layer (company-wide)","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: \"user\" (typed by a person or agent) or \"inferred\" (a suggestion)"},"position":{"type":"number","description":"Sort order within the org's targets"},"status":{"type":"string","enum":["active","inactive","achieved","retired"],"description":"'active' steers content and runs probes (capped per scope); 'inactive' is held, switched off (never capped); 'achieved' / 'retired' are outcomes"},"suggested_status":{"description":"A pending auto-graduation suggestion ('achieved' | 'retired'), cleared when a person or agent sets status","type":["string","null"]},"suggested_at":{"type":["string","null"]},"decided_by":{"description":"Who made the last status call: 'user' or 'system'; null before any","type":["string","null"]},"priority":{"description":"How much this target's questions count in share of voice (high = 3 votes, the default for a ranking target; normal = 2; low = 1; any other general question counts 2 and a vendor list 1), and how often its ground is written across target slots, 3 : 2 : 1. null = the default (high).","anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}]},"priority_set_by":{"description":"The user who last set priority","type":["string","null"]},"priority_set_at":{"description":"When priority was last set","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","phrase","mention_framing","stances","offering_id","source","position","status","suggested_status","suggested_at","decided_by","created_by","created_at","updated_at"]}},"bindings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"target_id":{"type":"string"},"collection_id":{"type":"string"},"cluster_id":{"type":["string","null"]},"status":{"type":"string"},"rationale":{"type":["string","null"]},"auto_approve_at":{"description":"For a proposed pillar: when it approves itself if nobody acts (ISO); else null","type":["string","null"]}},"required":["id","target_id","collection_id","cluster_id","status","rationale","auto_approve_at"]},"description":"Cluster/collection binding summaries for the returned targets"},"scopes":{"type":"array","items":{"type":"object","properties":{"offeringId":{"description":"The offering, or null for the company layer","type":["string","null"]},"label":{"type":"string","description":"What the scope is called: the offering's name, or \"the company\""},"cap":{"type":"number","description":"How many targets may be ACTIVE in this scope at once"},"capSetHere":{"type":"boolean","description":"True when this scope set its own cap; false when it inherits (an offering → the company's cap; the company → the preset of 5)"},"active":{"type":"number","description":"Targets active in this scope right now"}},"required":["offeringId","label","cap","capSetHere","active"]},"description":"Every scope — the company layer first, then each active offering — with its active cap and usage"},"cap":{"description":"The company layer's active cap (legacy; read `scopes` for every scope's cap)","type":["number","null"]}},"required":["items","bindings","scopes","cap"]}},{"name":"add_ranking_target","description":"Add a ranking target for the caller's organization, company-wide or on one offering (offering_id). A target belongs to the COMPANY (offering_id null) or to ONE OFFERING (list_offerings). Each scope — the company layer and every offering — holds its own ACTIVE targets up to its cap: a preset of 5, adjustable per scope with set_ranking_target_cap (an offering without its own cap uses the company's). Inactive targets are never capped. Only active targets steer content and run probes. A phrase is unique across the whole org, so two of the company's offerings never compete for the same answer. Adding never fails on the cap: the target lands 'active' when its scope has room, else 'inactive' (held, switched off) — read the returned status, and switch it on with update_ranking_target once that scope has room. Bindings and probes materialize on the next GEO cycle, once the target is active. priority (high / normal / low, optional) sets how much its questions count in share of voice (high = 3 votes, normal = 2, low = 1; omitted = high, the default for a ranking target) and how often its ground is written: across target slots, a high target's ground gets 3 picks for every 2 a normal one's gets and every 1 a low one's gets (a pillar several targets bind weighs as its highest-priority one). stances (optional, up to 12) are the points of view the company holds specifically on this target's ground: they reach the writer ONLY on a post whose ground is bound to this target, where they lead the post's stances, ahead of the publication's, offering's and company-wide ones. Requires capability ranking_target:write.","capability":"ranking_target:write","inputSchema":{"type":"object","properties":{"phrase":{"type":"string","minLength":1,"maxLength":200,"description":"What you want to be THE answer for, e.g. 'best payroll software'"},"mention_framing":{"description":"Optional: how content should frame the subject for this target (e.g. 'a cheaper alternative to X')","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"offering_id":{"description":"The offering this target presses (list_offerings). null or omitted = company-wide. Each offering and the company layer hold their own active targets, up to their cap.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"priority":{"description":"How much this target's questions count in share of voice: high = 3 votes (the default for a ranking target), normal = 2, low = 1. Any other general question counts 2 and a vendor list 1. The same weights decide how often its ground is written: across target slots a high target's ground gets 3 picks for every 2 a normal one's gets and every 1 a low one's gets. null = the default (high).","anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}]},"stances":{"maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":400},"description":"Points of view the company holds specifically on this target's ground, one per entry (up to 12, 400 characters each), e.g. 'Payroll should run itself; a human should only approve'. Used ONLY when a post sits on ground bound to this target, where they lead the post's stances ahead of the publication's, offering's and company-wide ones. Replaces the whole list; [] clears it."}},"required":["phrase"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"target":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"phrase":{"type":"string","description":"The phrase the org wants to rank for"},"mention_framing":{"description":"Optional guidance on how the target should be framed","type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"Points of view held specifically on this target's ground — used only on posts bound to it, ahead of the general stances; [] = none"},"offering_id":{"description":"The offering this target presses (list_offerings); null = the company layer (company-wide)","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: \"user\" (typed by a person or agent) or \"inferred\" (a suggestion)"},"position":{"type":"number","description":"Sort order within the org's targets"},"status":{"type":"string","enum":["active","inactive","achieved","retired"],"description":"'active' steers content and runs probes (capped per scope); 'inactive' is held, switched off (never capped); 'achieved' / 'retired' are outcomes"},"suggested_status":{"description":"A pending auto-graduation suggestion ('achieved' | 'retired'), cleared when a person or agent sets status","type":["string","null"]},"suggested_at":{"type":["string","null"]},"decided_by":{"description":"Who made the last status call: 'user' or 'system'; null before any","type":["string","null"]},"priority":{"description":"How much this target's questions count in share of voice (high = 3 votes, the default for a ranking target; normal = 2; low = 1; any other general question counts 2 and a vendor list 1), and how often its ground is written across target slots, 3 : 2 : 1. null = the default (high).","anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}]},"priority_set_by":{"description":"The user who last set priority","type":["string","null"]},"priority_set_at":{"description":"When priority was last set","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","phrase","mention_framing","stances","offering_id","source","position","status","suggested_status","suggested_at","decided_by","created_by","created_at","updated_at"]}},"required":["target"]}},{"name":"update_ranking_target","description":"Edit a ranking target: phrase, mention framing, stances, status (activate / deactivate / achieved / retired), offering_id (move it to another offering, or null = company-wide), position, and priority (high / normal / low: how much its questions count in share of voice — high = 3 votes, the default for a ranking target; normal = 2; low = 1; any other general question counts 2 and a vendor list 1; null = the default — and the same 3 : 2 : 1 decides how often its ground is written across target slots, so to put one target ahead set the others to normal or low). stances REPLACES the target's list ([] clears it): points of view held specifically on this target's ground, used only on posts bound to it, where they lead the post's stances ahead of the general ones. A target belongs to the COMPANY (offering_id null) or to ONE OFFERING (list_offerings). Each scope — the company layer and every offering — holds its own ACTIVE targets up to its cap: a preset of 5, adjustable per scope with set_ranking_target_cap (an offering without its own cap uses the company's). Inactive targets are never capped. Only active targets steer content and run probes. A phrase is unique across the whole org, so two of the company's offerings never compete for the same answer. Two transitions are refused when the scope the target would be active in is at its cap: activating it, and moving an ACTIVE target into another scope — the error names the scope; deactivate one there first or raise its cap. A status you set always overrides a pending auto-graduation suggestion; probes follow the status (activating starts them again, anything else stops them). Requires capability ranking_target:write.","capability":"ranking_target:write","inputSchema":{"type":"object","properties":{"target_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Ranking target UUID (list_ranking_targets)"},"phrase":{"description":"Rename the target phrase","type":"string","minLength":1,"maxLength":200},"mention_framing":{"description":"Update the mention framing; null clears it","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"status":{"description":"'active' steers content and runs probes; 'inactive' holds it switched off. Activating is refused when the target's scope (its offering, or the company) is already at its active cap — deactivate one there first.","type":"string","enum":["active","inactive","achieved","retired"]},"offering_id":{"description":"Move the target to another offering (or null = company-wide). An ACTIVE target can only move into a scope with room under its cap.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"position":{"description":"Display order","type":"integer","minimum":0,"maximum":1000},"priority":{"description":"How much this target's questions count in share of voice: high = 3 votes (the default for a ranking target), normal = 2, low = 1. Any other general question counts 2 and a vendor list 1. The same weights decide how often its ground is written: across target slots a high target's ground gets 3 picks for every 2 a normal one's gets and every 1 a low one's gets. null = the default (high).","anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}]},"stances":{"maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":400},"description":"Points of view the company holds specifically on this target's ground, one per entry (up to 12, 400 characters each), e.g. 'Payroll should run itself; a human should only approve'. Used ONLY when a post sits on ground bound to this target, where they lead the post's stances ahead of the publication's, offering's and company-wide ones. Replaces the whole list; [] clears it."}},"required":["target_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"target":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"phrase":{"type":"string","description":"The phrase the org wants to rank for"},"mention_framing":{"description":"Optional guidance on how the target should be framed","type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"Points of view held specifically on this target's ground — used only on posts bound to it, ahead of the general stances; [] = none"},"offering_id":{"description":"The offering this target presses (list_offerings); null = the company layer (company-wide)","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: \"user\" (typed by a person or agent) or \"inferred\" (a suggestion)"},"position":{"type":"number","description":"Sort order within the org's targets"},"status":{"type":"string","enum":["active","inactive","achieved","retired"],"description":"'active' steers content and runs probes (capped per scope); 'inactive' is held, switched off (never capped); 'achieved' / 'retired' are outcomes"},"suggested_status":{"description":"A pending auto-graduation suggestion ('achieved' | 'retired'), cleared when a person or agent sets status","type":["string","null"]},"suggested_at":{"type":["string","null"]},"decided_by":{"description":"Who made the last status call: 'user' or 'system'; null before any","type":["string","null"]},"priority":{"description":"How much this target's questions count in share of voice (high = 3 votes, the default for a ranking target; normal = 2; low = 1; any other general question counts 2 and a vendor list 1), and how often its ground is written across target slots, 3 : 2 : 1. null = the default (high).","anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}]},"priority_set_by":{"description":"The user who last set priority","type":["string","null"]},"priority_set_at":{"description":"When priority was last set","type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","phrase","mention_framing","stances","offering_id","source","position","status","suggested_status","suggested_at","decided_by","created_by","created_at","updated_at"]}},"required":["target"]}},{"name":"set_ranking_target_cap","description":"Set how many ranking targets one scope may hold ACTIVE at once: offering_id = that offering, or null = the company layer (whose cap is also the default for every offering without its own). cap null puts the scope back on its default (an offering inherits the company's cap; the company returns to the preset of 5). A target belongs to the COMPANY (offering_id null) or to ONE OFFERING (list_offerings). Each scope — the company layer and every offering — holds its own ACTIVE targets up to its cap: a preset of 5, adjustable per scope with set_ranking_target_cap (an offering without its own cap uses the company's). Inactive targets are never capped. Only active targets steer content and run probes. A phrase is unique across the whole org, so two of the company's offerings never compete for the same answer. Lowering a cap below what is active today deactivates nothing — it only refuses further activations in that scope until it is back under. Returns every scope as it now stands. Requires capability ranking_target:write.","capability":"ranking_target:write","inputSchema":{"type":"object","properties":{"offering_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"The offering whose cap to set, or null for the company layer (whose cap is also every offering's default)"},"cap":{"anyOf":[{"type":"integer","minimum":0,"maximum":25},{"type":"null"}],"description":"How many targets may be ACTIVE at once in that scope. null on an offering = use the company's cap; null on the company = back to the preset (5)."}},"required":["offering_id","cap"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"scopes":{"type":"array","items":{"type":"object","properties":{"offeringId":{"description":"The offering, or null for the company layer","type":["string","null"]},"label":{"type":"string","description":"What the scope is called: the offering's name, or \"the company\""},"cap":{"type":"number","description":"How many targets may be ACTIVE in this scope at once"},"capSetHere":{"type":"boolean","description":"True when this scope set its own cap; false when it inherits (an offering → the company's cap; the company → the preset of 5)"},"active":{"type":"number","description":"Targets active in this scope right now"}},"required":["offeringId","label","cap","capSetHere","active"]},"description":"Every scope as it stands after the change"}},"required":["scopes"]}},{"name":"list_ranking_subqueries","description":"The phrasings under each ranking target (Measure → Ranking targets → The questions we ask; GET /api/ranking-targets/subqueries, `letterstory targets subqueries`): the other ways buyers ask the target's question. Each is asked of the AI engines in its own words and briefs posts. source = user (a person wrote or reworded it) or inferred (we proposed it; every proposal is judged on the target's intent first). Pass target_id for one target; include_retired to see the ones taken out, with retired_by (user | intent_check) and retired_reason. The target phrase itself and the generated vendor-list / buyer questions are probes, not phrasings — list_probes shows them. Requires capability ranking_target:read.","capability":"ranking_target:read","inputSchema":{"type":"object","properties":{"target_id":{"description":"One target's phrasings (list_ranking_targets); omitted = every target's","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"include_retired":{"description":"Also return retired phrasings, with who retired each and why (default false)","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"target_id":{"type":"string"},"text":{"type":"string"},"source":{"type":"string","description":"user = a person wrote or reworded it; inferred = we proposed it"},"status":{"type":"string","description":"active | retired"},"position":{"type":"number"},"retired_by":{"description":"Who retired it: user | intent_check; null while active","type":["string","null"]},"retired_reason":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","target_id","text","source","status","position","retired_by","retired_reason","created_at"]}}},"required":["items"]}},{"name":"add_ranking_subquery","description":"Add a phrasing to a ranking target — another way its buyers ask the question (the app's 'Another way your buyers ask this…'). It is asked of the AI engines from the next GEO cycle and briefs posts on every site bound to the target. A target holds up to 15 active phrasings. Yours are never judged or retired by us. Requires capability ranking_target:write.","capability":"ranking_target:write","inputSchema":{"type":"object","properties":{"target_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The ranking target this phrasing belongs to (list_ranking_targets)"},"text":{"type":"string","minLength":3,"maxLength":300,"description":"Another way buyers ask the target's question, in their own words"}},"required":["target_id","text"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"subquery":{"type":"object","properties":{"id":{"type":"string"},"target_id":{"type":"string"},"text":{"type":"string"},"source":{"type":"string","description":"user = a person wrote or reworded it; inferred = we proposed it"},"status":{"type":"string","description":"active | retired"},"position":{"type":"number"},"retired_by":{"description":"Who retired it: user | intent_check; null while active","type":["string","null"]},"retired_reason":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","target_id","text","source","status","position","retired_by","retired_reason","created_at"]}},"required":["subquery"]}},{"name":"update_ranking_subquery","description":"Reword, retire or restore one phrasing under a ranking target (the ✕ and edits in Measure → Ranking targets). Rewording stops asking the old words and asks the new ones from the next cycle. Retiring stops asking it — its history stays, it stays off when the target is switched off and on, and it is never proposed again; status active restores it. The generated vendor-list / buyer questions are probes: pause one with control_probe. Requires capability ranking_target:write.","capability":"ranking_target:write","inputSchema":{"type":"object","properties":{"subquery_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The phrasing to change (list_ranking_subqueries)"},"text":{"description":"Reword it: the old wording stops being asked and the new one is asked from the next cycle","type":"string","minLength":3,"maxLength":300},"status":{"description":"retired = stop asking it (its history stays, and it is never proposed again); active = ask it again","type":"string","enum":["active","retired"]}},"required":["subquery_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"subquery":{"type":"object","properties":{"id":{"type":"string"},"target_id":{"type":"string"},"text":{"type":"string"},"source":{"type":"string","description":"user = a person wrote or reworded it; inferred = we proposed it"},"status":{"type":"string","description":"active | retired"},"position":{"type":"number"},"retired_by":{"description":"Who retired it: user | intent_check; null while active","type":["string","null"]},"retired_reason":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","target_id","text","source","status","position","retired_by","retired_reason","created_at"]}},"required":["subquery"]}},{"name":"list_landings","description":"The caller's landing pages — each a URL plus context that collections with auto-mention enabled weave into articles where relevant. Requires capability landing:read.","capability":"landing:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"The external URL this landing page represents."},"context":{"type":"string","description":"Free text: what the page is and when it is contextually relevant to mention."},"last_mentioned_at":{"description":"When an article last linked to it, or null if never.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","url","context","last_mentioned_at","created_at","updated_at"]}}},"required":["items"]}},{"name":"get_landing","description":"Fetch a single landing page by id. Requires capability landing:read.","capability":"landing:read","inputSchema":{"type":"object","properties":{"landing_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Landing page UUID (from list_landings)"}},"required":["landing_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"landing":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"The external URL this landing page represents."},"context":{"type":"string","description":"Free text: what the page is and when it is contextually relevant to mention."},"last_mentioned_at":{"description":"When an article last linked to it, or null if never.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","url","context","last_mentioned_at","created_at","updated_at"]}},"required":["landing"]}},{"name":"create_landing","description":"Create a landing page (a URL plus context) for the caller's organization. Collections with auto-mention enabled link relevant landing pages from articles as they're written. Requires capability landing:write.","capability":"landing:write","inputSchema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"The landing page URL a mention will link to"},"context":{"description":"What the page is and when it's contextually relevant to mention (free text)","type":"string","maxLength":4000}},"required":["url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"landing":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"The external URL this landing page represents."},"context":{"type":"string","description":"Free text: what the page is and when it is contextually relevant to mention."},"last_mentioned_at":{"description":"When an article last linked to it, or null if never.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","url","context","last_mentioned_at","created_at","updated_at"]}},"required":["landing"]}},{"name":"update_landing","description":"Update a landing page's URL or context. Requires capability landing:write.","capability":"landing:write","inputSchema":{"type":"object","properties":{"landing_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Landing page UUID (from list_landings)"},"url":{"description":"Update the landing page URL","type":"string","minLength":1,"maxLength":2048},"context":{"description":"Update the context text","type":"string","maxLength":4000}},"required":["landing_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"landing":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"The external URL this landing page represents."},"context":{"type":"string","description":"Free text: what the page is and when it is contextually relevant to mention."},"last_mentioned_at":{"description":"When an article last linked to it, or null if never.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","url","context","last_mentioned_at","created_at","updated_at"]}},"required":["landing"]}},{"name":"delete_landing","description":"Delete a landing page. Requires capability landing:write.","capability":"landing:write","inputSchema":{"type":"object","properties":{"landing_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Landing page UUID (from list_landings)"}},"required":["landing_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true,"description":"Always true on a successful delete."}},"required":["success"]}},{"name":"guess_geo_competitors","description":"AI-suggested competitors (not persisted) inferred by web search over the org's domain/positioning. Returns configured: false (with an empty list) when no competitor-search provider is set up. Pass results to add_competitor to start tracking one. Requires capability geo:read.","capability":"geo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"competitors":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"reason":{"type":"string","description":"Why the judge classed this as a competitor"},"relationship":{"anyOf":[{"type":"string","enum":["direct_rival","adjacent","complementary","incidental"]},{"type":"null"}],"description":"How this company relates to the org; null when the judge left it unclassified"},"confidence":{"type":"string","enum":["high","medium","low"]}},"required":["name","domain","reason","relationship","confidence"]}},"configured":{"type":"boolean","description":"False when no competitor-search provider is configured"}},"required":["competitors","configured"]}},{"name":"list_clusters","description":"Topic clusters for the caller's organization, each with its assigned article ids/titles. Without collection_id: the org-level content taxonomy. With collection_id: that collection's clusters (its Planner topic-map pillars). Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional collection UUID (from list_collections) to scope to that collection's clusters. Omit for the org-level content taxonomy.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"clusters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"description":"Set for a collection-scoped cluster; null for org-level taxonomy.","type":["string","null"]},"name":{"type":"string"},"display_name":{"type":["string","null"]},"description":{"type":["string","null"]},"source":{"type":"string","description":"Provenance, e.g. \"user\" or an AI generator."},"status":{"type":"string","description":"\"proposed\" | \"active\" | \"rejected\" | \"completed\"."},"status_reason":{"type":["string","null"]},"group_kind":{"type":"string","description":"\"cluster\" | \"series\"."},"coverage":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Declared coverage items (series only)."},"series_purpose":{"type":["string","null"]},"template_key":{"type":["string","null"]},"template_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"parent_group_id":{"type":["string","null"]},"next_headline":{"anyOf":[{},{"type":"null"}],"description":"RETIRED: the drafted next headline moved to series_next_resolutions. Stale on any row written after 2026-09-04; kept so existing consumers do not break."},"is_priority":{"type":"boolean"},"is_uncategorized":{"type":"boolean","description":"True for the scope's Uncategorized holding-pen cluster."},"focus_muted":{"type":"boolean"},"retired_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"article_ids":{"type":"array","items":{"type":"string"}},"article_count":{"type":"number"},"articles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"lifecycle_phase":{"type":["string","null"]},"published_at":{"type":["string","null"]}},"required":["id","title","lifecycle_phase","published_at"]}}},"required":["id","org_id","collection_id","name","display_name","description","source","status","status_reason","group_kind","coverage","series_purpose","template_key","template_keys","parent_group_id","next_headline","is_priority","is_uncategorized","focus_muted","retired_at","created_by","created_at","updated_at","article_ids","article_count","articles"]}},"unassigned_count":{"type":"number","description":"Keyworded articles in scope not yet assigned to any cluster."}},"required":["clusters","unassigned_count"]}},{"name":"create_cluster","description":"Create a single topic cluster from scratch (manual, not AI-generated). Omit collection_id for an org-level cluster in the client content taxonomy. A collection-scoped pillar also gets a scoped keyword-discovery pass, so the Planner isn't picking against it blind before the nightly cron runs. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Cluster name"},"collection_id":{"description":"Optional collection UUID to scope this cluster to a collection's topic map. Omit for an org-level cluster.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cluster":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"description":"Set for a collection-scoped cluster; null for org-level taxonomy.","type":["string","null"]},"name":{"type":"string"},"display_name":{"type":["string","null"]},"description":{"type":["string","null"]},"source":{"type":"string","description":"Provenance, e.g. \"user\" or an AI generator."},"status":{"type":"string","description":"\"proposed\" | \"active\" | \"rejected\" | \"completed\"."},"status_reason":{"type":["string","null"]},"group_kind":{"type":"string","description":"\"cluster\" | \"series\"."},"coverage":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Declared coverage items (series only)."},"series_purpose":{"type":["string","null"]},"template_key":{"type":["string","null"]},"template_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"parent_group_id":{"type":["string","null"]},"next_headline":{"anyOf":[{},{"type":"null"}],"description":"RETIRED: the drafted next headline moved to series_next_resolutions. Stale on any row written after 2026-09-04; kept so existing consumers do not break."},"is_priority":{"type":"boolean"},"is_uncategorized":{"type":"boolean","description":"True for the scope's Uncategorized holding-pen cluster."},"focus_muted":{"type":"boolean"},"retired_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"article_ids":{"type":"array","items":{"type":"string"}}},"required":["id","org_id","collection_id","name","display_name","description","source","status","status_reason","group_kind","coverage","series_purpose","template_key","template_keys","parent_group_id","next_headline","is_priority","is_uncategorized","focus_muted","retired_at","created_by","created_at","updated_at","article_ids"]}},"required":["cluster"]}},{"name":"update_cluster","description":"Rename a cluster, set its site-facing nav label, pin it in (is_priority) or out (focus_muted) of the focus set, or merge it into another cluster. Merging moves this cluster's articles into the target and removes this one — the way to retire a near-duplicate without losing its content. Priority and focus mute are opposites: setting one clears the other. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"cluster_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Cluster UUID (from list_clusters)"},"name":{"description":"Rename the cluster.","type":"string","minLength":1,"maxLength":200},"display_name":{"description":"The short site-facing nav label for a collection pillar. Pass null or an empty string to clear it and fall back to the internal name.","anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"is_priority":{"description":"Pin this cluster IN: its topics are boosted in scoring and it sorts first. Mutually exclusive with focus_muted — setting this clears that.","type":"boolean"},"focus_muted":{"description":"Pin this cluster OUT of the focus set without removing it. Mutually exclusive with is_priority — setting this clears that.","type":"boolean"},"merge_into_id":{"description":"Fold this cluster's articles into the target cluster, then delete this one. Both must be in the same scope (either both org-level or both in the same collection). Use this to retire a near-duplicate without losing its articles.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["cluster_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cluster":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"display_name":{"type":["string","null"]},"is_priority":{"type":"boolean"},"focus_muted":{"type":["boolean","null"]},"collection_id":{"type":["string","null"]}},"required":["id","name","display_name","is_priority","focus_muted","collection_id"]},{"type":"null"}],"description":"The updated cluster, or null when it was merged away."},"merged_into":{"description":"Present only for a merge: the cluster the articles moved into.","type":"string"}},"required":["cluster"]}},{"name":"delete_cluster","description":"Remove a cluster. A cluster carrying an active client ranking target is refused (conflict) unless acknowledge_target_loss: true, which orphans that target's binding. A collection pillar that has topics or articles behind it is RETIRED, not deleted — a strategy version is captured first, so it can be restored from Strategy → Versions, and its articles are refit into an existing cluster or a new one (falling back to Uncategorized). An empty cluster, or any org-level cluster, is removed outright. To retire a near-duplicate while keeping its articles together, use update_cluster with merge_into_id instead. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"cluster_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Cluster UUID (from list_clusters)"},"acknowledge_target_loss":{"description":"Required (true) if this cluster carries an active client ranking target — removing it orphans that target's binding. The call fails with a conflict naming the target if this is needed and omitted.","type":"boolean"}},"required":["cluster_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"retired":{"description":"The pillar had topics or articles, so it was retired (recoverable from Strategy → Versions) rather than deleted.","type":"boolean","const":true},"deleted":{"description":"The cluster was empty, so it was removed outright.","type":"boolean","const":true}}}},{"name":"assign_cluster_articles","description":"(Re)assign unassigned articles to clusters with AI — the org-level taxonomy by default, or one collection's clusters (its topic-map pillars) when collection_id is given; in a collection, articles with no keyword yet are placed by title. Idempotent. A coherent group of no-fit articles may become a new cluster unless allow_new_clusters is false; the rest are parked in the scope's Uncategorized holding pen. include_parked re-offers what is already parked — the same action as the topic map's \"Assign with AI\". Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional collection UUID to scope assignment to that collection's clusters (its topic-map pillars). Omit to assign across the org-level taxonomy.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"include_parked":{"description":"Also re-offer articles parked in the scope's Uncategorized holding pen; one that now fits leaves the pen. Default false.","type":"boolean"},"allow_new_clusters":{"description":"Whether a coherent group of no-fit articles (2+) may become a new cluster. Default true. Pass false to place only into existing clusters and park the rest in Uncategorized.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"assigned":{"type":"number","description":"Articles newly assigned to a real cluster."},"uncategorized":{"type":"number","description":"Articles newly parked in the Uncategorized holding pen."},"new_clusters":{"type":"number","description":"Clusters created from coherent groups of no-fit articles."}},"required":["assigned","uncategorized","new_clusters"]}},{"name":"propose_collection_directive","description":"Interpret one plain-language instruction about a collection (\"focus on the pricing area\", \"add an area for geo measurement and write about it\", \"start publishing twice a week with manual approval\") into a small set of concrete, individually acceptable levers. APPLIES NOTHING — it returns a proposal with a directive_id to pass to apply_collection_directive. Levers are a closed set — add a content area, focus one in, pin one out, draft a specific headline, turn the Planner on/off, set the weekly cadence, set the approval posture — and each is checked against the collection's real state, so a lever that would change nothing is never proposed. Anything the instruction asks for that no lever expresses comes back in `unsupported` rather than as an invented action. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection this instruction is about."},"text":{"type":"string","minLength":1,"maxLength":2000,"description":"What you want this collection to do, in plain language — \"focus on the pricing area\", \"write an article about the 2026 tariff change\". Stored verbatim."}},"required":["collection_id","text"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"proposal":{"type":"object","properties":{"directiveId":{"type":"string","description":"Pass this to apply_collection_directive."},"body":{"type":"string","description":"The instruction, verbatim."},"interpretation":{"type":"string","description":"Our one-line read-back, beginning \"You want…\"."},"scope":{"type":"object","properties":{"cluster_ids":{"type":"array","items":{"type":"string"}},"topics":{"type":"array","items":{"type":"string"}}},"required":["cluster_ids","topics"]},"effects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within this directive. What accept/reject refers to."},"kind":{"type":"string","enum":["create_cluster","pin_cluster","mute_cluster","queue_headline","set_planner_enabled","set_cadence","set_approval_mode"],"description":"The lever. Closed set — see lib/collections/directives/vocabulary.ts."},"label":{"type":"string","description":"The verb for this lever, e.g. \"Focus here: Pricing\". Never \"Approve\"."},"ongoing":{"type":"boolean","description":"Whether this keeps steering after it commits. A directive whose accepted effects are all false is satisfied at apply time and stops being live."},"rationale":{"type":"string","description":"Why this lever follows from the sentence. The \"Why this\" line."},"clusterId":{"type":["string","null"]},"clusterRef":{"description":"The id of a create_cluster effect in this same proposal. Resolved at apply time, which is what lets one directive create an area and focus on it — creates always run first.","type":["string","null"]},"clusterName":{"type":["string","null"]},"clusterDescription":{"description":"create_cluster only.","type":["string","null"]},"headline":{"description":"queue_headline only: the headline to draft.","type":["string","null"]},"enabled":{"description":"set_planner_enabled only.","type":["boolean","null"]},"postsPerWeek":{"description":"set_cadence only.","type":["number","null"]},"approvalMode":{"description":"set_approval_mode only.","anyOf":[{"type":"string","enum":["manual","review_window","autopilot"]},{"type":"null"}]}},"required":["id","kind","label","ongoing","rationale"]},"description":"The levers, each individually acceptable. NOTHING is applied yet."},"unsupported":{"description":"Anything asked for that no lever expresses, in plain English. Null when we expressed all of it.","type":["string","null"]},"expiresInDays":{"description":"Non-null only when the instruction is explicitly time-bound.","type":["number","null"]},"supersedes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"body":{"type":"string"},"why":{"type":"string"}},"required":["id","body","why"]},"description":"Live directives this one would retire if accepted. Shown before the decision, not after."}},"required":["directiveId","body","interpretation","scope","effects","unsupported","expiresInDays","supersedes"]}},"required":["proposal"]}},{"name":"apply_collection_directive","description":"Commit the accepted effects of a proposed directive through the real save paths. Effects you omit are declined; an empty accepted_effect_ids rejects the whole instruction and needs a rejection_reason. A directive whose accepted effects are all one-shot is marked satisfied and stops steering; one with an ongoing effect keeps steering until it is revoked or expires. Older directives that contradict or restate it are superseded, each with a reason recorded. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"directive_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"From propose_collection_directive."},"accepted_effect_ids":{"type":"array","items":{"type":"string"},"description":"The effect ids to commit. Anything omitted is declined. An empty array rejects the whole instruction and requires a rejection_reason."},"rejection_reason":{"description":"Why the declined effects were declined. Read back by the next proposal, so the same one isn't re-suggested.","type":"string","minLength":1}},"required":["directive_id","accepted_effect_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"applied":{"type":"object","properties":{"acceptedEffectIds":{"type":"array","items":{"type":"string"},"description":"Effects that actually committed."},"results":{"type":"array","items":{"type":"object","properties":{"effectId":{"type":"string"},"kind":{"type":"string"},"ok":{"type":"boolean"},"detail":{"type":"string"},"createdClusterId":{"description":"create_cluster only: the pillar it made.","type":"string"}},"required":["effectId","kind","ok","detail"]}},"supersededIds":{"type":"array","items":{"type":"string"},"description":"Older directives this apply retired."},"at":{"type":"string"},"by":{"description":"Null with `at` set means the system decided.","type":["string","null"]}},"required":["acceptedEffectIds","results","supersededIds","at","by"]}},"required":["applied"]}},{"name":"list_collection_directives","description":"What is steering this collection right now: every standing instruction still in force, with the levers it pulled and who decided. This is the answer to \"why is this collection writing what it's writing\" as a list of decisions rather than a paragraph of prose. Satisfied one-shots and expired instructions are excluded. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection to read."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"directives":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"body":{"type":"string"},"interpretation":{"type":["string","null"]},"scope":{"type":"object","properties":{"cluster_ids":{"type":"array","items":{"type":"string"}},"topics":{"type":"array","items":{"type":"string"}}},"required":["cluster_ids","topics"]},"effects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable within this directive. What accept/reject refers to."},"kind":{"type":"string","enum":["create_cluster","pin_cluster","mute_cluster","queue_headline","set_planner_enabled","set_cadence","set_approval_mode"],"description":"The lever. Closed set — see lib/collections/directives/vocabulary.ts."},"label":{"type":"string","description":"The verb for this lever, e.g. \"Focus here: Pricing\". Never \"Approve\"."},"ongoing":{"type":"boolean","description":"Whether this keeps steering after it commits. A directive whose accepted effects are all false is satisfied at apply time and stops being live."},"rationale":{"type":"string","description":"Why this lever follows from the sentence. The \"Why this\" line."},"clusterId":{"type":["string","null"]},"clusterRef":{"description":"The id of a create_cluster effect in this same proposal. Resolved at apply time, which is what lets one directive create an area and focus on it — creates always run first.","type":["string","null"]},"clusterName":{"type":["string","null"]},"clusterDescription":{"description":"create_cluster only.","type":["string","null"]},"headline":{"description":"queue_headline only: the headline to draft.","type":["string","null"]},"enabled":{"description":"set_planner_enabled only.","type":["boolean","null"]},"postsPerWeek":{"description":"set_cadence only.","type":["number","null"]},"approvalMode":{"description":"set_approval_mode only.","anyOf":[{"type":"string","enum":["manual","review_window","autopilot"]},{"type":"null"}]}},"required":["id","kind","label","ongoing","rationale"]},"description":"Only the accepted, still-ongoing effects."},"expiresAt":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"}},"required":["id","body","interpretation","scope","effects","expiresAt","decidedBy","decidedAt","createdAt"]},"description":"What is steering this collection right now, newest first."}},"required":["directives"]}},{"name":"revoke_collection_directive","description":"Stop a live instruction steering this collection. The reason is required and is read back by the next proposal, so the same instruction isn't suggested again next week. Levers it already pulled are NOT reversed — revoking stops it steering from now on, the same way a capability flip never reinterprets history. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"directive_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The live directive to turn off."},"reason":{"type":"string","minLength":1,"description":"Why it's being turned off. Required — the next proposal reads it, so a revoke without one lets the same instruction come back."}},"required":["directive_id","reason"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"revoked":{"type":"boolean"}},"required":["revoked"]}},{"name":"list_offerings","description":"List the organization's offerings: the products or services it would name when recommending them (e.g. \"Phantomstory\"). Each offering's fields are its sparse overrides of the org. Context is layered org → offering → collection. Each layer stores ONLY what it overrides: an omitted field inherits, and null resets a field to inheriting. name, aliases, url, manifesto, identity, default_framing and priority_topics resolve to the nearest layer that sets them (priority_topics REPLACES, so a product never inherits its siblings' focus); stances, avoid_topics and blocked_mentions are ADDITIVE across layers, and stances rank most targeted first (collection, then offering, then company-wide). An offering that sets nothing IS the org, prescribed as mentionable. No offerings = the org is the only subject (unchanged behaviour). Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"offerings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"description":"Null = inherits the org's name","type":["string","null"]},"aliases":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Null = inherits"},"url":{"type":["string","null"]},"manifesto":{"description":"Null = inherits the org's manifesto","type":["string","null"]},"identity":{"anyOf":[{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},{"type":"null"}],"description":"Null = inherits the org's identity"},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's stances (never replaces them)"},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"REPLACES the org's list; null = inherits"},"avoid_topics":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"blocked_mentions":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"brand_profile_slug":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this offering does NOT inherit from the org (only its own value, or none)"},"position":{"type":"number"},"source":{"type":"string"},"status":{"type":"string"},"decided_by":{"description":"Who made the last decision. Null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","slug","name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","brand_profile_slug","detached_fields","position","source","status","decided_by","decided_at","created_at","updated_at"]}}},"required":["offerings"]}},{"name":"get_offering","description":"Fetch one offering (its sparse overrides of the org — null fields inherit) plus its competitor overrides: which org competitors it may name (mention) or must never name (avoid), and competitors only it tracks (added_here). Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"offering_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["offering_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"offering":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"description":"Null = inherits the org's name","type":["string","null"]},"aliases":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Null = inherits"},"url":{"type":["string","null"]},"manifesto":{"description":"Null = inherits the org's manifesto","type":["string","null"]},"identity":{"anyOf":[{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},{"type":"null"}],"description":"Null = inherits the org's identity"},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's stances (never replaces them)"},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"REPLACES the org's list; null = inherits"},"avoid_topics":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"blocked_mentions":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"brand_profile_slug":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this offering does NOT inherit from the org (only its own value, or none)"},"position":{"type":"number"},"source":{"type":"string"},"status":{"type":"string"},"decided_by":{"description":"Who made the last decision. Null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","slug","name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","brand_profile_slug","detached_fields","position","source","status","decided_by","decided_at","created_at","updated_at"]},"competitors":{"type":"array","items":{"type":"object","properties":{"competitor_domain_id":{"type":"string"},"mention_stance":{"type":"string","description":"mention | avoid (inherit is the absence of a row)"},"added_here":{"type":"boolean","description":"True for a competitor only this offering tracks"},"name":{"type":["string","null"]}},"required":["competitor_domain_id","mention_stance","added_here","name"]},"description":"This offering's competitor overrides on top of the org's"}},"required":["offering","competitors"]}},{"name":"create_offering","description":"Create an offering: something the org would name when recommending it. Only slug is required; set only what differs from the org. Context is layered org → offering → collection. Each layer stores ONLY what it overrides: an omitted field inherits, and null resets a field to inheriting. name, aliases, url, manifesto, identity, default_framing and priority_topics resolve to the nearest layer that sets them (priority_topics REPLACES, so a product never inherits its siblings' focus); stances, avoid_topics and blocked_mentions are ADDITIVE across layers, and stances rank most targeted first (collection, then offering, then company-wide). To make a collection publish as / promote it, follow with set_collection_offerings. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"name":{"description":"What it is called in prose (e.g. 'Phantomstory'). Null = the org's name","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"aliases":{"description":"Other names it answers to. Null = inherit; [] = none","anyOf":[{"maxItems":10,"type":"array","items":{"type":"string","minLength":2,"maxLength":60}},{"type":"null"}]},"url":{"description":"Canonical page a mention links to","anyOf":[{"type":"string","maxLength":500,"format":"uri"},{"type":"null"}]},"manifesto":{"description":"What it is and why it exists, in prose. Null = the org's manifesto","anyOf":[{"type":"string","maxLength":8000},{"type":"null"}]},"identity":{"description":"The factual guardrail. Null = the org's","anyOf":[{"type":"object","properties":{"one_liner":{"description":"The one canonical sentence describing it","anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"is":{"description":"Facts a mention MAY assert","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":160}},"is_not":{"description":"Hard negatives a mention must NEVER assert","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":160}},"descriptors":{"description":"1–3 interchangeable short noun phrases the mention writer rotates","maxItems":3,"type":"array","items":{"type":"string","minLength":1,"maxLength":80}}}},{"type":"null"}]},"default_framing":{"description":"How it should be mentioned. Null = the org's default framing","anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"stances":{"description":"Points of view it takes — ADDED to the org's company-wide stances","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"priority_topics":{"description":"What to write about. REPLACES the org's list (siblings' focus never leaks in). Null = inherit","anyOf":[{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},{"type":"null"}]},"avoid_topics":{"description":"What to steer clear of — ADDED to the org's","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"blocked_mentions":{"description":"Names never to mention — ADDED to the org's","maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"brand_profile_slug":{"description":"Which org_brand_profiles visual identity it renders with. Null = the org's default","anyOf":[{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"},{"type":"null"}]},"position":{"description":"Display order","type":"integer","minimum":0,"maximum":1000},"detached_fields":{"description":"Fields this offering does NOT inherit from the org even when it sets nothing itself — e.g. ['stances'] = only its own stances, ['manifesto'] = no manifesto. Replaces the list; [] = inherit everything. avoid_topics / blocked_mentions can never be detached","maxItems":10,"type":"array","items":{"type":"string","enum":["aliases","url","manifesto","identity","default_framing","stances","priority_topics"]}},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$","description":"Stable handle, unique in the org (e.g. 'lettershred')"}},"required":["slug"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"offering":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"description":"Null = inherits the org's name","type":["string","null"]},"aliases":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Null = inherits"},"url":{"type":["string","null"]},"manifesto":{"description":"Null = inherits the org's manifesto","type":["string","null"]},"identity":{"anyOf":[{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},{"type":"null"}],"description":"Null = inherits the org's identity"},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's stances (never replaces them)"},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"REPLACES the org's list; null = inherits"},"avoid_topics":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"blocked_mentions":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"brand_profile_slug":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this offering does NOT inherit from the org (only its own value, or none)"},"position":{"type":"number"},"source":{"type":"string"},"status":{"type":"string"},"decided_by":{"description":"Who made the last decision. Null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","slug","name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","brand_profile_slug","detached_fields","position","source","status","decided_by","decided_at","created_at","updated_at"]}},"required":["offering"]}},{"name":"update_offering","description":"Patch an offering. Omitted fields are unchanged; null resets a field to inheriting the org. List fields (stances, avoid_topics, blocked_mentions, aliases, priority_topics) are replaced whole when sent, and identity is replaced whole (send every key you want kept). Context is layered org → offering → collection. Each layer stores ONLY what it overrides: an omitted field inherits, and null resets a field to inheriting. name, aliases, url, manifesto, identity, default_framing and priority_topics resolve to the nearest layer that sets them (priority_topics REPLACES, so a product never inherits its siblings' focus); stances, avoid_topics and blocked_mentions are ADDITIVE across layers, and stances rank most targeted first (collection, then offering, then company-wide). Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"name":{"description":"What it is called in prose (e.g. 'Phantomstory'). Null = the org's name","anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"aliases":{"description":"Other names it answers to. Null = inherit; [] = none","anyOf":[{"maxItems":10,"type":"array","items":{"type":"string","minLength":2,"maxLength":60}},{"type":"null"}]},"url":{"description":"Canonical page a mention links to","anyOf":[{"type":"string","maxLength":500,"format":"uri"},{"type":"null"}]},"manifesto":{"description":"What it is and why it exists, in prose. Null = the org's manifesto","anyOf":[{"type":"string","maxLength":8000},{"type":"null"}]},"identity":{"description":"The factual guardrail. Null = the org's","anyOf":[{"type":"object","properties":{"one_liner":{"description":"The one canonical sentence describing it","anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"is":{"description":"Facts a mention MAY assert","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":160}},"is_not":{"description":"Hard negatives a mention must NEVER assert","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":160}},"descriptors":{"description":"1–3 interchangeable short noun phrases the mention writer rotates","maxItems":3,"type":"array","items":{"type":"string","minLength":1,"maxLength":80}}}},{"type":"null"}]},"default_framing":{"description":"How it should be mentioned. Null = the org's default framing","anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"stances":{"description":"Points of view it takes — ADDED to the org's company-wide stances","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"priority_topics":{"description":"What to write about. REPLACES the org's list (siblings' focus never leaks in). Null = inherit","anyOf":[{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},{"type":"null"}]},"avoid_topics":{"description":"What to steer clear of — ADDED to the org's","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"blocked_mentions":{"description":"Names never to mention — ADDED to the org's","maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"brand_profile_slug":{"description":"Which org_brand_profiles visual identity it renders with. Null = the org's default","anyOf":[{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"},{"type":"null"}]},"position":{"description":"Display order","type":"integer","minimum":0,"maximum":1000},"detached_fields":{"description":"Fields this offering does NOT inherit from the org even when it sets nothing itself — e.g. ['stances'] = only its own stances, ['manifesto'] = no manifesto. Replaces the list; [] = inherit everything. avoid_topics / blocked_mentions can never be detached","maxItems":10,"type":"array","items":{"type":"string","enum":["aliases","url","manifesto","identity","default_framing","stances","priority_topics"]}},"offering_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"}},"required":["offering_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"offering":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"description":"Null = inherits the org's name","type":["string","null"]},"aliases":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Null = inherits"},"url":{"type":["string","null"]},"manifesto":{"description":"Null = inherits the org's manifesto","type":["string","null"]},"identity":{"anyOf":[{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},{"type":"null"}],"description":"Null = inherits the org's identity"},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's stances (never replaces them)"},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"REPLACES the org's list; null = inherits"},"avoid_topics":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"blocked_mentions":{"type":"array","items":{"type":"string"},"description":"ADDED to the org's"},"brand_profile_slug":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this offering does NOT inherit from the org (only its own value, or none)"},"position":{"type":"number"},"source":{"type":"string"},"status":{"type":"string"},"decided_by":{"description":"Who made the last decision. Null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","slug","name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","brand_profile_slug","detached_fields","position","source","status","decided_by","decided_at","created_at","updated_at"]}},"required":["offering"]}},{"name":"delete_offering","description":"Delete an offering. Collections that had it as primary fall back to the org as their subject; planner topics, ranking targets and landing pages that pointed at it go back to org-wide. Published posts are not touched. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"offering_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["offering_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}},{"name":"set_offering_competitor","description":"Set an offering's stance on one competitor, on top of the org's: mention (may be named in this offering's posts), avoid (never named), or inherit (clear the override). Pass competitor_domain_id for an org-tracked competitor (list_competitors), or add {domain, name} for a competitor only this offering has — that one never joins the org's list. Collections using the offering inherit these, and can override them again. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"offering_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"competitor_domain_id":{"description":"An org-tracked competitor (list_competitors)","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"add":{"description":"A competitor only this offering has","type":"object","properties":{"domain":{"type":"string","pattern":"^(https?:\\/\\/)?(www\\.)?[a-zA-Z0-9][a-zA-Z0-9\\-\\.]*\\.[a-zA-Z]{2,}(\\/.*)?$"},"name":{"type":"string","minLength":1,"maxLength":80}},"required":["domain","name"]},"mention_stance":{"type":"string","enum":["inherit","mention","avoid"],"description":"inherit = the org's stance; mention = may be named here; avoid = never named here"}},"required":["offering_id","mention_stance"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"competitor_domain_id":{"type":"string"},"mention_stance":{"type":"string"},"added_here":{"type":"boolean"}},"required":["ok","competitor_domain_id","mention_stance","added_here"]}},{"name":"get_collection_context","description":"Why is this collection writing what it's writing? Returns the context generation actually reads for it — every field resolved org → offering → collection, with origin naming the layer that decided each one — plus the collection's own layer (local, what set_collection_context edits) and its mention set (offerings; the primary is the offering it publishes as and inherits from). Context is layered org → offering → collection. Each layer stores ONLY what it overrides: an omitted field inherits, and null resets a field to inheriting. name, aliases, url, manifesto, identity, default_framing and priority_topics resolve to the nearest layer that sets them (priority_topics REPLACES, so a product never inherits its siblings' focus); stances, avoid_topics and blocked_mentions are ADDITIVE across layers, and stances rank most targeted first (collection, then offering, then company-wide). For the standing instructions steering it, see list_collection_directives. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"context":{"type":"object","properties":{"resolved":{"type":"object","properties":{"name":{"type":["string","null"]},"aliases":{"type":"array","items":{"type":"string"}},"url":{"type":["string","null"]},"manifesto":{"type":["string","null"]},"identity":{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]},"description":"Most targeted first: this collection's, then its offering's, then company-wide"},"priority_topics":{"type":"array","items":{"type":"string"}},"avoid_topics":{"type":"array","items":{"type":"string"}},"blocked_mentions":{"type":"array","items":{"type":"string"}},"item_origins":{"type":"object","properties":{"avoid_topics":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]}},"blocked_mentions":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]}}},"required":["avoid_topics","blocked_mentions"],"description":"Avoid topics and blocked mentions entry by entry, each with the layer that set it"},"offering":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","slug","name"]},{"type":"null"}],"description":"The offering the context resolved through (the primary). Null = the org itself is the subject"},"origin":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["org","offering","collection"]},"description":"Which layer decided each field: org, offering or collection"}},"required":["name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","item_origins","offering","origin"],"description":"What generation actually reads for this collection"},"local":{"type":"object","properties":{"stances":{"type":"array","items":{"type":"string"}},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"avoid_topics":{"type":"array","items":{"type":"string"}},"blocked_mentions":{"type":"array","items":{"type":"string"}},"default_framing":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this collection does NOT inherit from its offering and the company"}},"required":["stances","priority_topics","avoid_topics","blocked_mentions","default_framing","detached_fields"],"description":"What this collection sets itself (the editable layer; set_collection_context writes it)"},"offerings":{"type":"array","items":{"type":"object","properties":{"offering_id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]},"is_primary":{"type":"boolean","description":"The offering the collection publishes as / mostly promotes, and the parent its context inherits"},"weight":{"type":"number","description":"Bias for the planner's per-topic choice among non-primary members"}},"required":["offering_id","slug","name","is_primary","weight"]},"description":"The mention set. Empty = the org itself is the subject"}},"required":["resolved","local","offerings"]}},"required":["context"]}},{"name":"set_collection_context","description":"Edit a collection's own layer of context — editorial fields only (identity belongs to the offering). Omitted fields are unchanged; a sent list replaces this collection's list. stances, avoid_topics and blocked_mentions are ADDED to what the offering and org already say (this collection's stances lead in prompts; a collection can only be stricter, never remove an inherited avoid topic). priority_topics REPLACES the inherited focus (null = inherit again). default_framing null = inherit. Returns the re-resolved context, as get_collection_context does. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"stances":{"description":"This publication's stances — ranked ahead of the product's and company's","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"priority_topics":{"description":"Replaces the inherited focus. Null = inherit","anyOf":[{"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},{"type":"null"}]},"avoid_topics":{"description":"Added to the inherited avoid list (can only be stricter)","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"blocked_mentions":{"description":"Added to the inherited never-mention list","maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"default_framing":{"description":"Null = inherit","anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"detached_fields":{"description":"Fields this collection does NOT inherit from its offering and the company — ['stances'] = only this collection's stances. Replaces the list; [] = inherit everything","maxItems":5,"type":"array","items":{"type":"string","enum":["default_framing","stances","priority_topics"]}}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"context":{"type":"object","properties":{"resolved":{"type":"object","properties":{"name":{"type":["string","null"]},"aliases":{"type":"array","items":{"type":"string"}},"url":{"type":["string","null"]},"manifesto":{"type":["string","null"]},"identity":{"type":"object","properties":{"one_liner":{"type":["string","null"]},"is":{"type":"array","items":{"type":"string"},"description":"Facts a mention MAY assert"},"is_not":{"type":"array","items":{"type":"string"},"description":"Hard negatives a mention must NEVER assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Short noun phrases the mention writer rotates"}},"required":["one_liner","is","is_not","descriptors"]},"default_framing":{"type":["string","null"]},"stances":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]},"description":"Most targeted first: this collection's, then its offering's, then company-wide"},"priority_topics":{"type":"array","items":{"type":"string"}},"avoid_topics":{"type":"array","items":{"type":"string"}},"blocked_mentions":{"type":"array","items":{"type":"string"}},"item_origins":{"type":"object","properties":{"avoid_topics":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]}},"blocked_mentions":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"origin":{"type":"string","enum":["org","offering","collection"]}},"required":["text","origin"]}}},"required":["avoid_topics","blocked_mentions"],"description":"Avoid topics and blocked mentions entry by entry, each with the layer that set it"},"offering":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","slug","name"]},{"type":"null"}],"description":"The offering the context resolved through (the primary). Null = the org itself is the subject"},"origin":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","enum":["org","offering","collection"]},"description":"Which layer decided each field: org, offering or collection"}},"required":["name","aliases","url","manifesto","identity","default_framing","stances","priority_topics","avoid_topics","blocked_mentions","item_origins","offering","origin"],"description":"What generation actually reads for this collection"},"local":{"type":"object","properties":{"stances":{"type":"array","items":{"type":"string"}},"priority_topics":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"avoid_topics":{"type":"array","items":{"type":"string"}},"blocked_mentions":{"type":"array","items":{"type":"string"}},"default_framing":{"type":["string","null"]},"detached_fields":{"type":"array","items":{"type":"string"},"description":"Fields this collection does NOT inherit from its offering and the company"}},"required":["stances","priority_topics","avoid_topics","blocked_mentions","default_framing","detached_fields"],"description":"What this collection sets itself (the editable layer; set_collection_context writes it)"},"offerings":{"type":"array","items":{"type":"object","properties":{"offering_id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]},"is_primary":{"type":"boolean","description":"The offering the collection publishes as / mostly promotes, and the parent its context inherits"},"weight":{"type":"number","description":"Bias for the planner's per-topic choice among non-primary members"}},"required":["offering_id","slug","name","is_primary","weight"]},"description":"The mention set. Empty = the org itself is the subject"}},"required":["resolved","local","offerings"]}},"required":["context"]}},{"name":"set_collection_offerings","description":"Replace a collection's mention set: the offerings (list_offerings) it may promote. Exactly one must be is_primary — the offering the collection publishes as or mostly promotes, and the parent its context inherits from. A main blog has just its primary; a phantom may list others it can mention, and the planner picks one per topic, biased by weight (override a pick with set_topic_offering). Pass an empty list to make the org itself the subject again. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"offerings":{"maxItems":50,"type":"array","items":{"type":"object","properties":{"offering_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"is_primary":{"default":false,"type":"boolean"},"weight":{"default":1,"description":"Bias for the planner's per-topic choice among non-primary members","type":"number","minimum":0,"maximum":10}},"required":["offering_id"]}}},"required":["collection_id","offerings"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"offerings":{"type":"array","items":{"type":"object","properties":{"offering_id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]},"is_primary":{"type":"boolean","description":"The offering the collection publishes as / mostly promotes, and the parent its context inherits"},"weight":{"type":"number","description":"Bias for the planner's per-topic choice among non-primary members"}},"required":["offering_id","slug","name","is_primary","weight"]}}},"required":["offerings"]}},{"name":"set_topic_offering","description":"Override which offering a planner topic's post promotes (the planner picks one per topic from the collection's mention set at plan time). The offering must be in that collection's mention set (get_collection_context → offerings); null clears the override so the post falls back to the collection's primary. Applies to drafts written from the topic after the change. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"topic_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A planner topic (list_topics → spokes[].topic_id)"},"offering_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"An offering in the topic's collection's mention set (get_collection_context → offerings). Null clears the override: the post falls back to the collection's primary offering"}},"required":["topic_id","offering_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"topic_id":{"type":"string"},"collection_id":{"type":"string"},"mention_offering_id":{"description":"The offering a post written from this topic mentions. Null = the collection's primary","type":["string","null"]}},"required":["topic_id","collection_id","mention_offering_id"]}},{"name":"list_automations","description":"A letterbase's automations: what it writes, how often, and why (docs/architecture/automations.md). Each is one kind: topic (a pillar of the topic map, optionally aiming at a ranking target, or goal-led: the topics that press a target), series (a format repeated over items), signal (a seer: writes when something happens) or best (the most valuable idea anywhere: the rest of the week). weekly_total is the sum of the paces that are on. With none stored, `derived` lists what runs today; the first change stores them. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"cluster_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"seer_id":{"type":["string","null"]},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"position":{"type":"number"},"derived":{"type":"boolean"},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."},"written_recently":{"type":"number","description":"Posts it wrote (or had scheduled) in the last 4 weeks: the rolling count its pace is measured on."},"waiting":{"type":"number","description":"Ideas in the pool that are its own."},"brief":{"description":"What it should cover, in a person's words: a topic's or series' description, a seer's angle. Set with create/update_automation `brief`.","type":["string","null"]},"publish_at":{"anyOf":[{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone."}},"required":["weekday","time","timezone"]},{"type":"null"}],"description":"Pinned to a weekday and time: one post a week, in that slot and no other. Null: paced by pace_per_week. Set with create/update_automation `publish_at`."},"decision":{"type":"object","properties":{"source":{"type":"string","enum":["user","proposal"],"description":"Who last set its pace or added it: a person, or a strategy proposal."},"proposal_id":{"description":"The proposal that set it (list_strategy_history), when one did: decide_strategy_suggestion with decision 'undo' restores the paces before it.","type":["string","null"]},"decided_by":{"description":"The person who decided it; null when the review window applied a proposal on its own.","type":["string","null"]},"decided_at":{"type":["string","null"]}},"required":["source","proposal_id","decided_by","decided_at"],"description":"Who set it, for \"3/wk · from a proposal, Oct 22 · Undo\"."}},"required":["id","kind","name","cluster_id","target_id","seer_id","goal","pace_per_week","paused","position","derived","covers","written_recently","waiting","brief","publish_at","decision"]}},"weekly_total":{"type":"number","description":"The sum of the paces that are on: the letterbase's posts a week."},"max_per_week":{"type":"number"},"derived":{"type":"boolean","description":"True when none are stored yet: this is what runs today."}},"required":["automations","weekly_total","max_per_week","derived"]}},{"name":"get_ground_measures","description":"The ground a letterbase's automations write on, as writing reads it (docs/architecture/measure-feedback.md): each pillar and each question with its kind — strength (the client already wins: named, cited, posts beating their peers; double down), near_win (doing decently, not yet winning: the fastest results) or gap (rivals win, nothing of the client's there yet: slower) — and the numbers behind it: Named and share of voice (the one measure, docs/architecture/share-of-voice.md, on the pillar's questions), the leading rival, its posts' performance against their peers, and its Search position. The stored snapshot with its age (`computedAt`, `stale`); refreshed every few hours. Ranking ideas, measured subjects and strategy proposals all read this. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"computedAt":{"description":"When the stored snapshot was computed.","type":["string","null"]},"stale":{"type":"boolean","description":"Older than three refresh intervals (or not computed yet)."},"readiness":{"anyOf":[{"type":"object","properties":{"posts":{"type":"number"},"measureComparable":{"type":"boolean","description":"Share of voice / Named compare two windows here."},"measured":{"type":"boolean"},"readsCounted":{"type":"boolean"},"searchConnected":{"type":"boolean"}},"required":["posts","measureComparable","measured","readsCounted","searchConnected"]},{"type":"null"}]},"periodDays":{"type":["number","null"]},"pillars":{"type":"array","items":{"type":"object","properties":{"clusterId":{"type":"string"},"name":{"type":"string"},"named":{"anyOf":[{"type":"object","properties":{"level":{"type":"number","description":"0..1"},"change":{"description":"The previous period against today on the pairs asked in both; null until comparable.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"answers":{"type":"number"}},"required":["level","change","lo","hi","answers"]},{"type":"null"}],"description":"Named on the pillar's questions (every answer counts)."},"shareOfVoice":{"anyOf":[{"type":"object","properties":{"level":{"type":"number","description":"0..1"},"change":{"description":"The previous period against today on the pairs asked in both; null until comparable.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"answers":{"type":"number"}},"required":["level","change","lo","hi","answers"]},{"type":"null"}],"description":"Share of voice on the pillar's brand-naming answers."},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"name":{"type":["string","null"]},"share":{"type":"number"}},"required":["key","name","share"]},{"type":"null"}]},"performance":{"anyOf":[{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]},{"type":"null"}]},"position":{"description":"Its posts' average Search position, impressions-weighted.","type":["number","null"]},"questions":{"type":"number"},"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"]},{"type":"null"}],"description":"strength: the client already wins here (double down); near_win: ahead of every rival here, not yet winning outright (fastest results); building: behind a rival, not by much (real but partial traction); gap: rivals win by far, or the client isn't there at all (slower). Null: nothing readable yet."},"reason":{"description":"Why it is that kind, with its numbers.","type":["string","null"]}},"required":["clusterId","name","named","shareOfVoice","rival","performance","position","questions","kind","reason"]}},"questions":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"t:<target> / a:<post> / q:<probe> — open with get_share_of_voice_question."},"text":{"type":["string","null"]},"clusterId":{"type":["string","null"]},"targetId":{"type":["string","null"]},"answers":{"type":"number"},"namedYou":{"type":"number"},"named":{"type":"number","description":"Share of its answers naming the client."},"share":{"description":"Share of voice on its brand-naming answers.","type":["number","null"]},"change":{"type":["number","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"name":{"type":["string","null"]},"share":{"type":"number"}},"required":["key","name","share"]},{"type":"null"}]},"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"]},{"type":"null"}],"description":"strength: the client already wins here (double down); near_win: ahead of every rival here, not yet winning outright (fastest results); building: behind a rival, not by much (real but partial traction); gap: rivals win by far, or the client isn't there at all (slower). Null: nothing readable yet."}},"required":["key","text","clusterId","targetId","answers","namedYou","named","share","change","rival","kind"]}}},"required":["computedAt","stale","readiness","periodDays","pillars","questions"]}},{"name":"get_what_works","description":"What kind of content works in a letterbase, read against peers (docs/architecture/measure-feedback.md): each post's agent uses — live AI reads (an agent fetching the page to answer a person; our own probes out) plus answers citing it — and its impressions and people, each over its peers' median in the same window (a young post's first X days against its peers' first X days; a settled post's last 30 days against settled peers'; 1 is typical, 2 twice its peers), a settled post's traction against its own previous 30 days, and every group the same way: by automation, pillar, series format, question type and topic kind (`now` reads its newest posts and settled posts' last 30 days: what pressing reads). The stored snapshot with its age. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"computedAt":{"description":"When the stored snapshot was computed.","type":["string","null"]},"stale":{"type":"boolean","description":"Older than three refresh intervals (or not computed yet)."},"readiness":{"anyOf":[{"type":"object","properties":{"posts":{"type":"number"},"measureComparable":{"type":"boolean","description":"Share of voice / Named compare two windows here."},"measured":{"type":"boolean"},"readsCounted":{"type":"boolean"},"searchConnected":{"type":"boolean"}},"required":["posts","measureComparable","measured","readsCounted","searchConnected"]},{"type":"null"}]},"posts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":["string","null"]},"url":{"type":["string","null"]},"publishedAt":{"type":"string"},"ageDays":{"type":"number"},"clusterId":{"type":["string","null"]},"automationId":{"type":["string","null"]},"seriesId":{"type":["string","null"]},"topicKind":{"type":["string","null"]},"questionType":{"anyOf":[{"type":"string","enum":["target","general"]},{"type":"null"}]},"window":{"type":"object","properties":{"kind":{"type":"string","enum":["young","settled"]},"days":{"type":"number"},"start":{"type":"string"},"end":{"type":"string"}},"required":["kind","days","start","end"],"description":"young: its first X days (X its age) against its peers' first X days; settled: its last 30 days against settled peers'."},"counts":{"type":"object","properties":{"reads":{"type":"number","description":"Live AI reads: an agent fetching the page to answer a person; our own probes out."},"citations":{"type":"number","description":"Answers (our probes) that cited the page."},"agentUses":{"type":"number","description":"reads + citations: an agent used the page to answer."},"impressions":{"type":"number"},"clicks":{"type":"number"},"people":{"type":"number"}},"required":["reads","citations","agentUses","impressions","clicks","people"]},"performance":{"type":"object","properties":{"agentUses":{"type":["number","null"]},"impressions":{"type":["number","null"]},"people":{"type":["number","null"]}},"required":["agentUses","impressions","people"],"description":"Each count over its peers' median in the same window: 1 typical, 2 twice its peers."},"position":{"type":["number","null"]},"traction":{"type":"object","properties":{"agentUses":{"anyOf":[{"type":"string","enum":["gaining","steady","fading"]},{"type":"null"}]},"impressions":{"anyOf":[{"type":"string","enum":["gaining","steady","fading"]},{"type":"null"}]},"ratio":{"type":["number","null"]}},"required":["agentUses","impressions","ratio"],"description":"A settled post's last 30 days against its own previous 30."}},"required":["articleId","title","url","publishedAt","ageDays","clusterId","automationId","seriesId","topicKind","questionType","window","counts","performance","position","traction"]}},"groups":{"type":"object","properties":{"automation":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]}},"pillar":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]}},"format":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]}},"question_type":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]}},"topic_kind":{"type":"array","items":{"type":"object","properties":{"dimension":{"type":"string","enum":["automation","pillar","format","question_type","topic_kind"]},"key":{"type":"string","description":"The automation id, pillar id, series id, `target` / `general`, or topic kind."},"label":{"type":"string"},"posts":{"type":"number","description":"Posts with a readable window."},"newestPosts":{"type":"number","description":"Of them, published in the last 4 weeks."},"performance":{"description":"Median of its posts' agent uses against their peers (1 typical), shrunk toward 1 when small.","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"now":{"type":"object","properties":{"performance":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"posts":{"type":"number"}},"required":["performance","lo","hi","posts"],"description":"The same on what it does now: its newest posts and its settled posts' last 30 days. What pressing reads."},"impressions":{"description":"Median of its posts' impressions against their peers.","type":["number","null"]},"traction":{"description":"Its settled posts: share gaining minus share fading (−1..1).","type":["number","null"]}},"required":["dimension","key","label","posts","newestPosts","performance","lo","hi","now","impressions","traction"]}}},"required":["automation","pillar","format","question_type","topic_kind"]}},"required":["computedAt","stale","readiness","posts","groups"]}},{"name":"list_automation_ideas","description":"A letterbase's (collection's) future ideas, grouped by the automation that owns each (docs/architecture/automations.md): one group per automation (a subject, a format, an event, then Best) with the headlines it writes next in write-next order, each with a few words of why. Every idea belongs to exactly one automation (a series' installment, a seer's idea, a pillar's, a goal-led topic's, else Best's), so this is what is waiting and who will write it. The same read as the letterbase page's Future ideas by automation and get_automation's Next. For the topic map's spokes and their status, list_topics. With none stored, `derived` groups are what runs today. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"automation":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."}},"required":["id","kind","name","pace_per_week","paused","goal","covers"]},"ideas":{"type":"array","items":{"type":"object","properties":{"suggestion_id":{"type":"string"},"headline":{"type":"string"},"why":{"type":"string","description":"A few words of why, from what was stored when it was chosen."}},"required":["suggestion_id","headline","why"]},"description":"Its own ideas in write-next order: the order the schedule takes them."}},"required":["automation","ideas"]},"description":"One group per automation (a subject, a format, an event, then Best), each with the ideas it owns."},"total":{"type":"number","description":"Every idea waiting, across the groups."},"derived":{"type":"boolean","description":"True when no automation is stored yet: the groups are what runs today."}},"required":["groups","total","derived"]}},{"name":"suggest_automations","description":"What a letterbase could add, per kind of automation, ranked by the same evidence as what gets written. Topics: its pillars with no automation yet (with what each writes next, and its kind of ground, strength / near win / gap, with a reason in numbers; strengths and near wins first), new ground no automation covers (a gap only where it fits the letterbase) then its priority topics that aren't pillars yet (create_cluster, then create_automation), and ranking targets no automation aims at. Series: formats the writing pipeline delivers (explainers, approach comparisons, practical guides, our take, common mistakes, questions buyers ask), each with what one post is and an example in this company's words, less the ones it already runs, the ones that do best against their peers here first (create_automation with new_series and that purpose; no template, research shapes each post from its headline and the purpose). Writes nothing. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"topics":{"type":"object","properties":{"pillars":{"type":"array","items":{"type":"object","properties":{"cluster_id":{"type":"string"},"name":{"type":"string"},"next":{"type":"string","description":"What it writes next: its open subjects, or that it maps new ones."},"open":{"type":"number","description":"Open subjects it has mapped."},"goal":{"description":"The ranking target it aims at, when it has one.","type":["string","null"]},"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},{"type":"null"}],"description":"Its kind of ground from your measures; null: not read yet."},"reason":{"description":"Why it is that kind, with its numbers (\"Named on 3 of 11 answers about …\").","type":["string","null"]}},"required":["cluster_id","name","next","open","goal","kind","reason"]},"description":"Pillars of its topic map with no automation yet: strengths, near wins, gaps, then the ones not read yet; within each, the most to write first."},"new":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The subject, titled as a topic."},"why":{"type":"string","description":"The reason with its numbers, or that it is one of your priority topics."},"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},{"type":"null"}],"description":"Its kind of ground from your measures; null: a priority topic no evidence reaches yet."}},"required":["name","why","kind"]},"description":"Subjects it doesn't map yet: new ground no automation covers (strengths and near wins first, a gap only where it fits the letterbase), then the company's priority topics. create_cluster, then create_automation."},"goals":{"type":"array","items":{"type":"object","properties":{"target_id":{"type":"string"},"phrase":{"type":"string"}},"required":["target_id","phrase"]},"description":"Ranking targets no automation aims at: a goal-led topic (create_automation with target_id)."}},"required":["pillars","new","goals"]},"series":{"type":"object","properties":{"formats":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"purpose":{"type":"string","description":"What one post in it is."},"example":{"type":"string","description":"A headline in this company's words."},"every_days":{"type":"number"},"performance":{"description":"How that kind of post does against its peers here (agent uses; 1 typical); null: too few posts to read.","type":["number","null"]},"reason":{"description":"The same in a line: \"Comparisons are 1.6× their peers here (5 posts)\".","type":["string","null"]}},"required":["key","name","purpose","example","every_days","performance","reason"]},"description":"Formats it doesn't run yet, the ones doing best here first (then the ones not read yet, then the ones below their peers): create_series (name, purpose), then create_automation."},"existing":{"type":"array","items":{"type":"object","properties":{"cluster_id":{"type":"string"},"name":{"type":"string"},"purpose":{"type":["string","null"]}},"required":["cluster_id","name","purpose"]},"description":"Its series with no automation yet."}},"required":["formats","existing"]},"signals":{"type":"object","properties":{"seers":{"type":"array","items":{"type":"object","properties":{"seer_id":{"type":"string"},"name":{"type":"string"},"line":{"type":"string"}},"required":["seer_id","name","line"]},"description":"Seers writing into it with no automation yet."},"sources":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string"},"label":{"type":"string"},"tagline":{"type":"string"},"example":{"type":"string"},"needs_connection":{"type":["string","null"]},"fields":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"placeholder":{"type":["string","null"]},"type":{"type":"string"},"required":{"type":"boolean"}},"required":["key","label","placeholder","type","required"]}}},"required":["provider","label","tagline","example","needs_connection","fields"]},"description":"The sources a new signal is offered from (for now a spec change or news & trends), with the fields each needs."}},"required":["seers","sources"]}},"required":["topics","series","signals"]}},{"name":"suggest_starter_automations","description":"The first automations for a letterbase, chosen from evidence about the client (what its onboarding read: keywords it ranks for, 28 days of Search Console, its pages, where its rivals rank, where AI answers name it): two or three topics on its strengths (ranks in the top 3, named on most answers) and near wins (ranks 4-20, shown off page one, named some of the time), one gap topic (a rival ranks or is named, it isn't) only when relevant and strong, a series for a format the site already keeps, and Best for the rest, at 3 posts a week. Each item has its reason with the client's numbers and the first three headlines it will write. With no evidence yet, the set comes from positioning and rival gaps, and `note` says so. Writes nothing: accepting is running `steps` in order (create_automation / update_automation, pauses first, Best last), the person's choice; leave an item's step out to skip it. Reads the evidence now (about two minutes, bounded) when onboarding stored none. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"ground":{"description":"The evidence to choose from: answers (what AI assistants said to the probes; a GEO plan's), search (Search Console, rankings, the site's page families; an SEO plan's). Unset: everything.","type":"string","enum":["answers","search"]}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"basis":{"type":"string","enum":["evidence","positioning"],"description":"evidence: chosen from where the client ranks, is shown or is named; positioning: no evidence yet, chosen from what it told us and where rivals win."},"note":{"description":"Said to the person when the set is not from evidence (\"No evidence yet: …\"); null otherwise.","type":["string","null"]},"weekly_total":{"type":"number","description":"Posts a week across the set (the default is 3)."},"items":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Stable within the set."},"kind":{"type":"string","enum":["topic","series","best"]},"name":{"type":"string"},"ground":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: it already ranks in the top 3 or is named on most AI answers; near_win: ranks 4-20, shown off page one, or named more than every rival; building: named some, behind a rival, not by much; gap: a rival ranks or is named far more, or isn't there at all."},{"type":"null"}],"description":"The ground it presses; null when not from evidence."},"cluster_id":{"description":"The existing pillar (topic) or series (series) it points at.","type":["string","null"]},"new_topic":{"description":"A topic that isn't a pillar yet: made on accept.","type":["string","null"]},"new_series":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"purpose":{"type":"string"}},"required":["name","purpose"]},{"type":"null"}],"description":"A series that doesn't exist yet: made on accept."},"automation_id":{"description":"An automation that already exists (stored or derived:<n>): accepting re-paces it.","type":["string","null"]},"pace_per_week":{"type":"number"},"reason":{"type":"string","description":"Why, in a line, with the client's own numbers."},"headlines":{"type":"array","items":{"type":"string"},"description":"The first three it will write."}},"required":["key","kind","name","ground","cluster_id","new_topic","new_series","automation_id","pace_per_week","reason","headlines"]},"description":"Two or three topics on strengths and near wins, a gap topic only when relevant and strong, a series, and Best for the rest."},"pause":{"type":"array","items":{"type":"object","properties":{"automation_id":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"},"pace_per_week":{"type":["number","null"]}},"required":["automation_id","name","kind","pace_per_week"]},"description":"What runs today (derived, never chosen) and isn't in the set: paused on accept, so the week is the set's."},"steps":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string","enum":["create_automation","update_automation"]},"for":{"type":"string","description":"The item's key, or 'pause'."},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The tool's arguments, as they are."}},"required":["tool","for","args"]},"description":"Accepting, as calls: run them in order (pauses first, Best last). Drop an item's step to leave it out, or change its pace_per_week first."},"running":{"type":"array","items":{"type":"object","properties":{"automation_id":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["topic","series"]},"ground":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: it already ranks in the top 3 or is named on most AI answers; near_win: ranks 4-20, shown off page one, or named more than every rival; building: named some, behind a rival, not by much; gap: a rival ranks or is named far more, or isn't there at all."},{"type":"null"}]},"pace_per_week":{"type":"number"},"reason":{"type":"string","description":"Why, in a line, in the letterbase's own terms."},"headlines":{"type":"array","items":{"type":"string"},"description":"The next three it writes."}},"required":["automation_id","name","kind","ground","pace_per_week","reason","headlines"]},"description":"What the letterbase writes on now (its stored automations that are on), each with why: the main blog's say where the brand stands, an independent site's say what the field asks about. Empty with none stored."},"ground":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"kind":{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: it already ranks in the top 3 or is named on most AI answers; near_win: ranks 4-20, shown off page one, or named more than every rival; building: named some, behind a rival, not by much; gap: a rival ranks or is named far more, or isn't there at all."},"reason":{"type":"string"}},"required":["label","kind","reason"]},"description":"The ground the set was read from, strongest first."},"sources":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Per source (ranked, search_console, pages, rivals, answers): ok, empty, unavailable (no provider or connection) or timed_out."},"evidence_at":{"description":"When the evidence was read (at onboarding, or now).","type":["string","null"]}},"required":["collection_id","basis","note","weekly_total","items","pause","steps","running","ground","sources","evidence_at"]}},{"name":"preview_automation","description":"What a topic or series would write, before adding it or after changing its brief: a topic's subjects (mapped from its name and brief, through the same checks as the topic map) or a series' first headlines. Writes nothing; one model call. Pass a topic's subjects back to create_automation (new_topic.subjects) or update_automation (subjects, with remap) to get exactly these. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"kind":{"type":"string","enum":["topic","series"]},"cluster_id":{"description":"An existing pillar or series; else name a new one.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"description":"A new topic's or series' name.","type":"string","maxLength":200},"brief":{"type":"string","maxLength":1500,"description":"What it should cover, in your words: angle, examples, what to leave out. A topic's or series' description (the topic map and the series title from it), a signal's angle. Up to 1500 characters."},"items":{"description":"series: its declared items, if any.","maxItems":12,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}}},"required":["collection_id","kind"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"samples":{"type":"array","items":{"type":"string"},"description":"What it would write: a topic's subjects, a series' headlines."},"subjects":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"subtopic":{"type":"string"},"angle":{"type":"string"},"brief":{"type":"string"},"client_relevance":{"type":"string"},"target_keyword":{"type":"string"}},"required":["subtopic"]}},{"type":"null"}],"description":"A topic's subjects in full: pass them back (create_automation new_topic.subjects, update_automation subjects) to keep exactly these."}},"required":["samples","subjects"]}},{"name":"suggest_more_topics","description":"New subjects a letterbase doesn't cover yet, each with why: proposed from search demand, competitors' ground and the company's positioning, never one it or a sibling site already maps. Writes nothing; one model call. Add one with create_automation (kind topic, new_topic). Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"topics":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"why":{"type":"string"}},"required":["name","why"]},"description":"New subjects it doesn't cover yet, each with why."}},"required":["topics"]}},{"name":"dismiss_subject","description":"Set one open subject of a letterbase's topic map aside: it won't be written. Queued and written subjects can't be dismissed. Records who set it aside, when and why (reason); a subject placed from your measures is never proposed to that automation again. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"topic_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"An open subject of its topic map (get_automation next[].topic_id, list_topics)."},"reason":{"description":"Why it's set aside, in a line. Recorded on the subject; what proposes next reads it back.","type":"string","maxLength":300}},"required":["collection_id","topic_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"dismissed":{"type":"boolean","const":true}},"required":["dismissed"]}},{"name":"get_automation","description":"One automation, opened: what it covers (scope: its subjects, items, watched settings or sources, each written or not), its scheduled posts and the open slots the engine will give it (soonest first: an open slot goes to the automation furthest behind its pace), the ideas it writes next in write-next order (the stored rank), what it wrote last, and, for a topic, where it stands: its strengths, near wins and gaps on its questions with their numbers. The same read as the letterbase page's automation row, opened. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"automation_id":{"type":"string","pattern":"^([0-9a-f-]{36}|derived:\\d+)$","description":"From list_automations: a stored id, or `derived:<n>` for one not stored yet (stored on first change)."}},"required":["collection_id","automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"cluster_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"seer_id":{"type":["string","null"]},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"position":{"type":"number"},"derived":{"type":"boolean"},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."},"written_recently":{"type":"number","description":"Posts it wrote (or had scheduled) in the last 4 weeks: the rolling count its pace is measured on."},"waiting":{"type":"number","description":"Ideas in the pool that are its own."},"brief":{"description":"What it should cover, in a person's words: a topic's or series' description, a seer's angle. Set with create/update_automation `brief`.","type":["string","null"]},"publish_at":{"anyOf":[{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone."}},"required":["weekday","time","timezone"]},{"type":"null"}],"description":"Pinned to a weekday and time: one post a week, in that slot and no other. Null: paced by pace_per_week. Set with create/update_automation `publish_at`."},"decision":{"type":"object","properties":{"source":{"type":"string","enum":["user","proposal"],"description":"Who last set its pace or added it: a person, or a strategy proposal."},"proposal_id":{"description":"The proposal that set it (list_strategy_history), when one did: decide_strategy_suggestion with decision 'undo' restores the paces before it.","type":["string","null"]},"decided_by":{"description":"The person who decided it; null when the review window applied a proposal on its own.","type":["string","null"]},"decided_at":{"type":["string","null"]}},"required":["source","proposal_id","decided_by","decided_at"],"description":"Who set it, for \"3/wk · from a proposal, Oct 22 · Undo\"."}},"required":["id","kind","name","cluster_id","target_id","seer_id","goal","pace_per_week","paused","position","derived","covers","written_recently","waiting","brief","publish_at","decision"]},"scope":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"queued":{"description":"A subject already claimed for a slot.","type":"boolean"},"topic_id":{"description":"The topic-map spoke it is (a pillar's subject).","type":"string"},"measure":{"description":"A subject placed from your measures: its kind and why.","type":"object","properties":{"kind":{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},"reason":{"type":"string","description":"The reason, with its numbers and window: \"Rivals named on 'X', you aren't (0 of 14 answers, 30 days)\"."}},"required":["kind","reason"]}},"required":["label"]},"description":"Its whole ground: each subject (topic), item or installment (series), watched setting (signal) or source (best)."},"queued":{"type":"array","items":{"type":"object","properties":{"run_id":{"type":"string"},"scheduled_for":{"type":"string"},"title":{"type":["string","null"]},"likely":{"description":"An open slot: title is what it will most likely write there, picked for good when written.","type":"boolean"}},"required":["run_id","scheduled_for","title"]},"description":"Its scheduled posts, soonest first (up to 5), including the open slots the engine will give it (furthest behind its pace). title null: an open slot with nothing lined up yet."},"queued_count":{"type":"number","description":"How many of the letterbase's scheduled slots are its own."},"next":{"type":"array","items":{"type":"object","properties":{"suggestion_id":{"type":["string","null"]},"topic_id":{"description":"A topic-map subject (dismiss_subject sets it aside).","type":["string","null"]},"title":{"type":"string"},"why":{"type":["string","null"]},"measure":{"description":"Placed from your measures (the weekly ground finder): its kind of ground and the reason with its numbers. why carries the same reason.","anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},"reason":{"type":"string","description":"The reason, with its numbers and window: \"Rivals named on 'X', you aren't (0 of 14 answers, 30 days)\"."}},"required":["kind","reason"]},{"type":"null"}]}},"required":["suggestion_id","topic_id","title","why"]},"description":"What it writes after its queued slots (up to 5): its ideas in write-next order, then its subjects or items not yet ideas (suggestion_id null), or a seer's detected events."},"recent":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":["string","null"]},"title":{"type":"string"},"at":{"type":["string","null"]}},"required":["article_id","title","at"]},"description":"What it wrote last (up to 3)."},"stands":{"anyOf":[{"type":"object","properties":{"computed_at":{"description":"When the measures were read; null: not measured yet.","type":["string","null"]},"stale":{"type":"boolean","description":"The reading is older than three refreshes."},"overall":{"anyOf":[{"type":"object","properties":{"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},{"type":"null"}]},"reason":{"description":"Why its pillar is that kind, with its numbers.","type":["string","null"]}},"required":["kind","reason"]},{"type":"null"}],"description":"A pillar topic's pillar as a whole; null for a goal-led topic."},"groups":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["strength","near_win","building","gap"],"description":"strength: you already win here (double down); near_win: ahead of every rival here, named or shown some of the time (one or two posts tip it); building: behind a rival, not by much; gap: rivals named far more or you aren't at all (harder; written only where it fits)."},"label":{"type":"string"},"explain":{"type":"string","description":"What this kind of ground means, in a line."},"count":{"type":"number","description":"Its questions of this kind (items shows up to 5)."},"items":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The question key (t: / a: / q:)."},"title":{"type":"string","description":"The question, as the probes ask it."},"numbers":{"type":"string","description":"\"Named on 3 of 11 answers · Hooli named most\"."},"answers":{"type":"number"},"named_you":{"type":"number"},"rival":{"description":"The rival named most on it.","type":["string","null"]}},"required":["key","title","numbers","answers","named_you","rival"]}}},"required":["kind","label","explain","count","items"]},"description":"Strengths, then near wins, then gaps; a kind with no questions is left out."},"empty":{"description":"Said instead when there is nothing to read yet.","type":["string","null"]}},"required":["computed_at","stale","overall","groups","empty"]},{"type":"null"}],"description":"Where it stands: a topic's strengths, near wins and gaps on its questions (its pillar's, or its target's for a goal-led topic), each with its numbers. Null for a series, signal or Best."}},"required":["automation","scope","queued","queued_count","next","recent","stands"]}},{"name":"create_automation","description":"Add an automation to a letterbase: a topic (cluster_id of a pillar, or target_id alone for a goal-led topic), a series (cluster_id), a signal (seer_id) or best. Refused when it would say the same thing as one already there (a second automation on the same pillar, series, target or seer; a pillar and the goal-led topic its bound target already writes). A brief (what it should cover, in your words) is kept where its engine reads it; new_topic / new_series make the pillar or series here (pass preview_automation's subjects to keep exactly what was previewed). Over the weekly cap, it takes the difference from Best (the rest of the week; Best keeps at least half a post, said in `warning`), else it is refused. The letterbase's schedule follows the new total. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"kind":{"type":"string","enum":["topic","series","signal","best"],"description":"topic: which subject (a pillar, cluster_id; or goal-led: a ranking target, target_id). series: which format (cluster_id of a series). signal: what just happened (seer_id; no pace). best: the most valuable idea anywhere (the rest of the week)."},"cluster_id":{"description":"topic: the pillar (list_topics); series: the series (list_series).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"target_id":{"description":"topic without cluster_id: the ranking target it writes toward (list_ranking_targets).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"seer_id":{"description":"signal: a seer writing into this letterbase.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"pace_per_week":{"type":"number","exclusiveMinimum":0,"maximum":21,"description":"Posts a week (fractions allowed: 0.5 = one every two weeks). The weekly total is capped at 21 on a client's own blog, 7 on a phantom (independent) site."},"publish_at":{"description":"topic or series: pin it to a weekday and time, e.g. every Friday at 09:00 America/New_York. It then writes one post a week, in that slot and no other, extra to the letterbase's cadence; leave pace_per_week out.","type":"object","properties":{"weekday":{"type":"integer","minimum":0,"maximum":6,"description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time it goes live, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone the time is in, e.g. America/New_York."}},"required":["weekday","time","timezone"]},"brief":{"type":"string","maxLength":1500,"description":"What it should cover, in your words: angle, examples, what to leave out. A topic's or series' description (the topic map and the series title from it), a signal's angle. Up to 1500 characters."},"new_topic":{"description":"topic: a subject that isn't a pillar yet. Made here (its description the brief), its subjects mapped.","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"subjects":{"maxItems":12,"type":"array","items":{"type":"object","properties":{"subtopic":{"type":"string","minLength":1,"maxLength":200},"angle":{"type":"string","maxLength":400},"brief":{"type":"string","maxLength":1200},"client_relevance":{"type":"string","maxLength":20},"target_keyword":{"type":"string","maxLength":120}},"required":["subtopic"]},"description":"The subjects preview_automation returned: these are mapped as they are (still checked)."}},"required":["name"]},"new_series":{"description":"series: a series that doesn't exist yet (e.g. a format from suggest_automations). Made here, its description the brief.","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"purpose":{"type":"string","maxLength":300},"every_days":{"type":"integer","minimum":1,"maximum":90},"template_key":{"description":"Only when the person asked for a template (list_templates); a format needs none.","type":"string","maxLength":120}},"required":["name"]}},"required":["collection_id","kind"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation_id":{"type":"string"},"warning":{"type":["string","null"]},"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"cluster_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"seer_id":{"type":["string","null"]},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"position":{"type":"number"},"derived":{"type":"boolean"},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."},"written_recently":{"type":"number","description":"Posts it wrote (or had scheduled) in the last 4 weeks: the rolling count its pace is measured on."},"waiting":{"type":"number","description":"Ideas in the pool that are its own."},"brief":{"description":"What it should cover, in a person's words: a topic's or series' description, a seer's angle. Set with create/update_automation `brief`.","type":["string","null"]},"publish_at":{"anyOf":[{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone."}},"required":["weekday","time","timezone"]},{"type":"null"}],"description":"Pinned to a weekday and time: one post a week, in that slot and no other. Null: paced by pace_per_week. Set with create/update_automation `publish_at`."},"decision":{"type":"object","properties":{"source":{"type":"string","enum":["user","proposal"],"description":"Who last set its pace or added it: a person, or a strategy proposal."},"proposal_id":{"description":"The proposal that set it (list_strategy_history), when one did: decide_strategy_suggestion with decision 'undo' restores the paces before it.","type":["string","null"]},"decided_by":{"description":"The person who decided it; null when the review window applied a proposal on its own.","type":["string","null"]},"decided_at":{"type":["string","null"]}},"required":["source","proposal_id","decided_by","decided_at"],"description":"Who set it, for \"3/wk · from a proposal, Oct 22 · Undo\"."}},"required":["id","kind","name","cluster_id","target_id","seer_id","goal","pace_per_week","paused","position","derived","covers","written_recently","waiting","brief","publish_at","decision"]}},"weekly_total":{"type":"number","description":"The sum of the paces that are on: the letterbase's posts a week."},"max_per_week":{"type":"number"},"derived":{"type":"boolean","description":"True when none are stored yet: this is what runs today."}},"required":["automation_id","warning","automations","weekly_total","max_per_week","derived"]}},{"name":"update_automation","description":"Change an automation: its pace, on or off, a pillar topic's goal (the ranking target it aims at), or its brief (what it should cover; remap: true sets a topic's open subjects aside and maps them again from it). The weekly total stays within the cap; the schedule follows it. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"automation_id":{"type":"string","pattern":"^([0-9a-f-]{36}|derived:\\d+)$","description":"From list_automations: a stored id, or `derived:<n>` for one not stored yet (stored on first change)."},"pace_per_week":{"type":"number","exclusiveMinimum":0,"maximum":21,"description":"Posts a week (fractions allowed: 0.5 = one every two weeks). The weekly total is capped at 21 on a client's own blog, 7 on a phantom (independent) site."},"publish_at":{"description":"topic or series: pin it to a weekday and time (one post a week, in that slot), or null to unpin it and pace it by pace_per_week again.","anyOf":[{"type":"object","properties":{"weekday":{"type":"integer","minimum":0,"maximum":6,"description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time it goes live, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone the time is in, e.g. America/New_York."}},"required":["weekday","time","timezone"],"description":"Pin it to a weekday and time: one post a week, at that instant, e.g. every Friday at 09:00 New York."},{"type":"null"}]},"paused":{"description":"Off: it takes no slots until turned back on.","type":"boolean"},"target_id":{"description":"A pillar topic's goal: the ranking target it aims at, or null for none.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"brief":{"description":"Its brief, rewritten (null clears it).","anyOf":[{"type":"string","maxLength":1500,"description":"What it should cover, in your words: angle, examples, what to leave out. A topic's or series' description (the topic map and the series title from it), a signal's angle. Up to 1500 characters."},{"type":"null"}]},"remap":{"description":"topic: set its open subjects aside and map them again from its brief. Queued and written ones stay.","type":"boolean"},"subjects":{"maxItems":12,"type":"array","items":{"type":"object","properties":{"subtopic":{"type":"string","minLength":1,"maxLength":200},"angle":{"type":"string","maxLength":400},"brief":{"type":"string","maxLength":1200},"client_relevance":{"type":"string","maxLength":20},"target_keyword":{"type":"string","maxLength":120}},"required":["subtopic"]},"description":"The subjects preview_automation returned: these are mapped as they are (still checked)."}},"required":["collection_id","automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation_id":{"type":"string"},"warning":{"type":["string","null"]},"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"cluster_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"seer_id":{"type":["string","null"]},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"position":{"type":"number"},"derived":{"type":"boolean"},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."},"written_recently":{"type":"number","description":"Posts it wrote (or had scheduled) in the last 4 weeks: the rolling count its pace is measured on."},"waiting":{"type":"number","description":"Ideas in the pool that are its own."},"brief":{"description":"What it should cover, in a person's words: a topic's or series' description, a seer's angle. Set with create/update_automation `brief`.","type":["string","null"]},"publish_at":{"anyOf":[{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone."}},"required":["weekday","time","timezone"]},{"type":"null"}],"description":"Pinned to a weekday and time: one post a week, in that slot and no other. Null: paced by pace_per_week. Set with create/update_automation `publish_at`."},"decision":{"type":"object","properties":{"source":{"type":"string","enum":["user","proposal"],"description":"Who last set its pace or added it: a person, or a strategy proposal."},"proposal_id":{"description":"The proposal that set it (list_strategy_history), when one did: decide_strategy_suggestion with decision 'undo' restores the paces before it.","type":["string","null"]},"decided_by":{"description":"The person who decided it; null when the review window applied a proposal on its own.","type":["string","null"]},"decided_at":{"type":["string","null"]}},"required":["source","proposal_id","decided_by","decided_at"],"description":"Who set it, for \"3/wk · from a proposal, Oct 22 · Undo\"."}},"required":["id","kind","name","cluster_id","target_id","seer_id","goal","pace_per_week","paused","position","derived","covers","written_recently","waiting","brief","publish_at","decision"]}},"weekly_total":{"type":"number","description":"The sum of the paces that are on: the letterbase's posts a week."},"max_per_week":{"type":"number"},"derived":{"type":"boolean","description":"True when none are stored yet: this is what runs today."}},"required":["automation_id","warning","automations","weekly_total","max_per_week","derived"]}},{"name":"delete_automation","description":"Remove an automation from a letterbase. A removed series is paused and a removed seer stops writing here; the schedule follows the new weekly total. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase (list_collections)."},"automation_id":{"type":"string","pattern":"^([0-9a-f-]{36}|derived:\\d+)$","description":"From list_automations: a stored id, or `derived:<n>` for one not stored yet (stored on first change)."}},"required":["collection_id","automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"warning":{"type":["string","null"]},"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stored id, or derived:<n> for what runs today (stored on first change)."},"kind":{"type":"string","enum":["topic","series","signal","best"]},"name":{"type":"string"},"cluster_id":{"type":["string","null"]},"target_id":{"type":["string","null"]},"seer_id":{"type":["string","null"]},"goal":{"description":"The ranking target it writes toward, when it has one.","type":["string","null"]},"pace_per_week":{"description":"Posts a week; null for a signal (it writes when its seer fires).","type":["number","null"]},"paused":{"type":"boolean"},"position":{"type":"number"},"derived":{"type":"boolean"},"covers":{"type":"string","description":"What it writes, concretely, in one line: what's ahead (a pillar's next subjects, a series' purpose or next items, what a seer watches, where Best's ideas come from) and what it wrote lately (\"File locks, Caching model weights · 3 in the last 4 wks\")."},"written_recently":{"type":"number","description":"Posts it wrote (or had scheduled) in the last 4 weeks: the rolling count its pace is measured on."},"waiting":{"type":"number","description":"Ideas in the pool that are its own."},"brief":{"description":"What it should cover, in a person's words: a topic's or series' description, a seer's angle. Set with create/update_automation `brief`.","type":["string","null"]},"publish_at":{"anyOf":[{"type":"object","properties":{"weekday":{"type":"number","description":"0 = Sunday … 6 = Saturday."},"time":{"type":"string","description":"Local time, 24-hour HH:MM."},"timezone":{"type":"string","description":"IANA timezone."}},"required":["weekday","time","timezone"]},{"type":"null"}],"description":"Pinned to a weekday and time: one post a week, in that slot and no other. Null: paced by pace_per_week. Set with create/update_automation `publish_at`."},"decision":{"type":"object","properties":{"source":{"type":"string","enum":["user","proposal"],"description":"Who last set its pace or added it: a person, or a strategy proposal."},"proposal_id":{"description":"The proposal that set it (list_strategy_history), when one did: decide_strategy_suggestion with decision 'undo' restores the paces before it.","type":["string","null"]},"decided_by":{"description":"The person who decided it; null when the review window applied a proposal on its own.","type":["string","null"]},"decided_at":{"type":["string","null"]}},"required":["source","proposal_id","decided_by","decided_at"],"description":"Who set it, for \"3/wk · from a proposal, Oct 22 · Undo\"."}},"required":["id","kind","name","cluster_id","target_id","seer_id","goal","pace_per_week","paused","position","derived","covers","written_recently","waiting","brief","publish_at","decision"]}},"weekly_total":{"type":"number","description":"The sum of the paces that are on: the letterbase's posts a week."},"max_per_week":{"type":"number"},"derived":{"type":"boolean","description":"True when none are stored yet: this is what runs today."}},"required":["removed","warning","automations","weekly_total","max_per_week","derived"]}},{"name":"list_series","description":"The org's content series with their members and coverage state (covered / missing / unmatched items). A series is a coverage obligation beside topic clusters: one post per declared item, org-level (runs on the main blog) or bound to a collection. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"series":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"collection_id":{"type":["string","null"]},"name":{"type":"string"},"description":{"type":["string","null"]},"template_key":{"type":["string","null"]},"template_keys":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"The templates its posts may use, when several."},"series_purpose":{"description":"One sentence: why the series exists.","type":["string","null"]},"series_every_days":{"description":"Its pace: at most one written installment every this many days; null = 7.","type":["number","null"]},"coverage":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Declared coverage items."},"status":{"type":"string"},"focus_muted":{"type":"boolean"},"retired_at":{"type":["string","null"]},"created_at":{"type":"string"},"members":{"type":"array","items":{"type":"object","properties":{"title":{"type":["string","null"]},"coverage_item":{"type":["string","null"]},"source":{"type":"string","description":"\"written\" | \"backfilled\" | \"external\"."},"article_id":{"type":["string","null"]}},"required":["title","coverage_item","source","article_id"]}},"coverage_state":{"type":"object","properties":{"covered":{"type":"array","items":{"type":"string"},"description":"Declared items with at least one member."},"missing":{"type":"array","items":{"type":"string"},"description":"Declared items no member fulfills yet."},"unmatched":{"type":"array","items":{"type":"string"},"description":"Members claiming an item not in the declared set."},"unclassified":{"type":"number","description":"Members with no coverage_item."}},"required":["covered","missing","unmatched","unclassified"]}},"required":["id","collection_id","name","description","template_key","template_keys","series_purpose","series_every_days","coverage","status","focus_muted","retired_at","created_at","members","coverage_state"]}}},"required":["series"]}},{"name":"create_series","description":"Create a content series, an automation on the collection it writes into: a named coverage obligation (description + declared items, optionally template-bound, paced every_days) the planner queue writes one post per item for, merging items existing posts already fulfill. \"A comparison post every 7 days\" is a series with a comparison template and every_days 7. Omit collection_id for an org-level series that runs on the main blog. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Collection UUID to bind the series to (the collection it writes into). Omit for an org-level series (runs on the main blog).","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Series name (a rubric, e.g. 'Secrets management, tool by tool')"},"description":{"anyOf":[{"type":"string","minLength":1,"maxLength":1000,"description":"What every installment looks like: one sentence of shape, not topic"},{"type":"null"}]},"template_key":{"description":"Optional content-template key every installment composes with (from list_templates)","anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}]},"template_keys":{"anyOf":[{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},{"type":"null"}],"description":"The templates its posts may use (keys from list_templates); with several, each post picks one. [] or null: untemplated."},"coverage":{"default":[],"description":"Declared items to cover: one post per item","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"purpose":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"description":"One sentence: why the series exists. Empty or null clears it."},"every_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}],"description":"Its pace: at most one written installment every this many days (a comparison post every 7 days is a series with a comparison template paced at 7). null: the default, 7."},"paused":{"description":"Start it paused: listed, out of the queue until resumed.","type":"boolean"}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string","description":"The new series' cluster id."}},"required":["id"]}},{"name":"update_series","description":"Change a content series (from list_series): its pace (every_days: at most one written installment every N days; null = the default 7), the templates its posts may use (template_keys), its purpose, description, name or declared items (coverage, replaces the list), pause or resume it (paused), or set its status (retired is the delete; its members keep their history). Fields left out keep their value. The same write as the collection's series card and org config apply. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Series name (a rubric, e.g. 'Secrets management, tool by tool')"},"description":{"anyOf":[{"type":"string","minLength":1,"maxLength":1000,"description":"What every installment looks like: one sentence of shape, not topic"},{"type":"null"}]},"template_key":{"description":"The single template key; null clears it.","anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}]},"template_keys":{"anyOf":[{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},{"type":"null"}],"description":"The templates its posts may use (keys from list_templates); with several, each post picks one. [] or null: untemplated."},"coverage":{"description":"REPLACES the declared items: one post per item.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"purpose":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"description":"One sentence: why the series exists. Empty or null clears it."},"every_days":{"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}],"description":"Its pace: at most one written installment every this many days (a comparison post every 7 days is a series with a comparison template paced at 7). null: the default, 7."},"paused":{"description":"Pause (true) or resume (false): a paused series stays listed, out of the queue.","type":"boolean"},"status":{"description":"The series' state: active, paused, completed (coverage exhausted) or retired (the delete — its members keep their history).","type":"string","enum":["active","paused","completed","retired"]},"series_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Series UUID (from list_series)"}},"required":["series_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string","description":"The series' cluster id."},"updated":{"type":"boolean","const":true}},"required":["id","updated"]}},{"name":"queue_series_next","description":"Queue a series' next installment now, ignoring its pace (asking is the decision that it's time): the series' next headline takes the collection's next open slot when its Planner is on (a collection run, under its cadence and approval), or becomes a draft when it is off — the same queue every headline takes (queue_suggestion). Coverage dedupe and the never-mention blocklist still apply; an open-ended series (no declared items) picks a new subject it hasn't covered. The same operation as the Collections \"Queue\" on a series. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection the series writes into (list_collections)."},"series_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The series (list_series)."}},"required":["collection_id","series_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestion_id":{"type":"string","description":"The headline's row on the suggestion ledger."},"headline":{"type":"string"},"item":{"type":"string","description":"The series item it fulfils (an open-ended series: the headline itself)."},"via":{"type":"string","description":"How it was queued: a planner slot, a draft, or a run (queue_suggestion's `via`)."},"run_id":{"description":"The collection run, when it took a slot.","type":["string","null"]},"scheduled_for":{"type":["string","null"]},"article_id":{"description":"The draft, when the Planner is off.","type":["string","null"]}},"required":["suggestion_id","headline","item","via","run_id","scheduled_for","article_id"]}},{"name":"backfill_series","description":"Reconstruct a series from a collection's existing posts: deterministic title matching plus an AI-assist pass records which posts already fulfill declared items (as 'backfilled' members). Idempotent per article. This is the migration story: a blog starts with its history, not empty. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"series_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Series UUID (from list_series)"},"collection_id":{"description":"Collection whose existing posts are scanned. Defaults to the series' own collection; required for an org-level series.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["series_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"matched":{"type":"number"},"matches":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"item":{"description":"The coverage item this post fulfills.","type":["string","null"]},"via":{"type":"string","enum":["deterministic","ai"]}},"required":["title","item","via"]}}},"required":["matched","matches"]}},{"name":"list_suggestions","description":"The org's Curate post suggestions: \"active\" open suggested headlines in write-next order (the order the schedule will take them: composite score, with the collection's steering-tier share deciding which positions go to its ranking targets' ground), or \"queued\" ones already turned into draft posts (most-recently-queued first, joined to the resulting article's lifecycle phase). When collection_id is passed and that collection is still mid-onboarding, the response also carries onboarding_in_progress (the job's current stage) — treat the list as provisional, not final, until that clears. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional collection UUID (from list_collections) to scope the results","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"default":"active","description":"\"active\": open headlines, status suggested and parked (backlog) together, in write-next order; check items[].status. \"queued\": ones already queued (onto a scheduled run, or as a draft post). Without collection_id it covers every collection, phantoms included.","type":"string","enum":["active","queued"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"article":{"description":"Present only for status=queued items: the linked draft article.","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"lifecycle_phase":{"type":"string"}},"required":["id","title","lifecycle_phase"]},{"type":"null"}]}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"]}},"onboarding_in_progress":{"description":"Present (with the current stage) only when collection_id was given and that collection's onboarding job is still running — items above predate its topic map/series/GEO signal finishing, so treat this list as provisional, not final.","type":["string","null"]}},"required":["items"]}},{"name":"generate_suggestions","description":"Top the collection's active suggested headlines back up to target by scoring candidate topics (competitor, GEO, SEO, cluster-alignment, authority, cannibalization signals) and generating original headlines with reasoning. Idempotent-ish — already-suggested topics are never picked again. Refuses while the collection's onboarding job (from onboard_collection or import_sitemap's first-import follow-up) is still running — its topic map/series/GEO signal aren't fully in yet, and headlines sourced before then would look final but weren't drawn from the real picture. Wait for get_collection_onboard_status to report 'done' first. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections) to generate suggested headlines for"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created":{"type":"number","description":"Count of newly visible (status=suggested) suggestions."},"message":{"description":"Present only on no-op / at-target early exits.","type":"string"},"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"]},"description":"All inserted rows (visible + reserve)."}},"required":["created","suggestions"]}},{"name":"update_suggestion","description":"Change one headline suggestion (list_suggestions): dismiss it (status \"dismissed\" — not this one), park it (\"backlog\") or bring it back (\"suggested\"), and/or reword it (headline; the topic stays the same). A headline leaving the visible list is replaced at once. The same operation as the app's Dismiss and Edit. To write it, use queue_suggestion or run_collection. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"suggestion_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The headline (list_suggestions)."},"status":{"description":"dismissed = not this one; backlog = later; suggested = back in the list.","type":"string","enum":["suggested","backlog","dismissed"]},"headline":{"description":"New wording; the topic stays the same.","type":"string","minLength":1,"maxLength":200}},"required":["suggestion_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestion":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"]},"promoted":{"anyOf":[{},{"type":"null"}],"description":"The headline that took its place in the visible list, if one did."}},"required":["suggestion","promoted"]}},{"name":"queue_suggestion","description":"Turn a suggestion into a post in the suggestion's own collection — the same operation as the app's queue button. When the collection's Planner is on, the headline takes the next open slot of its schedule (or the calendar extends): a collection run that produces on the slot's lead time under the collection's approval mode (via \"slot\", with run_id and scheduled_for; follow it with get_collection_run). When the Planner is off there is no schedule to land in, so it creates a draft article titled with the headline, to be written by hand or with run_auto_pen (via \"draft\", with article; Auto-Pen starts on its own if the collection auto-starts Curate posts). Either way the suggestion is marked queued and a reserve headline backfills the vacated slot. To write it now instead, call run_collection with the suggestion as its topic. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"suggestion_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Suggestion UUID (from list_suggestions)"}},"required":["suggestion_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"via":{"type":"string","enum":["slot","run","draft"],"description":"slot: the Planner is on and the headline took the collection's next open slot (a scheduled collection run). draft: the Planner is off, so a draft article was created to fill by hand. run: a run started now (automations only; not returned to this tool)."},"scheduled":{"type":"boolean","description":"True when the post landed on the collection's schedule (via slot)."},"run_id":{"description":"The collection run that will write the post (via slot), or null.","type":["string","null"]},"scheduled_for":{"description":"When the slot publishes (via slot), or null.","type":["string","null"]},"article":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string"},"status":{"type":"string"},"lifecycle_phase":{"type":"string"},"phase_entered_at":{"type":"string"},"last_activity_at":{"type":"string"},"origin":{"type":"string","description":"\"authored\" | \"imported\"."},"commissioned_brand_subject":{"type":["string","null"]},"commissioned_publish_control":{"type":["string","null"]},"metadata":{"anyOf":[{},{"type":"null"}]},"published_at":{"type":["string","null"]},"published_version":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","creator_id","title","content","status","lifecycle_phase","phase_entered_at","last_activity_at","origin","commissioned_brand_subject","commissioned_publish_control","metadata","published_at","published_version","created_at","updated_at"]},{"type":"null"}],"description":"The newly created draft article (via draft), or null."},"suggestion":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"]},{"type":"null"}],"description":"The suggestion after being marked queued and linked to the draft (via draft), or null."},"promoted":{"description":"A reserve suggestion promoted to backfill the vacated slot, or null if none. Shape mirrors a curate_post_suggestions row but is typed as unknown because it comes straight from the promote_reserve_suggestion RPC."},"auto_pen_id":{"description":"Auto-Pen started on the draft (via draft, when the collection auto-starts Curate posts), or null.","type":["string","null"]}},"required":["via","scheduled","run_id","scheduled_for","article","suggestion","promoted","auto_pen_id"]}},{"name":"add_headline","description":"Add a headline you wrote to a collection — how a person or their agent starts a post of their own (the letterbase page's \"New post\"). It joins the collection's suggestions as source self_sourced (Up next, list_suggestions); the same headline asked twice is one suggestion. `context` carries your notes for the writer (an angle, background, links to read first, your own data) onto the post. With queue: true it is queued at once, exactly as queue_suggestion queues: the next open slot when the collection's Planner is on (a collection run, under its cadence and approval), a draft when it's off. With own: true (and queue) it is always a draft you write yourself, whatever the Planner says: returned as queued.article, with nothing writing it until you do (update_article) or start run_auto_pen on it. To write it now instead, call run_collection with the returned suggestion's id. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection to write it in (list_collections)"},"headline":{"type":"string","minLength":1,"maxLength":300,"description":"The headline, as it should read"},"context":{"description":"Optional notes for the writer: an angle, background research, links to read first, your own data. They become the post's creation context when it is written. Given for a headline already waiting, they replace its notes.","type":"string","maxLength":2000},"queue":{"description":"Queue it now, as queue_suggestion does: the next open slot when the Planner is on, a draft when it's off. Default false: it waits in Up next (list_suggestions).","type":"boolean"},"own":{"description":"With queue: true, a draft you write yourself: a post at once (returned as queued.article), whatever the Planner says, with no slot, no run and no Auto-Pen on it, even when the collection auto-starts Curate posts. Write it by hand (update_article) or have it written later with run_auto_pen, whose skip_steps pick the steps (research only, step by step).","type":"boolean"}},"required":["collection_id","headline"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestion":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"],"description":"The headline, as a suggestion of the collection (source self_sourced)."},"queued":{"anyOf":[{"type":"object","properties":{"via":{"type":"string","enum":["slot","run","draft"],"description":"slot: the Planner is on and the headline took the collection's next open slot (a scheduled collection run). draft: the Planner is off, so a draft article was created to fill by hand. run: a run started now (automations only; not returned to this tool)."},"scheduled":{"type":"boolean","description":"True when the post landed on the collection's schedule (via slot)."},"run_id":{"description":"The collection run that will write the post (via slot), or null.","type":["string","null"]},"scheduled_for":{"description":"When the slot publishes (via slot), or null.","type":["string","null"]},"article":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":"string"},"status":{"type":"string"},"lifecycle_phase":{"type":"string"},"phase_entered_at":{"type":"string"},"last_activity_at":{"type":"string"},"origin":{"type":"string","description":"\"authored\" | \"imported\"."},"commissioned_brand_subject":{"type":["string","null"]},"commissioned_publish_control":{"type":["string","null"]},"metadata":{"anyOf":[{},{"type":"null"}]},"published_at":{"type":["string","null"]},"published_version":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","creator_id","title","content","status","lifecycle_phase","phase_entered_at","last_activity_at","origin","commissioned_brand_subject","commissioned_publish_control","metadata","published_at","published_version","created_at","updated_at"]},{"type":"null"}],"description":"The newly created draft article (via draft), or null."},"suggestion":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"]},{"type":"null"}],"description":"The suggestion after being marked queued and linked to the draft (via draft), or null."},"promoted":{"description":"A reserve suggestion promoted to backfill the vacated slot, or null if none. Shape mirrors a curate_post_suggestions row but is typed as unknown because it comes straight from the promote_reserve_suggestion RPC."},"auto_pen_id":{"description":"Auto-Pen started on the draft (via draft, when the collection auto-starts Curate posts), or null.","type":["string","null"]}},"required":["via","scheduled","run_id","scheduled_for","article","suggestion","promoted","auto_pen_id"]},{"type":"null"}],"description":"What queuing did, when `queue` was set (the same shape queue_suggestion returns), else null."}},"required":["suggestion","queued"]}},{"name":"add_headline_from_phantom","description":"Take a phantom post that's working in Google (find one with list_phantom_seo_articles standouts_only=true) and add its HEADLINE to one of the organization's own collections, the main blog by default, as a Curate suggestion — the basis for a new article on the client's site, not a copy of the phantom's post. The suggestion is source_type 'phantom', status 'suggested', priority 'high', and carries the post's Search Console evidence over the period (impressions, clicks, average position, top queries) in why / keyword / signal_breakdown.phantom. Idempotent per post and collection: pushing the same post again returns the existing suggestion with created=false. Refuses a post that isn't published on one of this organization's phantoms, an organization with no main blog, and a collection that already has a post with that exact headline. To write it now, call run_collection on the returned collection with the suggestion as its topic; otherwise it's written like any other headline (queue_suggestion, or the Planner). Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Phantom article UUID (from list_phantom_seo_articles) whose headline the target collection should write"},"collection_id":{"description":"Collection to add the headline to. Omitted = the organization's main blog (list_collections is_main)","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"period":{"description":"Search Console window the post's evidence (impressions, clicks, top queries) is read over. Default 90d","type":"string","enum":["14d","30d","90d"]}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created":{"type":"boolean","description":"False when this post had already been pushed into the collection; the existing suggestion is returned."},"suggestion":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"collection_id":{"type":["string","null"]},"headline":{"type":"string"},"status":{"type":"string","description":"\"suggested\" | \"backlog\" | \"reserve\" | \"queued\"."},"priority":{"description":"Source-topic priority: \"high\" | \"medium\" | \"low\".","type":["string","null"]},"score":{"type":["number","null"]},"rank":{"description":"Its place in write-next order (higher first), ranked ahead of the slot from its ground, how that ground does against its peers and its market facts; null until first ranked.","type":["number","null"]},"rank_terms":{"description":"What made the rank: its kind of ground (strength / near win / gap), its peers, its market facts.","anyOf":[{},{"type":"null"}]},"ranked_at":{"type":["string","null"]},"why":{"description":"Reasoning for the suggestion.","type":["string","null"]},"context":{"description":"The author's notes for the writer (add_headline `context`); null for generated headlines.","type":["string","null"]},"keyword":{"type":["string","null"]},"source_type":{"description":"Origin signal, e.g. competitor / GEO / SEO / cluster.","type":["string","null"]},"source_topic_id":{"type":["string","null"]},"signal_breakdown":{"anyOf":[{},{"type":"null"}],"description":"Per-signal scoring breakdown blob."},"article_id":{"description":"The queued draft article, once queued.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","collection_id","headline","status","priority","score","why","keyword","source_type","source_topic_id","signal_breakdown","article_id","created_at","updated_at"],"description":"The headline suggestion (source_type 'phantom', source_topic_id = the phantom article)."},"collection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"The collection the headline was added to."},"evidence":{"type":"object","properties":{"articleId":{"type":"string"},"blogId":{"type":"string"},"blogName":{"type":"string"},"url":{"description":"The post's page on the phantom, once Search Console has matched it.","type":["string","null"]},"period":{"type":"object","properties":{"key":{"type":"string"},"days":{"type":"number"},"start":{"type":"string"},"end":{"type":"string"}},"required":["key","days","start","end"]},"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"description":"Impression-weighted average position; null with no impressions.","type":["number","null"]},"topQueries":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string"},"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]}},"required":["query","impressions","clicks","position"]}}},"required":["articleId","blogId","blogName","url","period","impressions","clicks","position","topQueries"],"description":"What the phantom post earned in Google over the period — also stored on the suggestion."}},"required":["created","suggestion","collection","evidence"]}},{"name":"infer_competitor_keywords","description":"Infer SEO keywords and per-org priority for scraped competitor topics, scored against the org's description. By default skips topics already scored for this org; pass force to re-score everything (e.g. after editing the description). Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"competitor_domain_id":{"description":"Optional competitor domain UUID (from list_competitors) to scope inference to just that competitor's topics. Omit to infer across all tracked competitors.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"force":{"default":false,"description":"Re-score topics that already have inferred keywords","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"updated":{"type":"number"},"message":{"type":"string"},"results":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"inferred_keyword":{"type":"string"},"keyword_priority":{"type":"string","enum":["high","medium","low"]}},"required":["id","inferred_keyword","keyword_priority"]}}},"required":["updated"]}},{"name":"infer_client_keywords","description":"Infer the primary keyword + relevance for each of the org's non-archived Letter articles (any collection or status, phantom posts included; get_content_inventory) with an LLM call, and store them. Idempotent: unchanged articles are skipped via content hash. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"inferred":{"type":"number"},"skipped":{"type":"number"},"total":{"type":"number"},"lowInventory":{"type":"boolean"},"assigned":{"type":"number"},"uncategorized":{"type":"number"},"newClusters":{"type":"number"}},"required":["inferred","skipped","total","lowInventory"]}},{"name":"get_content_inventory","description":"Read-only count of the org's Letter articles (every non-archived article in any collection, drafts and phantom posts included; not pages on the client's site), how many have an inferred keyword (infer_client_keywords), and lowInventory when total is under minInventory (5), too few for clustering. || total: \"Non-archived Letter articles in the org, any collection or status, phantoms included.\" inferred: \"Articles with an inferred keyword (archived ones included).\" Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"total":{"type":"number","description":"Non-archived articles."},"inferred":{"type":"number","description":"Articles with an inferred keyword."},"lowInventory":{"type":"boolean"},"minInventory":{"type":"number"}},"required":["total","inferred","lowInventory","minInventory"]}},{"name":"list_seo_opportunities","description":"The org's per-cluster SEO opportunities (keywords discovered per cluster, relevance-filtered by an LLM), grouped by cluster and sorted priority-cluster-first then by SEO score. Pass refresh to re-run discovery first (several SEO API calls plus an LLM filter call — rate-limited); omit to just read the latest synced data (also refreshed nightly by cron). cluster_id only narrows what a refresh re-discovers — the read itself always returns every org opportunity. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{"cluster_id":{"description":"Optional cluster UUID (from list_clusters) to scope a refresh to just that cluster. Read results are always the full org list — this only narrows what a refresh re-discovers.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"refresh":{"default":false,"description":"Re-run SEO discovery for the org (or just cluster_id, if given) before reading — several SEO API calls plus an LLM filter call, so it's rate-limited. Omit to just read the latest synced data (also refreshed nightly by cron).","type":"boolean"},"country":{"description":"ISO 3166-1 alpha-2 country code (e.g. 'AU') to measure this refresh in, overriding the org's saved market for this call only — an exploration, not a commitment. Volume and difficulty are market-specific: a keyword can be 10 searches at difficulty 47 in the US and 30 at difficulty 11 in Australia. Keywords found this way are stored labelled with this market, but the existing pool is left alone, and the org's own market re-measures them on its next sync. To make a market stick across discovery, suggestions and scoring, set it with set_company_info instead. Ignored without refresh.","type":"string","pattern":"^[A-Z]{2}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"opportunities":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"keyword":{"type":"string"},"cluster_id":{"type":["string","null"]},"cluster_name":{"type":"string"},"is_priority":{"type":"boolean"},"monthly_searches":{"type":["number","null"]},"keyword_difficulty":{"type":["number","null"]},"seo_score":{"type":["number","null"]},"market":{"type":"string","description":"ISO country code this row's volume and difficulty were measured in. Rows still showing a previous market are re-priced on the next sync."}},"required":["id","keyword","cluster_id","cluster_name","is_priority","monthly_searches","keyword_difficulty","seo_score","market"]}},"seoEnabled":{"type":"boolean"},"market":{"description":"The org's saved search market (ISO 3166-1 alpha-2). Absent when SEO isn't configured.","type":"string"},"quotaExhausted":{"description":"Present only when SEO is enabled.","type":"boolean"}},"required":["opportunities","seoEnabled"]}},{"name":"remove_seo_opportunities","description":"Delete discovered SEO keywords for the org — either specific opportunity ids (from list_seo_opportunities), or every keyword attributed to one cluster. Never touches the cluster itself. Pass permanent to also record them as exclusions so the nightly cron can't rediscover them. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"ids":{"description":"Specific SEO opportunity row UUIDs to delete (from list_seo_opportunities).","type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"cluster_id":{"description":"Delete every discovered keyword attributed to this cluster.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"permanent":{"default":false,"description":"Also record each removed keyword as a permanent exclusion, so discovery never adds it back. Without this a deleted keyword is simply absent from the dedupe set, and the nightly cron rediscovers it — the reason pruning the same terms can feel endless. Manage the resulting list with list_keyword_exclusions / remove_keyword_exclusions.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"excluded":{"description":"How many permanent exclusions were recorded (present only when permanent was set).","type":"number"}},"required":["deleted"]}},{"name":"list_keyword_exclusions","description":"Keywords this org has permanently excluded from SEO discovery. An exclusion is what makes a removal stick — without one, deleting an opportunity only drops it from the dedupe set and the nightly cron rediscovers it. Requires capability curate:read.","capability":"curate:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"exclusions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"pattern":{"type":"string"},"match_kind":{"type":"string","enum":["exact","contains"]},"cluster_id":{"type":["string","null"]},"reason":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","pattern","match_kind","cluster_id","reason","created_at"]}}},"required":["exclusions"]}},{"name":"add_keyword_exclusions","description":"Permanently exclude keywords from SEO discovery. Applies immediately (matching keywords already in the pool are removed) and to every future run, across all discovery sources. Use match_kind 'contains' for a pattern like a competitor brand or an out-of-market country name. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"keywords":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"description":"Keywords (or, with match_kind 'contains', substrings) discovery must never surface again."},"match_kind":{"default":"exact","description":"'exact' blocks the keyword itself. 'contains' blocks every keyword containing the string — e.g. a competitor's brand name, or a country you don't sell into.","type":"string","enum":["exact","contains"]},"cluster_id":{"description":"Optional cluster UUID to confine the exclusion to one cluster. Omit for org-wide.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"reason":{"description":"Why this was excluded — shown back in the list.","type":"string","maxLength":500}},"required":["keywords"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"added":{"type":"number","description":"Exclusions newly recorded (re-excluding a keyword is a no-op, not an error)."},"removed_from_pool":{"type":"number","description":"Opportunities already in the pool that the new exclusions removed."}},"required":["added","removed_from_pool"]}},{"name":"remove_keyword_exclusions","description":"Lift keyword exclusions (from list_keyword_exclusions), so discovery may surface those keywords again. Does not re-add anything on its own — the next refresh decides. Requires capability curate:write.","capability":"curate:write","inputSchema":{"type":"object","properties":{"ids":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"description":"Exclusion UUIDs to lift (from list_keyword_exclusions)."}},"required":["ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"number"}},"required":["removed"]}},{"name":"suggest_org_description","description":"Fetch the org's website and use AI to draft an organization description. Does NOT persist — pass the result to set_company_info to save it. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"description":{"type":"string","description":"AI-drafted org description; not persisted."}},"required":["description"]}},{"name":"suggest_positioning","description":"AI-draft positioning (priority topics, stances, avoid topics) from the org's content inventory + description. Does NOT persist — pass the result to set_positioning to save it. Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"description":{"description":"Override the org's stored manifesto/description for this draft only (does not persist). Omit to use the stored description.","type":"string","maxLength":50000}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"draft":{"type":"object","properties":{"priority_topics":{"type":"array","items":{"type":"string"}},"stances":{"type":"array","items":{"type":"string"}},"avoid_topics":{"type":"array","items":{"type":"string"}}},"required":["priority_topics","stances","avoid_topics"]}},"required":["draft"]}},{"name":"suggest_ranking_targets","description":"Evidence-informed ranking-target suggestions: positioning stances + what the client's own site already ranks for + what AI assistants already name them for. Nothing is saved. Pass offering_id to get suggestions about THAT offering (its manifesto, stances, name and own site) for its scope (validated, and echoed back as offering_id); then call add_ranking_target with that offering_id for each suggestion you keep — each lands active while that scope has room under its cap, inactive after. Requires capability ranking_target:read.","capability":"ranking_target:read","inputSchema":{"type":"object","properties":{"offering_id":{"description":"The scope you mean to add the suggestions to: an offering id (list_offerings), or null/omitted for the company layer. Validated and echoed back so each add_ranking_target call can pass it; nothing is saved by this tool.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"phrase":{"type":"string"},"basis":{"type":"string","enum":["already_strong","white_space","positioning"]},"rationale":{"type":"string"},"suggestedFraming":{"type":["string","null"]}},"required":["phrase","basis","rationale","suggestedFraming"]}},"offering_id":{"description":"The scope the suggestions were requested for (null = the company layer) — pass it to add_ranking_target","type":["string","null"]}},"required":["suggestions","offering_id"]}},{"name":"suggest_onboarding","description":"Draft a whole starting content strategy from just a domain — org description, editorial positioning (priority topics / stances / avoid topics), and evidence-based ranking targets — by composing suggest_org_description, suggest_positioning, and suggest_ranking_targets in one call. The freshly drafted description is threaded into the positioning step, so it works before anything is saved. Nothing is persisted: review, then apply with set_company_info (manifesto), set_positioning, and add_ranking_target (one call per target). Requires capability provisioning:read.","capability":"provisioning:read","inputSchema":{"type":"object","properties":{"domain":{"description":"Bare domain to draft the strategy from (no scheme), e.g. acme.com. Omit to use the org's stored domain (set via set_company_info). Lets an agent onboard from just a URL before anything is saved.","type":"string","maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":"string","description":"The domain the draft was generated from."},"description":{"type":"string","description":"AI-drafted org manifesto/description (may be empty if the site couldn't be read). Apply via set_company_info."},"positioning":{"type":"object","properties":{"priority_topics":{"type":"array","items":{"type":"string"}},"stances":{"type":"array","items":{"type":"string"}},"avoid_topics":{"type":"array","items":{"type":"string"}}},"required":["priority_topics","stances","avoid_topics"],"description":"Drafted editorial positioning. Apply via set_positioning."},"ranking_targets":{"type":"array","items":{"type":"object","properties":{"phrase":{"type":"string"},"basis":{"type":"string","enum":["already_strong","white_space","positioning"]},"rationale":{"type":"string"},"suggestedFraming":{"type":["string","null"]}},"required":["phrase","basis","rationale","suggestedFraming"]},"description":"Drafted, evidence-based ranking targets. Apply one at a time via add_ranking_target."},"note":{"type":"string","description":"How to apply the draft and any caveats."}},"required":["domain","description","positioning","ranking_targets","note"]}},{"name":"sync_geo_opportunities","description":"Import the latest opportunities from the org's connected third-party GEO provider (Profound or AirOps) into list_geo_opportunities. Fails with not_found when that provider isn't connected. Does not run Letter's own probes (list_probes, start_first_probes). Requires capability geo:write.","capability":"geo:write","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["airops","profound"],"description":"The connected GEO provider to sync (see list_geo_opportunities)"}},"required":["provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"synced":{"type":"number"},"last_synced_at":{"type":"string"}},"required":["success","synced","last_synced_at"]}},{"name":"backfill_seo_history","description":"One-time historical import: runs the full Search Console sync pipeline over the maximum lookback window so 90d views and previous-period comparisons work immediately. Idempotent — re-runs just re-upsert. Requires capability insight:write.","capability":"insight:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"pagesSynced":{"type":"number"},"matchedPages":{"type":"number"},"queryRowsSynced":{"type":"number"}},"required":["success","pagesSynced","matchedPages","queryRowsSynced"]}},{"name":"list_api_keys","description":"List the API keys in the caller's organization — id, name, capabilities, rate limit, and usage timestamps. Never returns the key secret or its hash; there is no way to recover a lost key's plaintext value through this API. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"api_key_id":{"type":"string"},"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"},"description":"Capability scopes granted to the key."},"rate_limit":{"description":"Requests-per-window cap, or null for the default.","type":["number","null"]},"last_used_at":{"type":["string","null"]},"revoked_at":{"description":"Set once the key has been revoked; null while active.","type":["string","null"]},"created_at":{"type":["string","null"]},"created_by_user_id":{"type":["string","null"]},"is_calling_key":{"type":"boolean","description":"True for the key authenticating this request."}},"required":["api_key_id","name","capabilities","rate_limit","last_used_at","revoked_at","created_at","created_by_user_id","is_calling_key"]}}},"required":["items"]}},{"name":"revoke_api_key","description":"Revoke an API key in the caller's organization by id, permanently disabling it. A key minted by an organization admin revokes any of the org's keys; a member's key only keys that member minted. Refuses to revoke the key currently making this call — revoke that one from the Letter app (Settings → API keys: https://app.letterstory.com/settings?tab=api-keys) instead. Idempotent: revoking an already-revoked key just returns its existing revoked_at. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"api_key_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"API key UUID to revoke (from list_api_keys). Cannot be the calling key's own id."}},"required":["api_key_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"revoked":{"type":"boolean","const":true},"api_key_id":{"type":"string"},"revoked_at":{"description":"Timestamp the key was revoked (existing value if already revoked).","type":["string","null"]}},"required":["revoked","api_key_id","revoked_at"]}},{"name":"get_billing_payments","description":"The caller's organization as usage billing sees it: which billing rule covers it (standing: on a plan, on free tokens, unassigned, legacy or internal), what paying by card would start (addCard: every priced plan to pick from, Enterprise included, as letter.dev/pricing shows them (tagline, per-1,000 prices, Growth's Most teams badge), each with its expected monthly cost at the organization's usage rate and, with usage, the one cheapest for it, for an organization on free tokens, with no plan yet, or whose plan ended; an admin enters the card in Settings → Billing), what an ended plan left owing (earlierPlan: a voided first invoice that won't be charged but is settled before a new plan, failed invoices still unpaid, and the sentence the customer reads; while set, paying by card can't start a plan), the plan it is billed on (and its gifted ratio), this period's usage against the allowance, the overage (usage.overageUsd so far; usage.overageChargedUsd already charged to the card this period; overageChargeUsd the step it is charged in, a setting, not a charge) and its monthly spend cap (with who set it and how many tokens can still start under it), whether it is past due, and every payment that failed, with what happens next: when the card is charged again, how many tries and emails so far, what the card said, and (for a key minted by an organization admin) a link to pay with any card while it is unpaid. Payments are retried on days 1, 3 and 7 after a failure; a payment the bank wants confirmed is never retried, only paid through the link. Failed payments only: get_billing_invoices lists every invoice, paid ones too, and the next one. The same read as `letterstory org billing` and the staff view (`letterstory admin billing lago payments`). Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"carryOverArriving":{"description":"Free tokens the organization had left when its plan started, on their way onto the plan (a one-off wallet, drawn before overage); null when none are.","anyOf":[{"type":"object","properties":{"tokens":{"type":"number"},"waitsFor":{"type":"string","enum":["first_payment","usage_count"],"description":"first_payment: they carry over once the first invoice is paid. usage_count: paid; usually within a day, at the latest by latestBy, once the usage from before the plan started is counted."},"latestBy":{"description":"When they land at the latest (the plan's start plus three days); null while unpaid.","type":["string","null"]},"words":{"type":"string","description":"The sentence every view shows: \"5,000 free tokens arriving, usually within a day\", or past a day \"5,000 free tokens still on their way, by Nov 5, 2026 at the latest\"."}},"required":["tokens","waitsFor","words"]},{"type":"null"}]},"standing":{"description":"Which billing rule covers the organization: lago (billed by usage on a plan), free (on free tokens, no card yet), unassigned (no rule decided yet: neither on a plan nor on free tokens), legacy (a flat price, or usage compared and never billed) or internal (Letter's own, a test, or not a customer yet). null when it can't be read right now.","anyOf":[{"type":"string","enum":["lago","free","unassigned","legacy","internal"]},{"type":"null"}]},"free":{"description":"Free tokens, for an org on them (no card yet); null when the free paywall doesn't cover the org.","anyOf":[{"type":"object","properties":{"standing":{"type":"string","const":"free","description":"On free tokens: no card yet."},"mode":{"type":"string","enum":["off","shadow","enforce"],"description":"Whether the free paywall refuses work at zero (enforce), only records it (shadow), or is off."},"grantedTokens":{"type":"number","description":"Free tokens given so far: at signup and for actions (Slack, the CLI, …)."},"usedTokens":{"type":"number","description":"Used since the first grant, rated as the bill rates usage."},"heldTokens":{"type":"number","description":"Reserved by work still running."},"availableTokens":{"type":"number","description":"Granted less used less held; at or below 0 new work needs a card."},"cardRequired":{"type":"boolean","description":"No free tokens left: new work is refused (CARD_REQUIRED) until an admin adds a card in Settings → Billing. Never while signupGrant is pending."},"signupGrant":{"anyOf":[{"type":"string","enum":["granted","pending","refused"]},{"type":"null"}],"description":"A self-serve org's signup tokens: granted; pending (its first piece of work gives it pendingTokens); refused (the same person or company already had them, so new work needs a card); null when not self-serve."},"pendingTokens":{"type":"number","description":"Signup tokens a pending grant gives at the first piece of work; 0 otherwise."},"grants":{"type":"array","items":{"type":"object","properties":{"tokens":{"type":"number"},"source":{"type":"string","enum":["signup","reward","staff","bonus"]},"sourceKey":{"type":"string","description":"What it was for: signup, slack, cli, a social action, a staff note."},"reason":{"type":["string","null"]},"channel":{"anyOf":[{"type":"string","enum":["human","agent"]},{"type":"null"}],"description":"The signup grant's channel: agent when an AI agent signed the person up (25,000 on a work email), human otherwise. Null on other grants."},"createdAt":{"type":"string"}},"required":["tokens","source","sourceKey","reason","channel","createdAt"]}}},"required":["standing","mode","grantedTokens","usedTokens","heldTokens","availableTokens","cardRequired","signupGrant","pendingTokens","grants"]},{"type":"null"}]},"addCard":{"description":"What paying by card starts: a choice of every priced plan (Basic, Starter, Growth, Scale, Enterprise), Growth marked Most teams and, once the organization has usage, the plan cheapest for its last 30 days marked, for an organization on free tokens, unassigned, or whose plan ended, while the free paywall is on. An organization admin pays in Settings → Billing (a person enters the card; an agent can't). null when the organization can't pay by card itself: on a plan already, legacy or internal (its billing is set up with Letter). While earlierPlan is set, paying by card can't start a plan until that is settled.","anyOf":[{"type":"object","properties":{"planCode":{"type":"string","description":"The plan the card form starts on: the one cheapest for this usage, else the Most teams one (Growth), or the fixed one."},"planName":{"type":"string"},"monthlyUsd":{"type":"number","description":"The plan's monthly fee, in dollars."},"plans":{"type":"array","items":{"type":"object","properties":{"planCode":{"type":"string"},"planName":{"type":"string"},"tagline":{"description":"What the plan is for, as letter.dev/pricing says it.","type":["string","null"]},"extras":{"type":"array","items":{"type":"string"},"description":"What comes with it besides tokens (Slack Connect support, …)."},"badge":{"description":"A static badge: Growth's \"Most teams\".","type":["string","null"]},"monthlyUsd":{"type":"number"},"includedTokens":{"type":"number"},"usdPer1kTokens":{"type":"number","description":"The plan's own rate per 1,000 tokens."},"overageUsdPer1kTokens":{"type":"number","description":"Past the pool, per 1,000 tokens."},"overageUsdPerToken":{"type":"number"},"expectedMonthlyUsd":{"description":"The fee plus overage at this organization's usage rate; null with no usage yet.","type":["number","null"]},"savesUsdPerMonth":{"description":"Dollars a month saved against the cheapest smaller plan at that rate; null when none.","type":["number","null"]},"cheapestForYou":{"type":"boolean","description":"Cheapest for you: the lowest cost for the last 30 days, overage included. Never with no usage."}},"required":["planCode","planName","tagline","extras","badge","monthlyUsd","includedTokens","usdPer1kTokens","overageUsdPer1kTokens","overageUsdPerToken","expectedMonthlyUsd","savesUsdPerMonth","cheapestForYou"]},"description":"Every plan paying by card can start (Enterprise included), smallest first, with Growth's Most teams badge and, with usage, the one cheapest for you."},"cheapestPlanCode":{"description":"The plan with the lowest cost for the last 30 days of usage, overage included; null with no usage yet.","type":["string","null"]},"tokensPerMonth":{"description":"The usage rate read: the last 30 days of rated tokens (or since the workspace was made) projected to a month.","type":["number","null"]},"lockedBy":{"description":"Where a signup in flight on its own terms came from (a crossing, a CRM link): only its plan is offered.","type":["string","null"]}},"required":["planCode","planName","monthlyUsd","plans","cheapestPlanCode","tokensPerMonth","lockedBy"]},{"type":"null"}]},"earlierPlan":{"description":"What an ended plan left owing; while set, the organization can't start a new plan on its own. null when nothing is owed or the plan isn't ended.","anyOf":[{"type":"object","properties":{"owedCents":{"type":"number"},"voidedCents":{"type":"number","description":"A first invoice the organization's own cancel voided: never charged, but settled with Letter before a new plan can start."},"unpaidCents":{"type":"number","description":"Failed invoices from the ended plan, still payable."},"message":{"type":"string","description":"What it means, in the customer's words: the sentence Settings → Billing shows."}},"required":["owedCents","voidedCents","unpaidCents","message"]},{"type":"null"}]},"plan":{"anyOf":[{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"ratio":{"type":"number"}},"required":["code","name","ratio"]},{"type":"null"}],"description":"The plan the org is billed on by usage; null when it is not."},"status":{"anyOf":[{"type":"string","enum":["active","shadow","ended"]},{"type":"null"}],"description":"active: billed. shadow: usage is compared, never billed. ended. null: not billed by usage."},"pastDueAt":{"description":"Set when retries gave up on an invoice; cleared once it is paid.","type":["string","null"]},"canceledAt":{"description":"When the plan was cancelled (status ended); null otherwise.","type":["string","null"]},"endsAt":{"description":"A plan cancelled to end at its period's end (still active and billed until then): when it ends.","type":["string","null"]},"overageChargeUsd":{"description":"A setting, not a charge: the step overage is charged to the card in, each time it builds up by this many dollars ($250), the rest with the next invoice. What was actually charged this period is usage.overageChargedUsd; the overage so far is usage.overageUsd. null: overage is billed with the next invoice (or the account is not billed by usage).","type":["number","null"]},"spendCap":{"anyOf":[{"type":"object","properties":{"usd":{"type":"number"},"setBy":{"description":"Who set the cap: an admin's name, or \"Letter\" when our team set it.","type":["string","null"]},"setAt":{"type":["string","null"]},"enforced":{"type":"boolean","description":"Whether reaching the cap stops new work yet; until then nothing stops at it and no cap alerts are sent."}},"required":["usd","setBy","setAt","enforced"]},{"type":"null"}],"description":"The account's own monthly cap on overage dollars and who set it; null is no cap (overage on)."},"backgroundPause":{"description":"Set while the account's background work (scheduled GEO probes, seer polls, competitor scrapes, sequence sends, prints and automations) is paused by its spend cap, a payment that didn't go through, or free tokens used up; it resumes by itself once that is fixed. null while it runs.","anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["card_required","payment_failed","spend_cap"],"description":"card_required: free tokens used, no card. payment_failed: an invoice failed or is unpaid, or the account is past due. spend_cap: at the account's own monthly cap."},"message":{"type":"string","description":"What is paused, why, and what an admin does about it, in one sentence."},"billingUrl":{"type":"string","description":"Settings → Billing, where an admin adds a card, pays or raises the cap."}},"required":["reason","message","billingUrl"]},{"type":"null"}]},"usage":{"anyOf":[{"type":"object","properties":{"periodFrom":{"type":"string"},"periodTo":{"type":"string"},"allowanceTokens":{"type":"number","description":"Tokens the plan includes this period."},"complimentaryTokens":{"type":"number","description":"Free tokens past the allowance."},"usedTokens":{"type":"number","description":"Billed this period so far (Lago's count and what is on its way), as get_billing's tokens.used."},"runningTokens":{"type":"number","description":"Held by work still running: not billed, but counted against the spend cap."},"overageUsdPerToken":{"type":"number"},"overageUsd":{"type":"number","description":"Overage so far on billed usage, after complimentary tokens; the same as get_billing's usd.overage."},"runningOverageUsd":{"type":"number","description":"What running work adds to the overage if it all lands: counted against the cap, not billed yet."},"overageChargedUsd":{"description":"Of the overage so far (overageUsd), what is already charged to the card this period, in steps of overageChargeUsd; 0 until the overage first reaches a step. null when it can't be read right now.","type":["number","null"]},"availableTokens":{"description":"Tokens that can still start before the spend cap; null with no cap.","type":["number","null"]},"capShare":{"description":"Overage with running work's, as a share of the cap (1 is at the cap); null with no cap.","type":["number","null"]}},"required":["periodFrom","periodTo","allowanceTokens","complimentaryTokens","usedTokens","runningTokens","overageUsdPerToken","overageUsd","runningOverageUsd","overageChargedUsd","availableTokens","capShare"]},{"type":"null"}],"description":"This billing period, for an account billed by usage; null otherwise."},"payments":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"invoiceId":{"type":"string","description":"The invoice: its number, as in get_billing_invoices (an opaque id when it has none yet)."},"invoiceNumber":{"type":["string","null"]},"invoiceType":{"description":"subscription (the plan), credit (a token purchase), …","type":["string","null"]},"amountCents":{"type":"number"},"currency":{"type":"string"},"status":{"type":"string","enum":["failing","requires_action","paid","voided","given_up","written_off","canceled"],"description":"failing: charged again on a schedule. requires_action: the bank wants the customer to confirm it. given_up: retries stopped; still payable. paid / voided: settled. written_off: an earlier plan's, closed when a new plan started and never charged to the new card. canceled: the plan was cancelled in its first month, so this first invoice is being cancelled (or its fee refunded if it was paid): nothing to pay, never charged again."},"firstFailedAt":{"type":"string"},"chargeFailedAt":{"description":"When the card was first declined, as Lago recorded it.","type":["string","null"]},"lastFailedAt":{"type":"string"},"lastError":{"description":"What the card said, in the words the customer is emailed; null when it said nothing a person can act on.","type":["string","null"]},"attempts":{"type":"number","description":"Retries so far."},"lastAttemptAt":{"type":["string","null"]},"nextAttemptAt":{"description":"When the card is charged again; null when it won't be.","type":["string","null"]},"emailsSent":{"type":"number"},"resolvedAt":{"type":["string","null"]},"payUrl":{"description":"Pay this invoice with any card, while it is unpaid; null unless the caller is an organization admin (a key an admin minted). It opens a Stripe page; once paid, the payer comes back to Letter (/pay/card?paid=1), where they can make that card the one charged from now on. Landing anywhere else (stripe.com) is a misconfiguration on Letter's side, not normal: the payment still went through, and Letter staff are told.","type":["string","null"]}},"required":["orgId","invoiceId","invoiceNumber","invoiceType","amountCents","currency","status","firstFailedAt","chargeFailedAt","lastFailedAt","lastError","attempts","lastAttemptAt","nextAttemptAt","emailsSent","resolvedAt","payUrl"]}}},"required":["plan","status","pastDueAt","canceledAt","endsAt","overageChargeUsd","spendCap","usage","payments"]}},{"name":"list_token_rewards","description":"The free tokens the caller's organization can earn, once each, in priority order, most tokens first: the Letter script on the organization's website (`site-script`: seen by its ping and found on the homepage, once per site host; visitors_get_script, visitors_verify_site), connecting Slack, connecting GitHub or GitLab, signing in with the Letter CLI (`letterstory login`), connecting Search Console, following on LinkedIn, subscribing on YouTube, sharing Letter on X, starring the GitHub repo, and the tours (`listed: false`, so no earn list shows them): one quick task in each view, paid the moment it's done by a person's own call, never for opening a view: `tour-build` the first answer to send_build_message, `tour-plan` the first plan for a goal (propose_account_plan with `goal`), `tour-fix` the first fix that goes through (fix_site_observations: a pull request started, or `handoff`'s prompt), `tour-site` the first page read (read_site_page). Each tour's `task` says what to do. Only a workspace on free tokens with its own signup tokens can earn them (eligible), and only while Letter's free tokens are on (off, nothing is eligible): a workspace on a plan is refused, and so is a second workspace from the same person or company, and one an AI agent signed up (its signup tokens are all its free tokens). Each with its tokens, whether it's claimed, whether it's checked (Slack, the CLI, GitHub, GitLab, the script and Search Console against Letter's own records, the tours by the task itself, a star on GitHub when a GitHub account is connected; the rest on the honour system), and for the six connections whether it's done. Those six and the tours are `auto`: granted the moment they're done, nothing to claim; `connect` names the integration whose connect flow earns each, and `group: code-host` marks GitHub and GitLab as one choice (Set-up offers the other only until one is claimed or done). What's left to earn (availableTokens) is every listed reward not yet claimed, so the tours aren't counted; what's earned (claimedTokens) is what the grants paid. `progress` counts every reward once, the tours included and GitHub or GitLab as one choice (earned, total, left, done of count): Set-up's quest board. Claim the social actions and the star with claim_token_reward. The same read as Settings → Billing's Earn free tokens and `letterstory org billing rewards`. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"eligible":{"type":"boolean","description":"Whether this workspace can earn them: only one on free tokens. A workspace on a plan (or legacy, or internal) is refused."},"rewards":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string","description":"site-script, slack, github-connect, gitlab-connect, cli, gsc-connect, linkedin-follow, youtube-subscribe, x-post, github-star, tour-build, tour-plan, tour-fix or tour-site (in priority order: most tokens first)."},"title":{"type":"string"},"detail":{"type":"string"},"tokens":{"type":"number","description":"Free tokens the action gives, once per organization. On a claimed one, what its grant paid (an amount changed since doesn't rewrite it)."},"claimed":{"type":"boolean"},"claimedAt":{"type":["string","null"]},"verifiable":{"type":"boolean","description":"True when the claim is checked (our data, the task itself for a tour, or GitHub for a star); false on the honour system."},"ready":{"description":"For slack, cli, github-connect, gitlab-connect, site-script and gsc-connect: whether it's done (its tokens are granted on their own). Null for the rest.","type":["boolean","null"]},"paidElsewhere":{"type":"boolean","description":"Done but unclaimed, and what it was earned with (the Slack workspace, the site, the GitHub account, …) already paid in another workspace: it never pays here. False for the rest."},"auto":{"type":"boolean","description":"True: granted automatically once done (checked against Letter's records, or a tour's task done), never claimed. False: claim it with claim_token_reward once done (the honour system, or a GitHub star)."},"listed":{"type":"boolean","description":"Whether the app shows it in an earn list. False for the tours (tour-build, tour-plan, tour-fix, tour-site): each is paid when its quick task is done (see `task`), never claimed and never for opening a view."},"connect":{"anyOf":[{"type":"string","enum":["slack","github","gitlab","gsc"]},{"type":"null"}],"description":"The integration whose connect flow earns it (slack, github, gitlab or gsc for Search Console): an earn list's button starts that flow. Null for the rest."},"group":{"anyOf":[{"type":"string","enum":["code-host"]},{"type":"null"}],"description":"The choice it is one of: code-host for github-connect and gitlab-connect (a site's code is on one), so Set-up stops offering the other once one is claimed or done. Both stay earnable. Null for the rest."},"url":{"description":"Where the action is done: an outside link, or a path in the app (Settings → Integrations).","type":["string","null"]},"cta":{"type":"string"},"task":{"description":"For a tour: what to do and where, in one line (the app's hint says it). Done by a person's own call (send_build_message, propose_account_plan with a goal, fix_site_observations, read_site_page), it pays at once. Null for the rest.","type":["string","null"]}},"required":["action","title","detail","tokens","claimed","claimedAt","verifiable","ready","paidElsewhere","auto","listed","connect","group","url","cta","task"]}},"claimedTokens":{"type":"number","description":"Tokens the organization's reward grants paid, each what it paid when it was granted."},"availableTokens":{"type":"number","description":"Tokens still to earn from the listed rewards not yet claimed: the rows an earn list shows. The tours (listed: false) aren't counted."},"progress":{"type":"object","properties":{"earnedTokens":{"type":"number","description":"What the organization's reward grants paid (as claimedTokens)."},"totalTokens":{"type":"number","description":"Every reward once, the tours included, as this organization can earn them: earned plus still to earn. GitHub or GitLab (group code-host) is one choice."},"leftTokens":{"type":"number","description":"Still to earn: totalTokens less earnedTokens."},"done":{"type":"number","description":"Rewards done, the code-host choice counted once."},"count":{"type":"number","description":"Every reward, the code-host choice counted once, the tours included."}},"required":["earnedTokens","totalTokens","leftTokens","done","count"],"description":"How far the organization is through its free tokens, the tours included: what Set-up's quest board shows (\"8,000 of 20,000 tokens · 4 of 13 done\"). All zeros when the organization can't earn (eligible: false). A reward done whose grant hasn't landed yet counts as still to earn; one already paid in another workspace (paidElsewhere) isn't counted."}},"required":["eligible","rewards","claimedTokens","availableTokens","progress"]}},{"name":"claim_token_reward","description":"Claim the free tokens for one action, once per organization (see list_token_rewards). `slack`, `cli`, `github-connect`, `gitlab-connect`, `site-script` and `gsc-connect` are granted on their own when done; claiming one only pays it sooner if it's done and wasn't paid yet. `slack` needs Slack connected, `cli` the Letter CLI signed in to this organization (`letterstory login` or `login --device`; an agent that just signed in can claim `cli` itself), `github-connect` GitHub, `gitlab-connect` GitLab, `site-script` the Letter script seen and found on a site's homepage (visitors_verify_site; once per site host), and `gsc-connect` Search Console (once per Google account). `github-star` is checked on GitHub when a GitHub account is connected. The social actions are on the honour system: claim once the person has followed, subscribed or posted. The tours (`tour-build`, `tour-plan`, `tour-fix`, `tour-site`) can't be claimed: each is paid when its task is done (see list_token_rewards' `task`), so claiming one not yet done is refused (409, \"Do it in the app: …\") and one already paid answers granted: false. Claiming again gives nothing more (granted: false). While Letter's free tokens are off every claim is refused (409); a connection made then is paid once they're on. A workspace an AI agent signed up earns none: its signup tokens are all its free tokens, so every claim there is refused (409). The same operation as Settings → Billing and `letterstory org billing rewards claim <action>`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["site-script","slack","github-connect","gitlab-connect","cli","gsc-connect","linkedin-follow","youtube-subscribe","x-post","github-star","tour-build","tour-plan","tour-fix","tour-site"],"description":"slack (Slack connected), cli (the Letter CLI signed in), github-connect (GitHub connected), gitlab-connect (GitLab connected), site-script (the Letter script seen and found on the site's homepage), gsc-connect (Search Console connected): granted on their own once done. linkedin-follow, youtube-subscribe, x-post, github-star. tour-build, tour-plan, tour-fix, tour-site: paid when the task is done (a question answered in Build, a goal's plan proposed, a fix started or its prompt copied, a page read as AI reads it), never claimed: a claim for one not yet done is refused."}},"required":["action"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"action":{"type":"string"},"tokens":{"type":"number"},"granted":{"type":"boolean","description":"False when the organization had already claimed it: nothing more was given."},"verified":{"type":"boolean","description":"True when checked (Slack connected, CLI signed in, star seen on GitHub)."}},"required":["ok","action","tokens","granted","verified"]}},{"name":"get_billing_invoices","description":"What the caller's organization was charged and what it will be charged next. Every invoice, newest first: what it is for (the plan's subscription invoice, a one-off charge, a $250 overage top-up), its number, amount, status (paid, open, failed, voided, refunded or partly refunded, with the credit notes), issue and due dates, when the card was charged, and the PDF. And the next invoice: the day it issues, the next period's plan fee in advance, the overage so far, what of it was already charged as top-ups, and the total if nothing more is used. Only for an account billed by usage; an account never billed sees no amounts. The same read as Settings → Billing and `letterstory org billing invoices`; get_billing has the usage by product and collection, get_billing_payments the spend cap and failed payments. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgId":{"type":"string"},"status":{"anyOf":[{"type":"string","enum":["active","shadow","ended"]},{"type":"null"}],"description":"active: billed by usage. ended: was billed (its invoices stay). shadow or null: never billed."},"billed":{"type":"boolean","description":"False for an account never billed on usage: no invoices, no amounts."},"invoices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"kind":{"type":"string","enum":["subscription","one_off","overage_top_up","overage","other"],"description":"subscription: the plan's fee in advance and the past period's usage. one_off: a one-off charge. overage_top_up: a $250 overage charge taken as overage is used. overage: a usage threshold charge."},"status":{"type":"string","enum":["paid","open","failed","voided","refunded","partly_refunded","refund_pending","refund_failed","draft"],"description":"paid; open (issued, not charged yet); failed (the card was declined: see get_billing_payments); voided (cancelled, nothing owed); refunded / partly_refunded (by credit note, paid back to the card); refund_pending (a refund is on its way to the card, not there yet); refund_failed (the card's bank refused the refund: Letter is told and sends it another way); draft (not issued yet)."},"currency":{"type":"string"},"amountUsd":{"type":"number","description":"The invoice's total."},"paidUsd":{"type":"number"},"dueUsd":{"type":"number","description":"Still to pay."},"prepaidUsd":{"type":"number","description":"Paid by credits already bought (overage top-ups)."},"refundedUsd":{"type":"number","description":"Refunded to the card, once the refund has gone through."},"issuedOn":{"description":"YYYY-MM-DD.","type":["string","null"]},"dueOn":{"description":"YYYY-MM-DD.","type":["string","null"]},"paidAt":{"description":"When the card was charged, for a paid invoice.","type":["string","null"]},"pdfUrl":{"description":"The invoice as a PDF.","type":["string","null"]},"creditNotes":{"type":"array","items":{"type":"object","properties":{"number":{"type":"string"},"reason":{"type":["string","null"]},"refundedUsd":{"type":"number"},"creditedUsd":{"type":"number"},"refundStatus":{"type":["string","null"]},"createdAt":{"type":"string"},"pdfUrl":{"type":["string","null"]}},"required":["number","reason","refundedUsd","creditedUsd","refundStatus","createdAt","pdfUrl"]}}},"required":["id","number","kind","status","currency","amountUsd","paidUsd","dueUsd","prepaidUsd","refundedUsd","issuedOn","dueOn","paidAt","pdfUrl","creditNotes"]},"description":"Newest first, at most 36."},"upcoming":{"anyOf":[{"type":"object","properties":{"issuesOn":{"type":"string","description":"The day the next invoice is issued and charged (YYYY-MM-DD)."},"periodFrom":{"type":"string"},"periodTo":{"type":"string"},"planFeeUsd":{"type":"number","description":"The next period's plan fee, charged in advance on that invoice; 0 when it is the last (final)."},"overageUsd":{"type":"number","description":"This period's overage so far, as the billing system has counted it."},"overagePrepaidUsd":{"type":"number","description":"Of that, already charged as $250 overage top-ups this period."},"totalUsd":{"type":"number","description":"What the next invoice comes to if nothing more is used: the plan fee plus the overage not yet charged."},"final":{"type":"boolean","description":"The plan is cancelled to end at periodTo: this is its last invoice, the period's overage only, with no plan fee."}},"required":["issuesOn","periodFrom","periodTo","planFeeUsd","overageUsd","overagePrepaidUsd","totalUsd","final"]},{"type":"null"}],"description":"The invoice the plan issues next; null for an account not billed now, or when it can't be read."}},"required":["orgId","status","billed","invoices","upcoming"]}},{"name":"get_billing","description":"The caller's organization's usage this billing period, by product (articles, kernel writes, Build & Fix agent runs, site days, …) and by collection: each product's quantity, and for an account billed by usage its tokens (what Lago has, plus usage harvested and on its way to the bill), the tokens running work holds, the plan's allowance and complimentary tokens, and the dollars so far (the plan fee and the overage). An account not billed by usage (the trial run, a flat price) sees quantities only. Never our cost. `plans` lists every plan at list prices (monthly fee, included tokens, per-token and overage rates), smallest first, then Custom (contact: true, priced on a call); `plan.code` marks the one this account is on. Usage and cost so far, not what was charged: get_billing_invoices has every invoice (the plan fee already charged, one-offs, overage top-ups, refunds) and the next invoice (when it issues and what it comes to). The same read as Settings → Billing and `letterstory org billing usage`; get_billing_payments has the spend cap and failed payments. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgId":{"type":"string"},"free":{"description":"Free tokens, for an org on them (no card yet); null when the free paywall doesn't cover the org.","anyOf":[{"type":"object","properties":{"standing":{"type":"string","const":"free","description":"On free tokens: no card yet."},"mode":{"type":"string","enum":["off","shadow","enforce"],"description":"Whether the free paywall refuses work at zero (enforce), only records it (shadow), or is off."},"grantedTokens":{"type":"number","description":"Free tokens given so far: at signup and for actions (Slack, the CLI, …)."},"usedTokens":{"type":"number","description":"Used since the first grant, rated as the bill rates usage."},"heldTokens":{"type":"number","description":"Reserved by work still running."},"availableTokens":{"type":"number","description":"Granted less used less held; at or below 0 new work needs a card."},"cardRequired":{"type":"boolean","description":"No free tokens left: new work is refused (CARD_REQUIRED) until an admin adds a card in Settings → Billing. Never while signupGrant is pending."},"signupGrant":{"anyOf":[{"type":"string","enum":["granted","pending","refused"]},{"type":"null"}],"description":"A self-serve org's signup tokens: granted; pending (its first piece of work gives it pendingTokens); refused (the same person or company already had them, so new work needs a card); null when not self-serve."},"pendingTokens":{"type":"number","description":"Signup tokens a pending grant gives at the first piece of work; 0 otherwise."},"grants":{"type":"array","items":{"type":"object","properties":{"tokens":{"type":"number"},"source":{"type":"string","enum":["signup","reward","staff","bonus"]},"sourceKey":{"type":"string","description":"What it was for: signup, slack, cli, a social action, a staff note."},"reason":{"type":["string","null"]},"channel":{"anyOf":[{"type":"string","enum":["human","agent"]},{"type":"null"}],"description":"The signup grant's channel: agent when an AI agent signed the person up (25,000 on a work email), human otherwise. Null on other grants."},"createdAt":{"type":"string"}},"required":["tokens","source","sourceKey","reason","channel","createdAt"]}}},"required":["standing","mode","grantedTokens","usedTokens","heldTokens","availableTokens","cardRequired","signupGrant","pendingTokens","grants"]},{"type":"null"}]},"carryOverArriving":{"description":"Free tokens the organization had left when its plan started, on their way onto the plan (a one-off wallet, drawn before overage); null when none are.","anyOf":[{"type":"object","properties":{"tokens":{"type":"number"},"waitsFor":{"type":"string","enum":["first_payment","usage_count"],"description":"first_payment: they carry over once the first invoice is paid. usage_count: paid; usually within a day, at the latest by latestBy, once the usage from before the plan started is counted."},"latestBy":{"description":"When they land at the latest (the plan's start plus three days); null while unpaid.","type":["string","null"]},"words":{"type":"string","description":"The sentence every view shows: \"5,000 free tokens arriving, usually within a day\", or past a day \"5,000 free tokens still on their way, by Nov 5, 2026 at the latest\"."}},"required":["tokens","waitsFor","words"]},{"type":"null"}]},"status":{"anyOf":[{"type":"string","enum":["active","shadow","ended"]},{"type":"null"}],"description":"active: billed by usage. shadow: compared, never billed. ended. null: not on usage billing."},"plan":{"anyOf":[{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"ratio":{"type":"number"}},"required":["code","name","ratio"]},{"type":"null"}]},"period":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"basis":{"type":"string","enum":["lago","ours","ours_split","anniversary","month"],"description":"lago: the billing period as Lago bills it. ours: the same period, rated by us because Lago could not be read. ours_split: the period as Lago bills it, split by product and collection by our own rating because Lago gave no breakdown yet. anniversary / month: an account not billed, read over its monthly anniversary or the calendar month."}},"required":["from","to","basis"],"description":"The period read; quantities cover its UTC days so far."},"tokens":{"anyOf":[{"type":"object","properties":{"used":{"type":"number","description":"This period so far: on the bill and on its way. The rows plus `arriving`; holds are not in it."},"arriving":{"type":"number","description":"Sent to billing and not in its count yet (usually seconds): in used, in no product or collection row."},"running":{"type":"number","description":"Held by work started and not yet finished: not billed, not in used or any row."},"allowance":{"type":"number","description":"Tokens the plan includes this period."},"complimentary":{"type":"number","description":"Free tokens past the allowance."}},"required":["used","arriving","running","allowance","complimentary"]},{"type":"null"}],"description":"Null for an account not billed by usage: it sees quantities only."},"usd":{"anyOf":[{"type":"object","properties":{"planFee":{"type":"number","description":"The plan's monthly fee, charged in advance."},"overage":{"type":"number","description":"Overage so far this period, after complimentary tokens."},"overagePerToken":{"type":"number","description":"Dollars per token past the allowance."}},"required":["planFee","overage","overagePerToken"]},{"type":"null"}],"description":"Dollars, only for an account billed by usage."},"byProduct":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","description":"The product: article, write, shred, agent_run, phantom_site_day, …"},"label":{"type":"string","description":"The product as the Billing page names it."},"unit":{"type":"string","description":"What one unit of quantity is."},"category":{"description":"Billing category key (writing, agents, sites, …).","type":["string","null"]},"categoryLabel":{"type":["string","null"]},"quantity":{"type":"number","description":"Everything recorded this period, billable or not."},"billableQuantity":{"type":"number","description":"What the billing rule counts: finished work, not platform failures."},"tokens":{"description":"Tokens on the bill (Lago's, plus usage on its way); null for an account not billed by usage.","type":["number","null"]}},"required":["sku","label","unit","category","categoryLabel","quantity","billableQuantity","tokens"]},"description":"Every product used this period, most tokens first."},"byCollection":{"type":"array","items":{"type":"object","properties":{"collectionId":{"description":"Null: work in no collection.","type":["string","null"]},"name":{"type":"string"},"tokens":{"type":["number","null"]},"products":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","description":"The product: article, write, shred, agent_run, phantom_site_day, …"},"label":{"type":"string","description":"The product as the Billing page names it."},"unit":{"type":"string","description":"What one unit of quantity is."},"category":{"description":"Billing category key (writing, agents, sites, …).","type":["string","null"]},"categoryLabel":{"type":["string","null"]},"quantity":{"type":"number","description":"Everything recorded this period, billable or not."},"billableQuantity":{"type":"number","description":"What the billing rule counts: finished work, not platform failures."},"tokens":{"description":"Tokens on the bill (Lago's, plus usage on its way); null for an account not billed by usage.","type":["number","null"]}},"required":["sku","label","unit","category","categoryLabel","quantity","billableQuantity","tokens"]}}},"required":["collectionId","name","tokens","products"]},"description":"The same usage per collection (project), most tokens first."},"plans":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"monthlyUsd":{"description":"The monthly fee, charged in advance. Null for Custom.","type":["number","null"]},"includedTokens":{"description":"Tokens the plan includes each month. Null for Custom.","type":["number","null"]},"usdPerToken":{"description":"The plan's own rate: its fee over its included tokens.","type":["number","null"]},"overageUsdPerToken":{"description":"Dollars per token past the included tokens: 5x the plan's rate.","type":["number","null"]},"contact":{"type":"boolean","description":"True for Custom: priced on a call with us, not picked from the list."},"tagline":{"description":"What the plan is for, as letter.dev/pricing says it.","type":["string","null"]},"extras":{"description":"What comes with it besides tokens.","type":"array","items":{"type":"string"}},"badge":{"description":"A static badge: Growth's \"Most teams\".","type":["string","null"]},"usdPer1kTokens":{"description":"The plan's own rate per 1,000 tokens.","type":["number","null"]},"overageUsdPer1kTokens":{"description":"Past the pool, per 1,000 tokens.","type":["number","null"]}},"required":["code","name","monthlyUsd","includedTokens","usdPerToken","overageUsdPerToken","contact"]},"description":"Every plan at list prices, smallest first, then Custom. Pricing is usage-based: the fee includes tokens, and overage is billed as it's used. `plan.code` is the one this account is on."}},"required":["orgId","status","plan","period","tokens","usd","byProduct","byCollection","plans"]}},{"name":"start_signup","description":"Sign your user up for Letter, with no API key: give their email and Letter emails them a link. They confirm in a browser (checking the code you show them, setting a password); the account and organization are made then, and you get a token for it. A new company workspace an agent signs up on a work email gets up to 25,000 free tokens; a free-mail address (Gmail, Outlook, iCloud, …) gets 5,000; no card needed; a person adds a card only once they run out, and an agent never can. Free tokens are once per person or company: start says \"up to\" the same for everyone, and finish_signup's answer says what the new account got (0 when the company already had its free tokens). If the person already has an account, the link has them sign in and connect you instead. Show them user_code, then call finish_signup with device_code every interval seconds (or poll token_endpoint per RFC 8628) until it returns your access token. Limited per IP and per email (rate_limited). The same operation as `letterstory signup --email`. No API key needed.","capability":"account:write","inputSchema":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The work email of the person to sign up. They get an email from Letter and confirm in a browser; nothing is created until they do."},"agent":{"description":"Your name, shown to the person on the confirmation page (e.g. \"Claude Code\"): up to 40 letters, digits, spaces and . _ -, no links.","type":"string","minLength":1,"maxLength":40,"pattern":"^[A-Za-z0-9 ._-]+$"}},"required":["email"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","const":"email_sent"},"email":{"type":"string","description":"Where the confirmation email went."},"user_code":{"type":"string","description":"Show it to the person: the confirmation page shows the same one."},"device_code":{"type":"string","description":"Pass to finish_signup, or poll token_endpoint with it (RFC 8628). Keep it to yourself."},"interval":{"type":"number","description":"Seconds between polls."},"expires_in":{"type":"number","description":"Seconds until the link and the device_code expire."},"token_endpoint":{"type":"string"},"grant_type":{"type":"string"},"client_id":{"type":"string"},"free_tokens":{"type":"object","properties":{"up_to":{"type":"number"},"note":{"type":"string"}},"required":["up_to","note"],"description":"Up to 25,000 free tokens for a new company workspace. The same answer for everyone: the real amount is on the confirmation page and in finish_signup's answer."},"next":{"type":"string","description":"What to tell the person and do next."}},"required":["status","email","user_code","device_code","interval","expires_in","token_endpoint","grant_type","client_id","free_tokens","next"]}},{"name":"finish_signup","description":"Collect your token once the person confirms a signup you started with start_signup: pass its device_code. Returns status pending until they confirm (call again after the interval), then complete with an access_token for their new organization (send it as `Authorization: Bearer`) and a refresh_token, once. expired: start again; cancelled: they declined. The same exchange as POST /api/oauth/token with the device_code grant, for an agent that only speaks MCP. No API key needed.","capability":"account:write","inputSchema":{"type":"object","properties":{"device_code":{"type":"string","minLength":1,"description":"The device_code start_signup returned. Call every `interval` seconds until status is not pending."}},"required":["device_code"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"status":{"type":"string","enum":["pending","expired","cancelled"]},"message":{"type":"string"}},"required":["status","message"]},{"type":"object","properties":{"status":{"type":"string","const":"complete"},"access_token":{"type":"string","description":"Send as `Authorization: Bearer <token>` to /api/mcp and the REST API."},"refresh_token":{"description":"Exchange at /api/oauth/token (grant_type refresh_token).","type":["string","null"]},"token_type":{"type":"string","const":"Bearer"},"expires_in":{"type":"number"},"scope":{"type":"string"},"free_tokens":{"type":"object","properties":{"tokens":{"type":"number"},"note":{"type":["string","null"]}},"required":["tokens","note"],"description":"What the new account got: 25,000, or 0 when the company already had its free tokens."}},"required":["status","access_token","refresh_token","token_type","expires_in","scope","free_tokens"]}],"type":"object"}},{"name":"set_billing_spend_cap","description":"Set, raise or remove the caller's organization's monthly spend cap: the dollars of overage (usage past the plan's allowance) it allows each billing period. Overage is on by default with no cap. Once a period's overage would pass the cap, new work (posts, runs, new sites, Build changes) is refused with payment_required until the next period or a higher cap; work already running finishes. The cap stops new work, not what is already running: live sites' days, probes, tracked competitors, mailboxes and emails keep running and are billed, so a period can end a little past the cap (pause a site to stop its days). 0 stops new work at the allowance (after any complimentary tokens); null removes the cap. Alerts fire at each 25% of the cap, counted past any complimentary tokens. Needs a key minted by an organization admin; recorded with who set it. The answer's `standing` is the period as the new cap leaves it: a cap at or under the overage already used refuses new work at once (`newWorkRefused`), so tell the person before or right after setting it (`note` says it in a sentence). The same operation as Settings → Billing and `letterstory org billing cap`. Requires capability billing:manage.","capability":"billing:manage","inputSchema":{"type":"object","properties":{"cap_usd":{"anyOf":[{"type":"number","minimum":0,"maximum":1000000},{"type":"null"}],"description":"Dollars of overage a month the account allows: work past it is refused until next period. 0 stops at the plan's allowance; null removes the cap (overage on, the default)."},"reason":{"description":"Why, kept with the cap.","type":"string","maxLength":500}},"required":["cap_usd"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"spendCap":{"anyOf":[{"type":"object","properties":{"usd":{"type":"number"},"setBy":{"type":"string"},"setAt":{"type":"string"}},"required":["usd","setBy","setAt"]},{"type":"null"}]},"alert":{"type":"string","enum":["created","updated","removed","none","failed"],"description":"Whether the alerts at each 25% of the cap moved with it; the cap holds either way."},"standing":{"anyOf":[{"type":"object","properties":{"overageUsd":{"type":"number","description":"Overage so far this period, in dollars, after complimentary tokens."},"capShare":{"description":"Overage with running work's, as a share of the cap; 1 is at the cap.","type":["number","null"]},"availableTokens":{"description":"Tokens that can still start before the cap.","type":["number","null"]},"newWorkRefused":{"type":"boolean","description":"The cap is at or under the overage used: new work is refused now (payment_required)."},"enforced":{"type":"boolean","description":"Whether the cap stops work yet; in shadow nothing is refused."},"note":{"type":"string","description":"The standing in one sentence, to tell the person as is."}},"required":["overageUsd","capShare","availableTokens","newWorkRefused","enforced","note"]},{"type":"null"}],"description":"The period as the new cap leaves it; null when it can't be read right now."}},"required":["spendCap","alert","standing"]}},{"name":"request_billing_tokens","description":"Ask the caller's organization's admins for more usage when its monthly spend cap refused work (payment_required): emails them, with an optional note, once a day per member. The same as Request more in the app and `letterstory org billing request`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"note":{"description":"What it's for, for the admins.","type":"string","maxLength":1000}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"emailedTo":{"type":"number","description":"How many of the account's admins were emailed."}},"required":["emailedTo"]}},{"name":"request_mobile_app_access","description":"Ask for access to Letter's mobile app, which is coming soon: emails the Letter team the caller's organization and who asked (the key's minter), so they follow up when there is a build to try. Nothing else is stored and no tokens are spent or granted. `emailed` is false when the email couldn't go out; the request still stands. The same as Request access on the Mobile app card under Set-up. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"emailed":{"type":"boolean","description":"Whether the team's email went out. The request stands either way; nothing else is stored."}},"required":["ok","emailed"]}},{"name":"cancel_billing_plan","description":"Cancel the caller's organization's usage-billed plan. In the plan's first month (its first billing period), the first time this organization cancels: the plan ends now and its plan fee is refunded to the card (or its first invoice voided if it was never paid). The refund is once per organization, ever, used only when money goes back to the card: a voided first invoice or a refund of $0 leaves it for a later plan. If it had it before, the plan ends at the end of the current billing period instead, with no refund (ends: period_end). Either way the usage so far is billed, and overage already charged ($250 at a time) is never refunded. Only in the plan's first month, and only with a key minted by an organization admin; after that, contact Letter. `message` says what happened, in the customer's words. The same as Cancel plan in Settings → Billing and the cancel button at the end of a crossing. Requires capability billing:manage.","capability":"billing:manage","inputSchema":{"type":"object","properties":{"reason":{"description":"Why, kept with the account.","type":"string","maxLength":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"ends":{"type":"string","enum":["now","period_end"],"description":"now: the plan has ended. period_end: it ends at endedAt, billed as usual until then."},"refundedCents":{"type":"number","description":"The plan fee refunded to the card, in cents (0 when nothing was)."},"firstInvoice":{"type":"string","enum":["refunded","voided","pending","none"],"description":"The plan's first invoice: its fee refunded (it was paid), voided (it wasn't), pending (unpaid with a payment page still open: voided within a day, or its fee refunded if it is paid first), or none."},"noRefund":{"anyOf":[{"type":"string","enum":["already_refunded","past_first_month"]},{"type":"null"}],"description":"Why nothing was refunded: the organization had its one first-month refund before, or it is past the first month."},"endedAt":{"type":"string","description":"When the plan ended, or will."},"message":{"type":"string","description":"What happened, in the customer's words."}},"required":["ok","ends","refundedCents","firstInvoice","noRefund","endedAt","message"]}},{"name":"undo_billing_plan_cancel","description":"Take back the caller's organization's cancel of its usage-billed plan, when the cancel was set to end the plan at the end of its billing period (cancel_billing_plan's ends: period_end; get_billing_payments shows `endsAt`): the plan renews on that day and is billed as before. Only before the end comes; a plan that has already ended can't be taken back (adding a card starts a new one). Only with a key minted by an organization admin. `message` says what happened, in the customer's words. The same as Keep my plan in Settings → Billing and `letterstory org billing keep`. Requires capability billing:manage.","capability":"billing:manage","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"renewsAt":{"type":"string","description":"When the current period ends and the plan renews, billed as before."},"message":{"type":"string","description":"What happened, in the customer's words."}},"required":["ok","renewsAt","message"]}},{"name":"update_billing_card","description":"Change the card the caller's organization is charged on: returns a Stripe page (valid for a day) where a person enters the new card. Once saved it is the card every charge goes to (the plan, its overage), and every plan invoice that failed is charged to it right away, once. Paying a failed invoice through its pay link (get_billing_payments) settles that invoice only and leaves the old card in place; this replaces it. Only for an account billed by usage, and only with a key minted by an organization admin. The same as Update card in Settings → Billing and `letterstory org billing card`. Requires capability billing:manage.","capability":"billing:manage","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"url":{"type":"string","description":"Stripe's page where a person enters the new card; hand it to them."},"expiresAt":{"description":"When the page stops working (about a day); ask again for a new one.","type":["string","null"]}},"required":["url","expiresAt"]}},{"name":"list_integrations","description":"Every integration the organization can connect (GitHub, GitLab, Vercel, Sanity, Contentful, Webflow, Framer, Google Docs, Google Search Console, AirOps, Slack, Linear, Daymaker, Your apps) and whether it is connected, with the account, site or workspace it's connected to — the same list Settings → Integrations shows. Site scripts are not integrations: whether Visitors or Letterprove is installed is read with their own tools. Connecting is a sign-in in the browser a person does (most are OAuth): hand them each one's `connect_url`. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"integrations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"group":{"type":"string","enum":["code","cms","search","team","more"]},"purpose":{"type":"string"},"connected":{"type":"boolean"},"detail":{"description":"Which account, site or workspace it's connected to.","type":["string","null"]},"connectedAt":{"type":["string","null"]},"connect_url":{"type":"string","description":"Where a person connects (or changes) it: a browser sign-in, not something an agent can do."}},"required":["id","name","group","purpose","connected","detail","connectedAt","connect_url"]},"description":"Every integration, in the order Settings lists them."},"connected":{"type":"number","description":"How many are connected."},"message":{"type":"string"}},"required":["integrations","connected","message"]}},{"name":"get_linear_connection","description":"The organization's Linear connection: the workspace, and the team (and project) Letter files issues in when a fix is sent to Linear (action linear) or a new finding is reported there (linear_reports). Connecting is OAuth, in Settings → Integrations → Linear. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connection":{"type":"object","properties":{"connected":{"type":"boolean"},"workspace":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"urlKey":{"type":["string","null"]}},"required":["id","name","urlKey"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"key":{"type":["string","null"]}},"required":["id","name","key"]},{"type":"null"}],"description":"Where issues are filed; null = not picked yet (set_linear_target)."},"project":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}]},"connectedAt":{"type":["string","null"]}},"required":["connected","workspace","team","project","connectedAt"]},"message":{"type":"string"}},"required":["connection","message"]}},{"name":"list_linear_targets","description":"The teams in the connected Linear workspace, and the projects in one team (team_id, else the picked team): where set_linear_target can send issues. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"team_id":{"description":"List this team's projects instead of the picked team's.","type":"string","minLength":1,"maxLength":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"teams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"key":{"type":"string"}},"required":["id","name","key"]}},"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"description":"Projects in team_id (or the picked team)."},"team_id":{"type":["string","null"]},"message":{"type":"string"}},"required":["teams","projects","team_id","message"]}},{"name":"set_linear_target","description":"Pick where Letter files Linear issues: a team (id or key, from list_linear_targets) and optionally one of its projects (project_id; null = none). Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"team_id":{"type":"string","minLength":1,"maxLength":100,"description":"The team issues are filed in: its id or key (e.g. ENG), from list_linear_targets."},"project_id":{"description":"A project in that team to file them under (list_linear_targets with team_id); null or left out = none.","anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]}},"required":["team_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connection":{"type":"object","properties":{"connected":{"type":"boolean"},"workspace":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"urlKey":{"type":["string","null"]}},"required":["id","name","urlKey"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"key":{"type":["string","null"]}},"required":["id","name","key"]},{"type":"null"}],"description":"Where issues are filed; null = not picked yet (set_linear_target)."},"project":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}]},"connectedAt":{"type":["string","null"]}},"required":["connected","workspace","team","project","connectedAt"]},"message":{"type":"string"}},"required":["connection","message"]}},{"name":"disconnect_linear","description":"Disconnect the organization's Linear workspace: Letter stops filing issues there and revokes its access. Issues already filed stay in Linear. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean"},"message":{"type":"string"}},"required":["disconnected","message"]}},{"name":"get_organization","description":"Get the caller's organization profile: id, name, slug, domain, description, vertical, search market (seo_market) and writing_kernel_id. No billing: plan, tokens and payment standing are get_billing and get_billing_payments. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":["string","null"]},"description":{"type":["string","null"]},"vertical":{"type":["string","null"]},"seo_market":{"type":"string","description":"ISO 3166-1 alpha-2 search market keyword demand is measured in. 'US' unless changed."},"writing_kernel_id":{"description":"The org's selected writing kernel, or null if unset.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","slug","domain","description","vertical","seo_market","writing_kernel_id","created_at","updated_at"]}},{"name":"list_org_members","description":"List the members (seats) of the caller's organization — role, join date, and profile. Letter staff with support access are omitted unless the key was created by staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"organization_users row id (the membership/seat id)."},"role":{"type":"string","description":"Member role, e.g. admin or editor."},"joined_at":{"type":"string"},"user_id":{"type":"string"},"profile":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]},{"type":"null"}],"description":"Embedded profile for the member; null if the profile row is missing."}},"required":["id","role","joined_at","user_id","profile"]}}},"required":["items"]}},{"name":"list_invites","description":"List the caller's organization's pending (unaccepted, unrevoked, unexpired) invites. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"role":{"type":"string","description":"Role the invitee will get on acceptance."},"created_at":{"type":"string"},"expires_at":{"type":"string"},"invited_by":{"type":["string","null"]},"allow_generic_email":{"type":"boolean"}},"required":["id","email","role","created_at","expires_at","invited_by","allow_generic_email"]}}},"required":["items"]}},{"name":"create_invite","description":"Create a pending invite for the caller's organization and email it, only with a key minted by an organization admin (best-effort — always returns invite_link so it can be shared manually if email delivery isn't configured or fails). Generic email providers (Gmail, Yahoo, etc.) are rejected; the invitee needs a work email. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"email":{"type":"string","maxLength":255,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Email address to invite. Must be a work email — generic providers (Gmail, Yahoo, etc.) are rejected."},"role":{"default":"editor","description":"Org role the invite grants on acceptance","type":"string","enum":["admin","editor"]}},"required":["email"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"invite":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"},"created_at":{"type":"string"},"expires_at":{"type":"string"},"invited_by":{"type":["string","null"]},"allow_generic_email":{"type":"boolean"}},"required":["id","email","role","created_at","expires_at","invited_by","allow_generic_email"]},"invite_link":{"type":"string","description":"Shareable acceptance URL; always works even if the email didn't send."},"email_sent":{"type":"boolean","description":"Whether the invite email was successfully dispatched (best-effort)."}},"required":["invite","invite_link","email_sent"]}},{"name":"revoke_invite","description":"Revoke a pending invite in the caller's organization by id. Only with a key minted by an organization admin. Refuses invites that were already accepted — remove the member instead. Errors if the invite doesn't exist in this org. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"invite_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Pending invite UUID to revoke (from list_invites)."}},"required":["invite_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"revoked":{"type":"boolean","const":true},"invite_id":{"type":"string"},"revoked_at":{"type":"string"}},"required":["revoked","invite_id","revoked_at"]}},{"name":"update_org_member_role","description":"Change a member's role (admin/editor) in the caller's organization. Only with a key minted by an organization admin. Refuses to demote the last admin — promote another member first. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"User UUID of the member (from list_org_members)."},"role":{"type":"string","enum":["admin","editor"],"description":"New org role"}},"required":["user_id","role"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"member":{"type":"object","properties":{"id":{"type":"string","description":"organization_users row id."},"role":{"type":"string","description":"The member's new role (admin or editor)."},"joined_at":{"type":"string"},"user_id":{"type":"string"}},"required":["id","role","joined_at","user_id"]}},"required":["member"]}},{"name":"remove_org_member","description":"Remove a member from the caller's organization. Only with a key minted by an organization admin. Refuses to remove the last admin — promote another member first. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"User UUID of the member to remove (from list_org_members)."}},"required":["user_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"user_id":{"type":"string"}},"required":["removed","user_id"]}},{"name":"submit_feedback","description":"Submit feedback about Letter itself -- a bug, something missing, something unclear, a feature request, or praise. Args: category (bug | missing_feature | unclear | feature_request | praise | other), message, optional rating (1-5), optional context (free-form metadata, e.g. which tool or page prompted it). Requires capability feedback:write.","capability":"feedback:write","inputSchema":{"type":"object","properties":{"category":{"type":"string","enum":["bug","missing_feature","unclear","feature_request","praise","other"],"description":"bug | missing_feature | unclear | feature_request | praise | other"},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"The feedback itself, in the caller's own words."},"rating":{"description":"Optional 1-5 satisfaction rating, if the caller has one to give.","type":"integer","minimum":1,"maximum":5},"context":{"description":"Optional free-form metadata: which tool/page prompted this, a request id, etc.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["category","message"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"feedback":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["bug","missing_feature","unclear","feature_request","praise","other"]},"message":{"type":"string"},"rating":{"type":["number","null"]},"context":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"created_at":{"type":"string"}},"required":["id","category","message","rating","context","created_at"]}},"required":["feedback"]}},{"name":"get_brand_context","description":"Get the cached MEASUREMENT of the organization's own website: description, slogan, industries, notable links, socials, and the computed visual identity (light/dark mode, colors, heading/body fonts with loadable font files, logos, component styling, spacing, shadows). This is raw material, NOT the org's strategy: positioning is get_positioning, voice is the writing kernel (get_org_writing_kernel), and both change only through a reviewed context pass (ingest_context). Returns brand: null when no website read has happened; run import_brand_context first. Pass `slug` to read a named profile (an offering's own site) instead of the org's default. Requires capability brand:read.","capability":"brand:read","inputSchema":{"type":"object","properties":{"slug":{"description":"A named brand profile; omitted = the org's default","type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"brand":{"anyOf":[{"type":"object","properties":{"brandId":{"type":"string"},"orgId":{"type":"string"},"name":{"type":"string","description":"Display name of the brand."},"slug":{"type":"string","description":"Stable key. 'default' is the org's primary brand."},"isDefault":{"type":"boolean"},"domain":{"description":"The website the context was imported from.","type":["string","null"]},"provider":{"description":"Which importer wrote it, e.g. 'context.dev'.","type":["string","null"]},"importedAt":{"type":["string","null"]},"identity":{"type":"object","properties":{"name":{"type":["string","null"]},"description":{"type":["string","null"]},"slogan":{"type":["string","null"]},"primaryLanguage":{"type":["string","null"]},"industries":{"type":"array","items":{"type":"object","properties":{"industry":{"type":"string"},"subindustry":{"type":["string","null"]}},"required":["industry","subindustry"]}}},"required":["name","description","slogan","primaryLanguage","industries"],"description":"Who the brand says it is. The judgement half (is/is_not, stances, voice) is NOT here."},"visual":{"description":"Measured visual identity: mode, colors, heading/body fonts with loadable files, logos, computed component styles, spacing, shadows. Null when the site's styleguide couldn't be read."},"links":{"description":"Notable pages on the brand's own site (blog, pricing, terms, privacy, contact, careers)."},"socials":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"url":{"type":"string"}},"required":["type","url"]}},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal problems from the last import."},"provenance":{"type":"object","properties":{"source":{"type":["string","null"]},"status":{"type":["string","null"]},"statusReason":{"description":"User-facing 'why this' line.","type":["string","null"]},"decidedBy":{"description":"Set once a person confirmed or corrected it.","type":["string","null"]},"decidedAt":{"type":["string","null"]}},"required":["source","status","statusReason","decidedBy","decidedAt"]}},"required":["brandId","orgId","name","slug","isDefault","domain","provider","importedAt","identity","visual","links","socials","warnings","provenance"]},{"type":"null"}],"description":"The org's resolved brand context, or null if nothing has been imported."}},"required":["brand"]}},{"name":"import_brand_context","description":"Read the organization's own website and cache the measurement. Reads the org's registered domain unless `domain` is given. This only caches — it changes no positioning, identity or styling; those change through a reviewed context pass. Pass refresh: true after a rebrand to bypass the provider's shared cache, which defaults to three months and is shared across the provider's customers. Pass `slug` (with `domain`) to store an offering's own site as a named profile; point the offering's brand_profile_slug at it and its collections render with that look. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"domain":{"description":"Read this site instead of the organization's registered domain.","type":"string","maxLength":253},"refresh":{"description":"Bypass the provider's shared cache. Use after a rebrand — the cache is global across the provider's customers and can otherwise return data warmed months ago.","type":"boolean"},"slug":{"description":"Store the measurement as this named brand profile instead of the org's default — e.g. an offering's own site (pair with `domain`, then set the offering's brand_profile_slug to it).","type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,62}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"brand":{"type":"object","properties":{"brandId":{"type":"string"},"orgId":{"type":"string"},"name":{"type":"string","description":"Display name of the brand."},"slug":{"type":"string","description":"Stable key. 'default' is the org's primary brand."},"isDefault":{"type":"boolean"},"domain":{"description":"The website the context was imported from.","type":["string","null"]},"provider":{"description":"Which importer wrote it, e.g. 'context.dev'.","type":["string","null"]},"importedAt":{"type":["string","null"]},"identity":{"type":"object","properties":{"name":{"type":["string","null"]},"description":{"type":["string","null"]},"slogan":{"type":["string","null"]},"primaryLanguage":{"type":["string","null"]},"industries":{"type":"array","items":{"type":"object","properties":{"industry":{"type":"string"},"subindustry":{"type":["string","null"]}},"required":["industry","subindustry"]}}},"required":["name","description","slogan","primaryLanguage","industries"],"description":"Who the brand says it is. The judgement half (is/is_not, stances, voice) is NOT here."},"visual":{"description":"Measured visual identity: mode, colors, heading/body fonts with loadable files, logos, computed component styles, spacing, shadows. Null when the site's styleguide couldn't be read."},"links":{"description":"Notable pages on the brand's own site (blog, pricing, terms, privacy, contact, careers)."},"socials":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"url":{"type":"string"}},"required":["type","url"]}},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal problems from the last import."},"provenance":{"type":"object","properties":{"source":{"type":["string","null"]},"status":{"type":["string","null"]},"statusReason":{"description":"User-facing 'why this' line.","type":["string","null"]},"decidedBy":{"description":"Set once a person confirmed or corrected it.","type":["string","null"]},"decidedAt":{"type":["string","null"]}},"required":["source","status","statusReason","decidedBy","decidedAt"]}},"required":["brandId","orgId","name","slug","isDefault","domain","provider","importedAt","identity","visual","links","socials","warnings","provenance"],"description":"The brand profile after the import."},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal problems, e.g. a logo that couldn't be rehosted."}},"required":["brand","warnings"]}},{"name":"ingest_context","description":"Feed context about the organization — notes, links, other products' MCP servers, and optionally a fresh measurement of its own website — and get back a REVIEWABLE proposal of targeted changes to positioning, competitors, identity, voice, visuals, ranking goals (new company-wide targets worth tracking) and custom memory (entries filed into existing memory sections, or into new sections the pass names when nothing fits). Nothing is applied: the response lists each proposed change with a stable id, its before/after, and why. Call apply_context with the ids you accept. This is the way to correct what the system believes about a client; it refines what is already stored rather than regenerating from scratch. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"text":{"description":"Free-text notes about the client.","type":"string","maxLength":50000},"links":{"description":"URLs to fetch and read — a Notion page, a Google Doc, anything public.","maxItems":10,"type":"array","items":{"type":"string","format":"uri"}},"include_website":{"description":"Also measure the organization's own website: its description and slogan join the text, and its computed styleguide becomes reviewable colour/font/logo changes.","type":"boolean"},"mcp_servers":{"description":"Connected products to read: only their read-only tools are called, and what they return is filed like any other context.","maxItems":3,"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"A remote MCP server (streamable HTTP or SSE) of another product the org uses."},"authorization_token":{"description":"Bearer token for that server. Used for this one read and never stored.","type":"string","maxLength":4000},"focus":{"description":"What to look for there, in plain words.","type":"string","maxLength":500}},"required":["url"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ingestion_id":{"type":"string"},"summary":{"type":"string","description":"What the context implied overall."},"changes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Pass this to apply_context to accept the change."},"area":{"type":"string","enum":["positioning","competitors","identity","voice","visuals","memory","ranking_goals"]},"field":{"type":"string"},"op":{"type":"string","enum":["replace","add","remove","flag"]},"label":{"type":"string"},"before":{"type":["string","null"]},"after":{"type":["string","null"]},"rationale":{"type":"string","description":"Why it was proposed, grounded in the supplied context."}},"required":["id","area","field","op","label","before","after","rationale"]},"description":"NOTHING is applied yet — call apply_context with the ids you accept."},"links":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"status":{"type":"string"},"chars":{"type":"number"}},"required":["url","status","chars"]},"description":"Per-source outcome (links and connected MCP servers), so a blocked or empty read is visible rather than silent."},"website":{"type":"string","enum":["skipped","ok","no_domain","error"],"description":"Outcome of the optional website measurement."}},"required":["ingestion_id","summary","changes","links","website"]}},{"name":"apply_context","description":"Commit the accepted subset of a context proposal from ingest_context. Each accepted change is written through its real save path — positioning is versioned, competitors are only ever added (flags are recorded for a human, never auto-removed), identity and visual settings are merged rather than replaced. Changes you omit are declined and never written. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"ingestion_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The context pass to commit, from ingest_context."},"accepted_change_ids":{"minItems":1,"type":"array","items":{"type":"string"},"description":"Ids of the proposed changes to apply. Anything omitted is declined and never written."}},"required":["ingestion_id","accepted_change_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"applied_change_ids":{"type":"array","items":{"type":"string"}},"positioning":{"anyOf":[{"type":"object","properties":{"versioned":{"type":"boolean"},"version":{"type":["number","null"]},"fieldsTouched":{"type":"array","items":{"type":"string"}}},"required":["versioned","version","fieldsTouched"]},{"type":"null"}]},"competitors":{"anyOf":[{"type":"object","properties":{"added":{"type":"number"}},"required":["added"]},{"type":"null"}]},"identity":{"anyOf":[{"type":"object","properties":{"touched":{"type":"boolean"},"fieldsTouched":{"type":"array","items":{"type":"string"}}},"required":["touched","fieldsTouched"]},{"type":"null"}]},"voice":{"anyOf":[{"type":"object","properties":{"changed":{"type":"boolean"},"kernelName":{"type":["string","null"]}},"required":["changed","kernelName"]},{"type":"null"}]},"visuals":{"anyOf":[{"type":"object","properties":{"changed":{"type":"boolean"},"fieldsTouched":{"type":"array","items":{"type":"string"}}},"required":["changed","fieldsTouched"]},{"type":"null"}]},"memory":{"anyOf":[{"type":"object","properties":{"added":{"type":"number"},"sectionsCreated":{"type":"array","items":{"type":"string"}}},"required":["added","sectionsCreated"]},{"type":"null"}],"description":"Entries filed in custom memory sections, and any sections the pass created"},"ranking_goals":{"anyOf":[{"type":"object","properties":{"added":{"type":"number"}},"required":["added"]},{"type":"null"}],"description":"New company-wide ranking targets created from accepted changes."}},"required":["applied_change_ids","positioning","competitors","identity","voice","visuals","memory","ranking_goals"]}},{"name":"list_context_ingestions","description":"Review what has been fed into the organization's memory, newest first: each ingest_context pass (from the app's Memory → Ingest or an agent) with its pasted notes, links and documents (names, sizes and read outcomes, not their text), the summary, every change it proposed and whether each was saved. A pass still `proposed` was never reviewed; save its changes with apply_context. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"limit":{"description":"How many passes to return, newest first (default 30)","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ingestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The ingestion_id ingest_context returned; apply_context takes it"},"created_at":{"type":"string"},"author":{"description":"Who ran it; null = an API key (the org acting)","type":["string","null"]},"status":{"type":"string","enum":["proposed","applied","discarded"],"description":"proposed = reviewed by no one yet; its changes can still be saved with apply_context"},"raw_text":{"description":"The pasted notes, verbatim","type":["string","null"]},"links":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"status":{"type":"string","enum":["ok","blocked","empty","error"]},"chars":{"type":"number"},"title":{"type":["string","null"]}},"required":["url","status","chars","title"]}},"files":{"type":"array","items":{"type":"object","properties":{"file_name":{"type":"string"},"mime_type":{"type":"string"},"file_size":{"type":"number"},"chars":{"type":"number"}},"required":["file_name","mime_type","file_size","chars"]}},"summary":{"type":"string"},"changes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"area":{"type":"string"},"field":{"type":"string"},"op":{"type":"string"},"label":{"type":"string"},"before":{"type":["string","null"]},"after":{"type":["string","null"]},"rationale":{"type":"string"},"applied":{"type":"boolean","description":"Saved to memory"}},"required":["id","area","field","op","label","before","after","rationale","applied"]}},"applied_at":{"type":["string","null"]}},"required":["id","created_at","author","status","raw_text","links","files","summary","changes","applied_at"]},"description":"Newest first"}},"required":["ingestions"]}},{"name":"get_memory","description":"Read everything held about the organization in one call: general context (name, domain, brand name, one-liner, overview, default framing, search market, offerings), stances, tenets (what it is, ways to describe it, what it is not), ranking goals, priority and avoided topics, competitors, and every custom memory section with its entries. Empty arrays and nulls mean not known yet. To add to memory, feed context to ingest_context; to file a fact by hand, use add_memory_entry. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"memory":{"type":"object","properties":{"general":{"type":"object","properties":{"name":{"type":["string","null"]},"domain":{"type":["string","null"]},"brand_name":{"description":"The product or brand content features, when it differs from name","type":["string","null"]},"one_liner":{"type":["string","null"]},"overview":{"description":"Long-form company context","type":["string","null"]},"default_framing":{"type":["string","null"]},"search_market":{"type":["string","null"]},"offerings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["id","slug","name","url"]}}},"required":["name","domain","brand_name","one_liner","overview","default_framing","search_market","offerings"]},"stances":{"type":"array","items":{"type":"string"}},"tenets":{"type":"object","properties":{"is":{"type":"array","items":{"type":"string"},"description":"What the company is: facts a mention may assert"},"descriptors":{"type":"array","items":{"type":"string"},"description":"Ways to describe it"},"is_not":{"type":"array","items":{"type":"string"},"description":"What it is not: a mention must never assert these"},"confirmed":{"type":"boolean"}},"required":["is","descriptors","is_not","confirmed"]},"ranking_goals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"phrase":{"type":"string"},"status":{"type":"string"},"priority":{"type":["string","null"]},"offering_id":{"type":["string","null"]}},"required":["id","phrase","status","priority","offering_id"]}},"topics":{"type":"object","properties":{"priority":{"type":"array","items":{"type":"string"}},"avoid":{"type":"array","items":{"type":"string"}},"blocked_mentions":{"type":"array","items":{"type":"string"}}},"required":["priority","avoid","blocked_mentions"]},"competitors":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"relationship":{"type":["string","null"]},"reason":{"type":["string","null"]}},"required":["name","domain","relationship","reason"]}},"sections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"section_id":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"},"origin":{"description":"Where it was read from (notes, a file, a URL, an MCP server); null = typed in","type":["string","null"]},"ingestion_id":{"description":"The ingest pass that filed it, when one did","type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","section_id","title","body","origin","ingestion_id","source","decided_by","decided_at","created_at","updated_at"]}}},"required":["id","slug","name","description","source","decided_by","decided_at","created_at","entries"]}}},"required":["general","stances","tenets","ranking_goals","topics","competitors","sections"]}},"required":["memory"]}},{"name":"find_relevant_context","description":"Which of the organization's custom memory entries actually bear on ONE task — call this before writing or answering something specific (a landing page for one product, a prospect's question about a feature) instead of either ignoring custom memory or dumping the whole pool into your own context. Reads lightweight summaries, not full bodies, so this stays cheap even as memory grows; often returns nothing, which is the correct answer when nothing stored is relevant. The organization's always-on context (positioning, identity, blocked mentions, ranking goals) is separate — read it with get_memory or get_positioning, not this. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"task":{"type":"string","minLength":1,"maxLength":2000,"description":"What you're about to write or answer, specific enough to judge relevance against, e.g. 'a landing page for the Phantoms product' or 'answer a prospect's question about pricing tiers'"},"limit":{"description":"At most this many entries (default 8)","type":"integer","minimum":1,"maximum":20}},"required":["task"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"A custom memory entry id"},"section":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"}},"required":["id","section","title","body"]},"description":"The custom memory entries judged relevant to the task, most relevant first. Often empty — that's the correct answer when nothing stored bears on it."}},"required":["entries"]}},{"name":"create_memory_section","description":"Create a custom memory section: a category of company knowledge the fixed fields don't hold (e.g. 'Case law databases', 'Pricing history'). Give it a one-line description of what belongs there — ingest_context reads it to decide where to file what it reads. Names may repeat; the slug is made unique. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Display name, e.g. 'Case law databases'"},"description":{"description":"What belongs in this section, in a line. Ingest reads it to decide where a fact is filed.","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"section":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"section_id":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"},"origin":{"description":"Where it was read from (notes, a file, a URL, an MCP server); null = typed in","type":["string","null"]},"ingestion_id":{"description":"The ingest pass that filed it, when one did","type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","section_id","title","body","origin","ingestion_id","source","decided_by","decided_at","created_at","updated_at"]}}},"required":["id","slug","name","description","source","decided_by","decided_at","created_at","entries"]}},"required":["section"]}},{"name":"update_memory_section","description":"Rename a custom memory section or change its description. Omitted fields are unchanged. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"section_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string","minLength":1,"maxLength":80,"description":"Display name, e.g. 'Case law databases'"},"description":{"description":"What belongs in this section, in a line. Ingest reads it to decide where a fact is filed.","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["section_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"section":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"section_id":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"},"origin":{"description":"Where it was read from (notes, a file, a URL, an MCP server); null = typed in","type":["string","null"]},"ingestion_id":{"description":"The ingest pass that filed it, when one did","type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","section_id","title","body","origin","ingestion_id","source","decided_by","decided_at","created_at","updated_at"]}}},"required":["id","slug","name","description","source","decided_by","decided_at","created_at","entries"]}},"required":["section"]}},{"name":"delete_memory_section","description":"Delete a custom memory section and every entry filed in it. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"section_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["section_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}},{"name":"add_memory_entry","description":"File one fact or passage in a custom memory section (section_id from get_memory or create_memory_section). Pass origin when it was read from somewhere (a URL, a document, another product). Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"section_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"description":"Short label for the entry","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"body":{"type":"string","minLength":1,"maxLength":20000,"description":"The remembered fact or passage"},"origin":{"description":"Where it was read from; omit when typed in","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["section_id","body"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"entry":{"type":"object","properties":{"id":{"type":"string"},"section_id":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"},"origin":{"description":"Where it was read from (notes, a file, a URL, an MCP server); null = typed in","type":["string","null"]},"ingestion_id":{"description":"The ingest pass that filed it, when one did","type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","section_id","title","body","origin","ingestion_id","source","decided_by","decided_at","created_at","updated_at"]}},"required":["entry"]}},{"name":"update_memory_entry","description":"Edit a custom memory entry's title or body. An edit marks the entry as asserted rather than read. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"entry_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"description":"Short label for the entry","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"body":{"type":"string","minLength":1,"maxLength":20000,"description":"The remembered fact or passage"}},"required":["entry_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"entry":{"type":"object","properties":{"id":{"type":"string"},"section_id":{"type":"string"},"title":{"type":["string","null"]},"body":{"type":"string"},"origin":{"description":"Where it was read from (notes, a file, a URL, an MCP server); null = typed in","type":["string","null"]},"ingestion_id":{"description":"The ingest pass that filed it, when one did","type":["string","null"]},"source":{"type":"string","description":"Who produced it: user (typed in) or ai (read out of ingested context)"},"decided_by":{"description":"The person who decided it; null with decided_at set = the system","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","section_id","title","body","origin","ingestion_id","source","decided_by","decided_at","created_at","updated_at"]}},"required":["entry"]}},{"name":"delete_memory_entry","description":"Delete one custom memory entry. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"entry_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["entry_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}},{"name":"get_org_branding","description":"Get the caller's organization's branding row: colors, logo_url, writing_style and tone. logo_url and colors style landings and visuals; writing_style and tone only steer first-piece ideas, not post writing (a post's voice is the org's writing kernel: get_org_writing_kernel). Distinct from per-asset logo/font uploads. Returns null if the org hasn't set branding yet. Requires capability brand:read.","capability":"brand:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"branding":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"primary_color":{"type":["string","null"]},"secondary_color":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"writing_style":{"type":["string","null"]},"tone":{"type":["string","null"]},"client_mention":{"description":"Free-form JSON."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","primary_color","secondary_color","accent_color","logo_url","writing_style","tone","client_mention","created_at","updated_at"]},{"type":"null"}],"description":"The org's brand-voice row, or null if none set yet."}},"required":["branding"]}},{"name":"update_org_branding","description":"Update the caller's organization's branding row (colors, logo_url, writing_style, tone). Each field is optional: a provided field is written, an omitted field is left unchanged. Creates the row on first write. This doesn't change how posts are written: that is the writing kernel (set_org_writing_kernel) or a reviewed ingest_context pass. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"primary_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"secondary_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"logo_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"writing_style":{"type":"string","maxLength":100},"tone":{"type":"string","maxLength":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"branding":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"primary_color":{"type":["string","null"]},"secondary_color":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"writing_style":{"type":["string","null"]},"tone":{"type":["string","null"]},"client_mention":{"description":"Free-form JSON."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","primary_color","secondary_color","accent_color","logo_url","writing_style","tone","client_mention","created_at","updated_at"],"description":"The org's brand-voice row after the upsert."}},"required":["branding"]}},{"name":"get_org_writing_kernel","description":"Get the caller's organization's selected writing kernel — the kernel id and, if one is set, the full writing_kernels record. Requires capability kernel:read.","capability":"kernel:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"writing_kernel_id":{"description":"Id of the org's selected writing kernel, or null.","type":["string","null"]},"kernel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image_path":{"type":["string","null"]},"image_url":{"type":["string","null"]},"kern_path":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"service_kernel_id":{"type":["string","null"]},"service_kernel_version":{"type":["number","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","name","image_path","image_url","kern_path","creator_id","service_kernel_id","service_kernel_version","created_at","updated_at"]},{"type":"null"}],"description":"The full writing_kernels record, or null if none is set."}},"required":["writing_kernel_id","kernel"]}},{"name":"set_org_writing_kernel","description":"Set (or clear, with null) the caller's organization's selected writing kernel. Errors if the kernel id doesn't exist. Selecting a global (shared, staff-curated) kernel requires the key's creator to be an organization admin — same rule the app enforces. Requires capability kernel:write.","capability":"kernel:write","inputSchema":{"type":"object","properties":{"writing_kernel_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Writing kernel UUID to select, or null to clear it."}},"required":["writing_kernel_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"writing_kernel_id":{"description":"The now-selected kernel id, or null if cleared.","type":["string","null"]}},"required":["writing_kernel_id"]}},{"name":"get_org_visual_settings","description":"Get the caller's organization's visual settings (table_settings, venn_settings) — the defaults merged into new table/Venn visuals. Returns an empty object if the org hasn't set any yet. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"visual_settings":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"table_settings":{"anyOf":[{"description":"Default table-visual settings JSON."},{"type":"null"}]},"venn_settings":{"anyOf":[{"description":"Default Venn-visual settings JSON."},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","table_settings","venn_settings","created_at","updated_at"]},{"type":"object","properties":{}}]}},"required":["visual_settings"]}},{"name":"update_org_visual_settings","description":"Update the caller's organization's visual settings (table_settings and/or venn_settings). Each field REPLACES its stored value; an omitted field is left unchanged. Creates the settings row on first write. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"table_settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"venn_settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"visual_settings":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"table_settings":{"anyOf":[{"description":"Default table-visual settings JSON."},{"type":"null"}]},"venn_settings":{"anyOf":[{"description":"Default Venn-visual settings JSON."},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","table_settings","venn_settings","created_at","updated_at"],"description":"The org's visual-settings row after the upsert."}},"required":["visual_settings"]}},{"name":"list_eligible_reviewers","description":"List teammates in the caller's organization eligible to be assigned as a post's reviewer. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"full_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]}},"required":["id","email","full_name","avatar_url"]}}},"required":["items"]}},{"name":"extract_blog_reference_images","description":"Extract candidate reference images from a blog/article URL, to condition cover generation on another blog's look. Returns public image URLs; pass the chosen ones as reference_image_urls when generating cover variants. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"Blog/article URL to extract candidate reference images from."}},"required":["url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"images":{"type":"array","items":{"type":"string"},"description":"Public image URLs extracted from the blog/article page."}},"required":["images"]}},{"name":"get_usage_summary","description":"Get the caller's organization's legacy AI-activity gate (lifetime weighted AI activity, creditsUsed; not billed tokens, which are get_billing): threshold, status, enforcement, overThreshold, shouldPrompt, blocked. This gate is off unless staff set a per-org threshold, so blocked is almost always false; it is NOT the spend cap or the free paywall that refuse work (payment_required, CARD_REQUIRED): read those with get_billing_payments and get_billing. The first read over a set threshold marks the org prompted. Best-effort: a read failure returns nothing blocked. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"creditsUsed":{"type":"number","description":"Lifetime AI activity this org has used, in weighted units (not billed tokens)."},"threshold":{"type":"number","description":"Effective gate threshold (per-org override ?? env default ?? config default)."},"status":{"type":"string","enum":["ok","prompted","call_booked","plan_active"],"description":"Gate / card / plan status."},"enforcement":{"type":"string","enum":["soft","hard"],"description":"Whether crossing the threshold prompts (soft) or blocks (hard)."},"overThreshold":{"type":"boolean","description":"creditsUsed >= threshold."},"shouldPrompt":{"type":"boolean","description":"Over threshold and the org hasn't yet booked a call / started a plan."},"blocked":{"type":"boolean","description":"Hard enforcement AND over threshold AND unresolved — AI actions should be refused."}},"required":["creditsUsed","threshold","status","enforcement","overThreshold","shouldPrompt","blocked"]}},{"name":"get_usage_breakdown","description":"Get the legacy AI-activity gate (as get_usage_summary) plus lifetime AI activity per action, in weighted units, highest first. Not tokens or spend: billed tokens this period by product and collection are get_billing; metered usage per SKU over a range is get_usage_events_summary. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"summary":{"type":"object","properties":{"creditsUsed":{"type":"number","description":"Lifetime AI activity this org has used, in weighted units (not billed tokens)."},"threshold":{"type":"number","description":"Effective gate threshold (per-org override ?? env default ?? config default)."},"status":{"type":"string","enum":["ok","prompted","call_booked","plan_active"],"description":"Gate / card / plan status."},"enforcement":{"type":"string","enum":["soft","hard"],"description":"Whether crossing the threshold prompts (soft) or blocks (hard)."},"overThreshold":{"type":"boolean","description":"creditsUsed >= threshold."},"shouldPrompt":{"type":"boolean","description":"Over threshold and the org hasn't yet booked a call / started a plan."},"blocked":{"type":"boolean","description":"Hard enforcement AND over threshold AND unresolved — AI actions should be refused."}},"required":["creditsUsed","threshold","status","enforcement","overThreshold","shouldPrompt","blocked"]},"byAction":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"credits":{"type":"number","description":"AI activity for this action, in weighted units (the field keeps its old name)."},"events":{"type":"number"}},"required":["action","credits","events"]},"description":"Per-action AI activity rollup, highest first (weighted units, not billed tokens)."}},"required":["summary","byAction"]}},{"name":"get_usage_events_summary","description":"Get what the caller's organization used over a range, from usage metering (the same events billing rates and sends to Lago): articles, writes, shreds, Build & Fix agent runs and sandbox minutes, live sites, domains, GEO answers, tracked competitors, outreach, enclosure prints. Quantity and billable quantity per SKU, grouped by collection, customer_ref or day if asked (default: last 30 days, by SKU). tokens is null for days rated on an unpublished rate card; that does not mean free (an org on a plan is billed for them). What it was charged is get_billing / get_billing_invoices. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"groupBy":{"description":"How to group the rows: sku, collection, customer_ref, day (default: sku).","maxItems":4,"type":"array","items":{"type":"string","enum":["sku","collection","customer_ref","day"]}},"includeNested":{"type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"range":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"description":"Inclusive UTC days."},"groupBy":{"type":"array","items":{"type":"string"},"description":"The grouping applied, besides SKU."},"rateCard":{"anyOf":[{"type":"object","properties":{"version":{"type":"string"},"published":{"type":"boolean"}},"required":["version","published"]},{"type":"null"}],"description":"The newest rate card the tokens were computed with; null while prices are not published."},"rateBasis":{"anyOf":[{"type":"string","enum":["card_in_force_per_day","named_card"]},{"type":"null"}],"description":"How tokens were rated: each UTC day on the card in force that day (as billing does), so a range across a card's start mixes cards."},"rateCards":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"version":{"type":"string"},"published":{"type":"boolean"},"effectiveFrom":{"description":"When the card took effect; null for one with no real start.","type":["string","null"]}},"required":["version","published","effectiveFrom"]}},{"type":"null"}],"description":"Every rate card the range's tokens were rated with, oldest first, and when each took effect."},"rows":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","description":"What was used: article, write, shred, agent_run, phantom_site_day, …"},"unit":{"type":"string","description":"What one unit of quantity is."},"category":{"description":"Billing category key (writing, agents, sites, …).","type":["string","null"]},"categoryLabel":{"type":["string","null"]},"collectionId":{"description":"The collection (project), when grouped by it.","type":["string","null"]},"customerRef":{"description":"Your own end customer, when grouped by it.","type":["string","null"]},"day":{"description":"UTC day, when grouped by it.","type":"string"},"events":{"type":"number"},"quantity":{"type":"number","description":"Everything recorded, billable or not."},"billableQuantity":{"type":"number","description":"What the billing rule counts: finished work, not platform failures."},"nestedQuantity":{"type":"number","description":"Work that ran inside another outcome (listed only with includeNested)."},"tokens":{"description":"Tokens for the billable quantity, once prices are published.","type":["number","null"]}},"required":["sku","unit","category","categoryLabel","events","quantity","billableQuantity","nestedQuantity"]}},"totals":{"type":"object","properties":{"events":{"type":"number"},"billableEvents":{"type":"number"},"tokens":{"type":["number","null"]}},"required":["events","billableEvents"]}},"required":["range","groupBy","rateCard","rateBasis","rateCards","rows","totals"]}},{"name":"get_story_actions","description":"Get The Story's 'Needs you' action deck: exact counts (never AI-estimated) of Curate topics ready to review, plus the article pipeline queued/writing/enhancing/ready-to-publish. Each queue includes a few of its newest items for deep-linking; the queue's count may exceed the items returned. Requires capability story:read.","capability":"story:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"queues":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["topics_ready","queued","writing","enhancing","ready_to_publish"],"description":"Which action deck this queue represents."},"count":{"type":"number","description":"Exact total in this queue; may exceed items.length."},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Article id for pipeline queues, suggestion id for topics_ready."},"title":{"type":"string"},"meta":{"description":"Short trailing hint (e.g. a score).","type":["string","null"]}},"required":["id","title"]}}},"required":["key","count","items"]}}},"required":["queues"]}},{"name":"get_recent_wins","description":"Get The Story's 'recent wins' momentum feed: articles published in a rolling window (default 7 days, up to 30), lifetime published count, the newest few titles, and completed CMS pushes (Sanity/Webflow) in the window. Unlike get_story_actions, this reflects what has already shipped rather than what's still to do. Requires capability story:read.","capability":"story:read","inputSchema":{"type":"object","properties":{"days":{"description":"Rolling window in days (1-30, default 7)","type":"integer","minimum":1,"maximum":30}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"window_days":{"type":"number"},"published_in_window":{"type":"number","description":"Articles published within the window."},"published_lifetime":{"type":"number","description":"Articles ever published (non-archived)."},"recently_published":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":"string"},"published_at":{"type":"string"}},"required":["article_id","title","published_at"]},"description":"Most recent published pieces, newest first."},"pushed_live":{"type":"object","properties":{"count":{"type":"number","description":"Total completed CMS export pushes in the window."},"providers":{"type":"array","items":{"type":"string","enum":["sanity","webflow"]},"description":"Providers that received a push."}},"required":["count","providers"]}},"required":["window_days","published_in_window","published_lifetime","recently_published","pushed_live"]}},{"name":"get_publish_failures","description":"Get The Story's Publish Health nudge: still-actionable failures from a rolling window (default 14 days, up to 60) — CMS pushes (Sanity/Webflow) whose LATEST attempt for that article failed. A failure already fixed by a later successful attempt is excluded. Requires capability story:read.","capability":"story:read","inputSchema":{"type":"object","properties":{"days":{"description":"Rolling window in days (1-60, default 14)","type":"integer","minimum":1,"maximum":60}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"window_days":{"type":"number"},"cms":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","const":"cms"},"provider":{"type":"string","enum":["sanity","webflow"]},"article_id":{"type":"string"},"title":{"type":"string"},"mapping_id":{"description":"Non-null ⇒ the push can be retried in place.","type":["string","null"]},"error":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["kind","provider","article_id","title","mapping_id","error","created_at"]},"description":"CMS export pushes whose latest attempt failed."}},"required":["window_days","cms"]}},{"name":"get_cadence_summary","description":"Get the org-wide publishing-cadence roll-up: one progress entry per collection, each measured against ITS OWN target over its own rolling 7- (weekly) or 30-day (monthly) window. 'Published' counts a transition to the published lifecycle phase, never a raw published_at timestamp. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collections":{"type":"array","items":{"type":"object","properties":{"collection_id":{"type":"string"},"name":{"type":"string"},"cadence_target":{"description":"Target publishes per window, or null if unset.","type":["number","null"]},"cadence_period":{"type":"string","enum":["week","month"]},"window_days":{"type":"number","description":"Rolling window length (7 for weekly, 30 for monthly)."},"published_count":{"type":"number"},"status":{"anyOf":[{"type":"string","enum":["behind","on_track","ahead"]},{"type":"null"}],"description":"Pace vs. target; null when the collection has no cadence target set."}},"required":["collection_id","name","cadence_target","cadence_period","window_days","published_count","status"]}}},"required":["collections"]}},{"name":"get_logo_upload_url","description":"Mint a short-lived signed upload URL for a new organization logo (organization-logos bucket). Raster only (jpeg/png/webp) — SVG is not offered here, since the browser upload route's SVG support relies on server-side rasterization this path skips (stored-XSS prevention). PUT the image bytes to the returned URL with the given content type, then the object is live at `public_url`. Call remove_logo on the old logo's path once the new one is in place, if replacing. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"format":{"type":"string","enum":["jpeg","png","webp"],"description":"Raster image format for the new logo. SVG is not offered here: the browser upload route accepts SVG only because it rasterizes it server-side first (stored-XSS prevention), and a signed-upload-url path has no server-side step to do that."}},"required":["format"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"bucket":{"type":"string","description":"Supabase Storage bucket the object lives in."},"path":{"type":"string","description":"Object key to PUT the bytes to."},"token":{"type":"string","description":"Short-lived signed-upload token for the PUT."},"content_type":{"type":"string","description":"Content-Type header to send with the PUT."},"public_url":{"type":"string","description":"Public URL the object is served from once uploaded."}},"required":["bucket","path","token","content_type","public_url"]}},{"name":"get_cadence_progress","description":"Get ONE collection's publishing pace against its cadence target over its rolling window (7 days for a weekly target, 30 for monthly), broken down by cluster with article ids/titles for deep-links. 'Published' counts a transition to the published lifecycle phase, never a raw published_at timestamp. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections) to measure cadence for"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cadence_target":{"description":"Target publishes per window, or null if unset.","type":["number","null"]},"cadence_period":{"type":"string","enum":["week","month"]},"window_days":{"type":"number","description":"Rolling window length (7 for weekly, 30 for monthly)."},"published_count":{"type":"number"},"status":{"anyOf":[{"type":"string","enum":["behind","on_track","ahead"]},{"type":"null"}],"description":"Pace vs. target; null when the collection has no cadence target set."},"clusters":{"type":"array","items":{"type":"object","properties":{"cluster_id":{"description":"Cluster id, or null for the Unclustered bucket.","type":["string","null"]},"name":{"type":"string"},"count":{"type":"number"},"articles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}},"required":["id","title"]}}},"required":["cluster_id","name","count","articles"]},"description":"Published articles grouped by cluster, largest first, Unclustered last."}},"required":["cadence_target","cadence_period","window_days","published_count","status","clusters"]}},{"name":"list_strategy_suggestions","description":"List one collection's pending writing-strategy suggestions (the planner's verdicts on pillars and proposals for the automations' week), not the account Plan (propose_account_plan) or Build/Fix suggestions (list_build_suggestions). Each carries its disposition: 'auto' items apply themselves on auto_apply_at unless acted on; 'hold' items wait for a human. snoozed_until, when set, defers an auto item. A change to the automations' week (action_kind rebalance_automations / add_automation) carries `week`, every automation's pace before and after, and `reason`; it is auto only where the letterbase's strategy_approval is review_window. Decide one with decide_strategy_suggestion; see already-decided ones with list_strategy_history. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose pending strategy suggestions to list"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Suggestion id — pass to decide_strategy_suggestion"},"kind":{"type":"string","enum":["verdict","proposal"],"description":"Which queue this is from"},"cluster_name":{"description":"The pillar this is about, or null for site-scope","type":["string","null"]},"title":{"type":"string","description":"The plain-English recommendation"},"action_kind":{"description":"The lever it would pull (e.g. set_priority), or null for FYI-only","type":["string","null"]},"action_label":{"description":"The button verb (e.g. 'Prioritize this area'), or null","type":["string","null"]},"disposition":{"type":"string","enum":["auto","hold"],"description":"auto = applies itself on auto_apply_at unless acted on; hold = waits for a human"},"auto_apply_at":{"description":"When an auto suggestion applies itself (ISO), or null for hold","type":["string","null"]},"snoozed_until":{"description":"A live snooze deadline (ISO) deferring auto-apply, or null","type":["string","null"]},"reason":{"description":"A change to the automations' week: why, in numbers (each automation against its peers)","type":"string"},"week":{"description":"A change to the automations' week (rebalance_automations / add_automation): every automation's pace before and after; the total never changes","type":"object","properties":{"before":{"type":"object","properties":{"total":{"type":"number","description":"Posts a week: the sum of the paces that are on"},"automations":{"type":"array","items":{"type":"object","properties":{"automationId":{"description":"null: the automation the proposal adds","type":["string","null"]},"name":{"type":"string"},"kind":{"type":"string","enum":["topic","series","signal","best"]},"pace":{"description":"Posts a week; null for a signal","type":["number","null"]},"paused":{"type":"boolean"}},"required":["automationId","name","kind","pace","paused"]}}},"required":["total","automations"]},"after":{"type":"object","properties":{"total":{"type":"number","description":"Posts a week: the sum of the paces that are on"},"automations":{"type":"array","items":{"type":"object","properties":{"automationId":{"description":"null: the automation the proposal adds","type":["string","null"]},"name":{"type":"string"},"kind":{"type":"string","enum":["topic","series","signal","best"]},"pace":{"description":"Posts a week; null for a signal","type":["number","null"]},"paused":{"type":"boolean"}},"required":["automationId","name","kind","pace","paused"]}}},"required":["total","automations"]},"line":{"type":"string","description":"The week after, in a line: '6.5/wk: Storage for agents 2 → 3, Best ideas 2 → 1'"}},"required":["before","after","line"]}},"required":["id","kind","cluster_name","title","action_kind","action_label","disposition","auto_apply_at","snoozed_until"]}}},"required":["collection_id","suggestions"]}},{"name":"decide_strategy_suggestion","description":"Approve, dismiss, or snooze a pending strategy suggestion — the SAME lever the web queue and REST pull (lib/strategy/decide.ts). approve applies the recommended action now; dismiss silences it until its evidence deepens; snooze defers an auto item's grace timer (snooze_days, default 7). undo (a proposal only) reverses an applied change to the automations' week: the paces stored on it come back and an automation it added is removed (an automation's `decision.proposal_id` in list_automations names it). Recorded against the API key's owner for the audit trail. dismiss is anyone's; approve, snooze and undo are Letter staff's, except on a proposal that is the client's own call (rebalance_automations, add_automation), as in the app. Requires capability strategy:write.","capability":"strategy:write","inputSchema":{"type":"object","properties":{"kind":{"type":"string","enum":["verdict","proposal"],"description":"Which queue the suggestion is from (see list_strategy_suggestions)"},"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Suggestion id from list_strategy_suggestions (or, for undo, list_strategy_history)"},"decision":{"type":"string","enum":["approve","dismiss","snooze","undo"],"description":"approve = pull the lever now; dismiss = silence it; snooze = defer its auto-apply; undo = reverse an applied change to the automations' week (a proposal only)"},"snooze_days":{"description":"For snooze: days to defer auto-apply (default 7)","type":"integer","minimum":1,"maximum":90}},"required":["kind","id","decision"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"decision":{"type":"string","enum":["approve","dismiss","snooze","undo"]},"applied":{"description":"What the lever did, a snooze deadline, or null when nothing to apply","type":["string","null"]},"lever_failed":{"type":"boolean","description":"True when the decision was recorded but the recommended lever threw after the claim landed — the row reads 'failed', not 'approved'; applied carries why."}},"required":["ok","decision","applied","lever_failed"]}},{"name":"list_strategy_history","description":"List a collection's recently DECIDED strategy suggestions — approved, dismissed, or auto-applied by the opinion layer's grace timer (lib/strategy/auto-apply.ts) — most recent first. The same rows list_strategy_suggestions shows before a decision, past tense: decide.ts never deletes, it updates status/decided_by/decided_at in place. decided_by_name is null when the system decided it on its own, not a person. Requires capability strategy:read.","capability":"strategy:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose decided strategy history to list"},"limit":{"description":"Maximum number of recently-decided items to return, most recent first. Defaults to 15.","type":"integer","minimum":1,"maximum":50}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"history":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The decided verdict or proposal's id"},"kind":{"type":"string","enum":["verdict","proposal"],"description":"Which queue this was from"},"title":{"type":"string","description":"The plain-English recommendation that was decided"},"cluster_name":{"description":"The pillar this was about, or null for site-scope","type":["string","null"]},"status":{"type":"string","enum":["approved","dismissed"]},"decided_at":{"type":"string","description":"ISO timestamp of the decision"},"decided_by_name":{"description":"Name or email of the person who decided it, or null when the opinion layer auto-applied it","type":["string","null"]}},"required":["id","kind","title","cluster_name","status","decided_at","decided_by_name"]},"description":"Most recently decided first"}},"required":["collection_id","history"]}},{"name":"list_assigned_reviews","description":"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. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"posts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"collectionId":{"type":["string","null"]},"collectionName":{"type":["string","null"]},"phase":{"type":["string","null"]},"createdAt":{"type":"string"},"myReviewId":{"type":"string"},"myStatus":{"type":"string","description":"pending · approved · changes_requested"},"summary":{"description":"The roster: total, approved, all_approved, pending reviewers."},"isLastApprover":{"type":"boolean"},"canPublish":{"type":"boolean","description":"Every reviewer has approved: it can be published."}},"required":["articleId","title","collectionId","collectionName","phase","createdAt","myReviewId","myStatus","summary","isLastApprover","canPublish"]},"description":"Posts waiting on the key's user, needing a verdict first, then ready to publish."}},"required":["posts"]}},{"name":"list_revisions","description":"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) or the removal of a never-mention competitor or a neutral mention the mention policy now blocks (source mention_remediation). 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. Requires capability review:read.","capability":"review:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Only this collection's proposed revisions. Omit for the whole org's queue.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"revisions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Revision id — pass to decide_revision"},"article_id":{"type":"string"},"collection_id":{"type":"string"},"article_title":{"type":["string","null"]},"collection_name":{"type":["string","null"]},"publish_control":{"description":"platform = accepting publishes it; connector/advisory = accept records agreement, apply it in your CMS","type":["string","null"]},"source":{"type":"string","description":"Which producer proposed it: flywheel (a mention an AI citation earned) | relink | …"},"reason":{"type":["string","null"]},"status":{"type":"string","description":"proposed"},"base_version":{"type":"number","description":"The published version this was computed against"},"live_version":{"type":"number","description":"The published version now"},"is_stale":{"type":"boolean","description":"The page moved past base_version: accepts are refused until it is recomputed"},"source_snapshot_at":{"description":"How recent our copy of a CMS page is","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Change id — pass to decide_revision to decide just this one"},"kind":{"type":"string","description":"replace (a text edit) | internal_link (a link the refresh would add)"},"anchor":{"description":"Where on the page the change lands, or null","type":["string","null"]},"before":{"description":"The text or link it replaces, or null for a pure addition","type":["string","null"]},"after":{"type":"string","description":"What the page says or links to after the change"},"reason":{"description":"Why this change, in plain English","type":["string","null"]},"decision":{"anyOf":[{"type":"string","enum":["accepted","rejected"]},{"type":"null"}],"description":"null until decided"}},"required":["id","kind","anchor","before","after","reason","decision"]}},"created_at":{"type":"string"}},"required":["id","article_id","collection_id","article_title","collection_name","publish_control","source","reason","status","base_version","live_version","is_stale","source_snapshot_at","changes","created_at"]},"description":"Newest first, at most 100"}},"required":["revisions"]}},{"name":"decide_revision","description":"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. Requires capability review:write.","capability":"review:write","inputSchema":{"type":"object","properties":{"decision":{"type":"string","enum":["accepted","rejected"],"description":"accepted = take the change(s): on a page we publish the revision goes live once every change is decided; rejected = decline"},"change_id":{"description":"Decide one change (from list_revisions). Omit to decide every undecided change at once.","type":"string","minLength":1},"note":{"description":"Optional note recorded with a whole-revision decision","type":"string","maxLength":500},"revision_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Revision id from list_revisions"}},"required":["decision","revision_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"status":{"type":"string","description":"proposed (changes still undecided) | accepted | rejected — the revision's status after this decision"},"applied":{"description":"On acceptance: whether it went live (a page we publish) or was only recorded; else null","type":["string","null"]}},"required":["ok","status","applied"]}},{"name":"get_internal_link_status","description":"The internal-link refresh for one collection: whether it is on (on by default wherever the collection has a sitemap), how many published posts are due for a refresh now, and when the next one comes due. The nightly sweep refreshes due posts, most overdue collections first, every post at least every two weeks; on a manual-approval or client-CMS collection the new links are proposed for approval (list_revisions) instead of republished. Run the next batch now with refresh_internal_links. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose internal-link refresh to read"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"enabled":{"type":"boolean","description":"Whether the policy is on (on by default wherever the collection has a sitemap)"},"due":{"type":"number","description":"Published posts due for a refresh now"},"next_due_in_days":{"description":"Days until the first post comes due, or null when only new publishing will make one due","type":["number","null"]}},"required":["collection_id","enabled","due","next_due_in_days"]}},{"name":"refresh_internal_links","description":"Run a collection's next batch of internal-link refreshes now instead of waiting for the nightly sweep — the same 'Refresh links now' the collection room offers (lib/services/relink.ts refreshCollectionLinksNow). Same batch size as a night. Runs even with the policy off (asking is the opt-in). Each started post re-links from the collection's own sitemap and republishes when it finishes; on a manual-approval or client-CMS collection the links are proposed for approval instead (posts_proposed; see list_revisions). Returns as soon as the work is queued. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID to run the next batch of internal-link refreshes on, now"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"posts_started":{"type":"number","description":"Posts whose refresh started — each republishes when it finishes"},"posts_proposed":{"type":"number","description":"Posts whose new links were proposed for approval instead (manual approval, or a client-CMS collection)"},"posts_skipped":{"type":"number","description":"Posts that were due but busy with another run"},"skipped":{"type":"object","properties":{"no_sitemap":{"type":"number"},"no_actor":{"type":"number"},"nothing_due":{"type":"number"}},"required":["no_sitemap","no_actor","nothing_due"],"description":"Why the collection contributed nothing, when it didn't"},"posts_considered":{"type":["number","null"]},"next_due_in_days":{"type":["number","null"]}},"required":["collection_id","posts_started","posts_proposed","posts_skipped","skipped","posts_considered","next_due_in_days"]}},{"name":"preview_preferred_sources","description":"Step 1 of bringing a collection's PUBLISHED posts up to its preferred sources (update_auto_pen_config preferred_sources). Queues one DRY proposal per eligible post — published, with a Sources list, and a preferred site it does not cite yet. Each searches the preferred sites for pages on the post's topic, reads them, and proposes at most one sentence stating one of their figures, judged against the sources the post ALREADY cites: a fit from 1 to 5 and whether it adds anything they do not. Nothing is written to any post. The enhance cron works through the queue every two minutes; read the result with get_preferred_sources_preview. Supersedes an unfinished earlier preview. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"batch_id":{"type":"string"},"queued":{"type":"number","description":"Posts queued for a proposal; the enhance cron works through them every two minutes"},"published":{"type":"number"},"already_citing_client":{"type":"number","description":"Published posts that already cite one of the client's pages"},"skipped":{"type":"object","properties":{"no_citations":{"type":"number"},"already_cites_preferred":{"type":"number"}},"required":["no_citations","already_cites_preferred"]}},"required":["collection_id","batch_id","queued","published","already_citing_client","skipped"]}},{"name":"get_preferred_sources_preview","description":"Step 2: the latest preferred-sources preview of a collection, ranked. A proposal QUALIFIES at fit 4 or 5 when it adds something the post's sources do not. For the client's own site only the strongest qualifying proposals are chosen, up to the collection's ceiling (client_one_in: at most one in N published posts cites the client, counting those that already do); relevance decides how many, the ceiling only caps it. Qualifying third-party proposals are all chosen. Returns each proposal's sentence before and after, its link, fit and reason; what is held by the ceiling or below the bar; why nothing fit elsewhere; and `projected` — the posts citing the client after applying, of the published, as a 1-in-N. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"batch_id":{"type":["string","null"]},"status":{"type":"string","description":"none | running | done"},"checked":{"type":"number"},"total":{"type":"number"},"published":{"type":"number"},"already_citing_client":{"type":"number"},"client_one_in":{"type":"number","description":"The ceiling: at most one in this many published posts cites the client"},"client_slots":{"type":"number","description":"How many more posts may cite the client under the ceiling"},"chosen":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"run_id":{"type":"string","description":"The proposal's enhance run (get_enhance_run)"},"sentence":{"type":"string","description":"The sentence as the post states it"},"text":{"type":"string","description":"The sentence as it would read, with the figure"},"url":{"type":"string","description":"The page the figure is linked to"},"fit":{"type":"number","description":"1 (unrelated) to 5 (direct evidence for the sentence's claim)"},"adds":{"type":"boolean","description":"The post's existing sources do not already support that claim"},"reason":{"type":"string"},"client":{"type":"boolean","description":"On the client's own site (counted against the ceiling)"},"kind":{"type":"string","description":"figure: a sentence rewritten and linked | source: listed in Sources, no change to the prose"},"note":{"description":"source: the line its Sources entry carries","type":["string","null"]}},"required":["article_id","title","run_id","sentence","text","url","fit","adds","reason","client","kind","note"]},"description":"What cite_preferred_sources would apply"},"held_by_ceiling":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"run_id":{"type":"string","description":"The proposal's enhance run (get_enhance_run)"},"sentence":{"type":"string","description":"The sentence as the post states it"},"text":{"type":"string","description":"The sentence as it would read, with the figure"},"url":{"type":"string","description":"The page the figure is linked to"},"fit":{"type":"number","description":"1 (unrelated) to 5 (direct evidence for the sentence's claim)"},"adds":{"type":"boolean","description":"The post's existing sources do not already support that claim"},"reason":{"type":"string"},"client":{"type":"boolean","description":"On the client's own site (counted against the ceiling)"},"kind":{"type":"string","description":"figure: a sentence rewritten and linked | source: listed in Sources, no change to the prose"},"note":{"description":"source: the line its Sources entry carries","type":["string","null"]}},"required":["article_id","title","run_id","sentence","text","url","fit","adds","reason","client","kind","note"]},"description":"Qualified, but past the ceiling"},"below_bar":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"run_id":{"type":"string","description":"The proposal's enhance run (get_enhance_run)"},"sentence":{"type":"string","description":"The sentence as the post states it"},"text":{"type":"string","description":"The sentence as it would read, with the figure"},"url":{"type":"string","description":"The page the figure is linked to"},"fit":{"type":"number","description":"1 (unrelated) to 5 (direct evidence for the sentence's claim)"},"adds":{"type":"boolean","description":"The post's existing sources do not already support that claim"},"reason":{"type":"string"},"client":{"type":"boolean","description":"On the client's own site (counted against the ceiling)"},"kind":{"type":"string","description":"figure: a sentence rewritten and linked | source: listed in Sources, no change to the prose"},"note":{"description":"source: the line its Sources entry carries","type":["string","null"]}},"required":["article_id","title","run_id","sentence","text","url","fit","adds","reason","client","kind","note"]},"description":"Proposed, but fit under 4 or adds nothing the post's sources do not"},"nothing_fit":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"why":{"type":"string"}},"required":["article_id","title","why"]}},"over_indexing":{"type":"boolean","description":"More client proposals qualified than the ceiling allows: likely over-indexing on the client. Review held_by_ceiling before raising the ceiling"},"projected":{"type":"object","properties":{"citing_client":{"type":"number"},"of":{"type":"number"},"one_in":{"type":["number","null"]},"inline":{"type":"number","description":"New citations stated and linked in the prose"},"sources_only":{"type":"number","description":"New citations listed in Sources only"}},"required":["citing_client","of","one_in","inline","sources_only"],"description":"After applying: posts citing the client, of the published — the 1-in-N to quote"}},"required":["collection_id","batch_id","status","checked","total","published","already_citing_client","client_one_in","client_slots","chosen","held_by_ceiling","below_bar","nothing_fit","over_indexing","projected"]}},{"name":"cite_preferred_sources","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID (from list_collections)"},"exclude_article_ids":{"description":"Chosen posts to leave out of this apply (article UUIDs from get_preferred_sources_preview).","maxItems":200,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"batch_id":{"type":"string"},"started":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]}},"required":["article_id","title"]},"description":"Posts whose previewed sentence is being applied"},"republishes":{"type":"boolean","description":"Whether started posts republish when they finish; false means the change waits on the draft"},"skipped":{"type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"title":{"type":["string","null"]},"reason":{"type":"string","description":"busy | failed | excluded"}},"required":["article_id","title","reason"]}}},"required":["collection_id","batch_id","started","republishes","skipped"]}},{"name":"get_collection_inventory","description":"Where every piece in a collection is — being written, written but not live (and what each waits on), live, and the client's own imported posts kept apart, each listed post with its coverImage — plus pendingReview: finished drafts never published, each with autoPublishAt, when it goes live with nobody acting (the collection's Human Checkpoint decides: autopilot publishes on the next sweep, a review window when its hours close, manual approval never — null means it waits for a person). The same read as the collection room's Work tab and 'needs you' strip. Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Collection UUID whose inventory to read"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"beingWritten":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"phase":{"type":"string"},"updatedAt":{"type":["string","null"]},"waiting":{"type":"object","properties":{"kind":{"type":"string","enum":["scheduled","awaiting_approval","planner_off","unscheduled"]},"at":{"type":"string"}},"required":["kind"],"description":"Why a written piece is not live yet"},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title","phase","updatedAt","waiting"]}}},"required":["count","items"]},"datedNotLive":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"phase":{"type":"string"},"updatedAt":{"type":["string","null"]},"waiting":{"type":"object","properties":{"kind":{"type":"string","enum":["scheduled","awaiting_approval","planner_off","unscheduled"]},"at":{"type":"string"}},"required":["kind"],"description":"Why a written piece is not live yet"},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title","phase","updatedAt","waiting"]}}},"required":["count","items"],"description":"Written posts given a day to go live after they were written (schedule_article_publish)"},"readyNotLive":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"phase":{"type":"string"},"updatedAt":{"type":["string","null"]},"waiting":{"type":"object","properties":{"kind":{"type":"string","enum":["scheduled","awaiting_approval","planner_off","unscheduled"]},"at":{"type":"string"}},"required":["kind"],"description":"Why a written piece is not live yet"},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title","phase","updatedAt","waiting"]}}},"required":["count","items"]},"live":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"publishedAt":{"type":["string","null"]},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title","publishedAt"]}}},"required":["count","items"]},"imported":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title"]}}},"required":["count","items"]},"pendingReview":{"type":"object","properties":{"count":{"type":"number"},"items":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":["string","null"]},"autoPublishAt":{"description":"When this finished draft goes live with nobody acting (a past time = on the next sweep), or null when it waits for a person: manual approval, a named reviewer, a hold, or the Planner off","type":["string","null"]},"coverImage":{"description":"The post's cover image URL, or null when it has none","type":["string","null"]}},"required":["articleId","title","updatedAt","autoPublishAt"]}}},"required":["count","items"],"description":"Finished drafts, never published"},"planner":{"type":"object","properties":{"enabled":{"type":"boolean"},"approvalMode":{"type":"string","enum":["autopilot","review_window","manual"]},"nextSlots":{"type":"array","items":{"type":"string"}}},"required":["enabled","approvalMode","nextSlots"]}},"required":["beingWritten","datedNotLive","readyNotLive","live","imported","pendingReview","planner"]}},{"name":"get_measure_summary","description":"How is this org doing in AI answers and search? The Measure Overview as data: how often the AI answers to the questions WE ask the engines (our probes, list_probes; not real users' chats) NAME the client and CITE one of their pages (each a hits/asks rate with a 95% interval and a trend that only says up or down when the windows' intervals separate), breakthrough questions (one answer did both), share of voice (`shareOfVoice`: the target-question line and the every-question line for the period — level, chart points each compared directly with today on the questions × engines asked in both, the period's change with its likely range, the top rival, what moved it; and `shareOfVoice.named`, the same lines on the mention rate — the share of every answer naming the client, compared on the same questions × engines, the number the Measure card's Named toggle shows; docs/architecture/share-of-voice.md), Search Console clicks/impressions for the marketing site and the phantom network kept apart, each ranking target's standing, and `actions` — what to do next, most important first (each with the app path where it's done; nothing in it has been done). Two are installs (`persistent`): connect_search (Install Google Search Console, until the client's own property is picked) and add_tracking (Add AI tracking: the Visitors / LetterVisit script, lettervisit.com/v.js — not Letterprove — until it is seen on the client's site; an agent does it with `tool`, visitors_open_install_pr, or, when no repo builds the site, installs visitors_get_script's tag itself). Every block states its own window. Pass `parts` to get only some blocks — `ai` is the slow one on a large org. `audience` says whether the org has a main blog with us or phantom sites only (phantoms_only scopes cited, breakthroughs and search to the phantom sites); `pageAccess` is AI answer engines reading the phantom sites day by day; `targets.weekly` is the ranking-target named rate week by week. Drill into the evidence with get_ai_visibility_bank, the questions with list_probes, traffic with get_reads (main site and phantoms; get_page_access is phantom sites only). Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window for the AI-answer and search figures (default 30d). AI answers end today; search ends ~2 days ago, when Google finalises a day. Each block reports its own window.","type":"string","enum":["14d","30d","90d"]},"parts":{"description":"Only these blocks (default: all). `ai` is the slow one on a large org — ask for the others alone when you only need them.","minItems":1,"type":"array","items":{"type":"string","enum":["audience","ai","shareOfVoice","pageAccess","search","actions","targets"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["14d","30d","90d"]},"audience":{"type":"string","enum":["main_blog","phantoms_only"],"description":"main_blog: we publish the client's own blog, so every number applies. phantoms_only: we run phantom sites only, so `ai.cited`, `ai.breakthroughQuestions` and search count the phantom sites alone."},"ai":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"previousWindow":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"probing":{"type":"boolean","description":"AI-answer probing is on for the org. False = not measured."},"answers":{"type":"number","description":"AI answers collected in the window."},"named":{"type":"object","properties":{"hits":{"type":"number","description":"Answers that met the bar."},"asks":{"type":"number","description":"Answers collected — the denominator."},"point":{"type":"number","description":"hits / asks, 0..1 (0 when nothing was asked — check asks)."},"lo":{"type":"number","description":"95% Wilson interval, lower bound."},"hi":{"type":"number","description":"95% Wilson interval, upper bound."},"trend":{"type":"object","properties":{"direction":{"type":"string","enum":["up","flat","down","new"],"description":"up/down only when the two windows' intervals separate; flat = inside the noise; new = no prior sample."},"deltaPts":{"description":"Change in percentage points vs the previous window.","type":["number","null"]}},"required":["direction","deltaPts"]}},"required":["hits","asks","point","lo","hi","trend"],"description":"Answers that named the client."},"cited":{"type":"object","properties":{"hits":{"type":"number","description":"Answers that met the bar."},"asks":{"type":"number","description":"Answers collected — the denominator."},"point":{"type":"number","description":"hits / asks, 0..1 (0 when nothing was asked — check asks)."},"lo":{"type":"number","description":"95% Wilson interval, lower bound."},"hi":{"type":"number","description":"95% Wilson interval, upper bound."},"trend":{"type":"object","properties":{"direction":{"type":"string","enum":["up","flat","down","new"],"description":"up/down only when the two windows' intervals separate; flat = inside the noise; new = no prior sample."},"deltaPts":{"description":"Change in percentage points vs the previous window.","type":["number","null"]}},"required":["direction","deltaPts"]}},"required":["hits","asks","point","lo","hi","trend"],"description":"Answers that cited one of the client's pages (their own site or a phantom site; phantom sites only when audience is phantoms_only)."},"citedBy":{"type":"object","properties":{"marketingSite":{"description":"Null when audience is phantoms_only (not reported).","type":["number","null"]},"phantomSites":{"type":"number"}},"required":["marketingSite","phantomSites"],"description":"Cited answers by whose page was cited. An answer citing both counts in each."},"breakthroughQuestions":{"type":"number","description":"Distinct questions where one answer both cited the client's page and named them."},"previousBreakthroughQuestions":{"type":"number"}},"required":["window","previousWindow","probing","answers","named","cited","citedBy","breakthroughQuestions","previousBreakthroughQuestions"]},"shareOfVoice":{"type":"object","properties":{"counted":{"type":"boolean","description":"False until the org's first count against its current competitor list completed"},"recounting":{"type":"boolean","description":"A recount against a changed list is running; these are the last complete count"},"period":{"type":"string","enum":["14d","30d","90d"]},"periodDays":{"type":"number"},"today":{"type":"string"},"probingStart":{"type":["string","null"]},"targets":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}],"description":"Your target questions; null without active targets with answers"},"every":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}],"description":"Every question we ask — the line the client's screens show until target questions are the headline"},"named":{"type":"object","properties":{"targets":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}]},"every":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}]}},"required":["targets","every"],"description":"The mention rate (Named): the same lines read on EVERY answer — the share of answers that named the client — with the same pairs, votes, windows, likely ranges and moves. `noOne` is 0 and `rival` null on these lines; `answers` counts every answer"},"names":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Entity key → display name (\"brand\" = the client)"},"questions":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Question key → its text"}},"required":["counted","recounting","period","periodDays","today","probingStart","targets","every","named","names","questions"],"description":"Share of voice — the one measure (docs/architecture/share-of-voice.md): the client's target-question line and every-question line for the period, each with its level, chart points compared directly with today, the period's change with its likely range, the top rival and what moved it."},"pageAccess":{"type":"object","properties":{"sites":{"type":"number","description":"Phantom sites reporting page access. 0 = not measured."},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"previousWindow":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"aiReads":{"type":"number","description":"Estimated live AI fetches of the phantom sites in the window."},"previousAiReads":{"description":"The window before; null when telemetry began inside the current window.","type":["number","null"]},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"aiReads":{"type":"number"}},"required":["date","aiReads"]}}},"required":["sites","window","previousWindow","aiReads","previousAiReads","days"],"description":"AI answer engines reading the phantom sites. Windows end yesterday (today is still filling in)."},"search":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Finalised Search Console days (ends ~2 days ago)."},"previousWindow":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"marketingSite":{"type":"object","properties":{"connected":{"type":"boolean","description":"Search Console is connected for this side. False = not measured, not zero."},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previousTotals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["connected","totals","previousTotals"],"description":"The client's own site — published posts only."},"phantomSites":{"type":"object","properties":{"connected":{"type":"boolean","description":"Search Console is connected for this side. False = not measured, not zero."},"totals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["clicks","impressions","ctr","position"]},"previousTotals":{"type":"object","properties":{"clicks":{"type":"number"},"impressions":{"type":"number"},"ctr":{"type":["number","null"]},"position":{"type":["number","null"]}},"required":["clicks","impressions","ctr","position"]}},"required":["connected","totals","previousTotals"],"description":"The phantom network — published posts only. Never summed with the marketing site."},"phantomPages":{"type":"object","properties":{"published":{"type":"number"},"showing":{"type":"number"}},"required":["published","showing"],"description":"Phantom posts live, and how many Google has shown at least once (Search Console)."}},"required":["window","previousWindow","marketingSite","phantomSites","phantomPages"]},"actions":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["connect_search","add_tracking","pick_targets","targets_not_landing","rival_gaps","review_competitors"]},"title":{"type":"string"},"detail":{"type":"string"},"tab":{"type":"string","enum":["overview","targets","ai","search","reads"]},"href":{"type":"string","description":"App path where the action is done."},"hrefLabel":{"type":"string"},"ask":{"description":"The request, phrased for the assistant; null when there's nothing to ask.","type":["string","null"]},"tool":{"description":"The tool that does it (add_tracking = install the Visitors / LetterVisit script, never Letterprove: visitors_open_install_pr; with no repo building the site, install visitors_get_script's tag instead); null when only a person can (a Google sign-in).","type":["string","null"]},"persistent":{"type":"boolean","description":"An install (connect_search, add_tracking): it stays listed until done — a property picked, the script seen on the site."}},"required":["kind","title","detail","tab","href","hrefLabel","ask","tool","persistent"]},"description":"What to do next, most important first. Nothing here has been done."},"targets":{"type":"object","properties":{"windowDays":{"type":"number","const":28,"description":"Targets are judged on a fixed trailing 28 days, whatever `period` is."},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"phrase":{"type":"string"},"standing":{"type":"string","enum":["landing","trending","building","measuring","not_landing"]},"named":{"type":"object","properties":{"hits":{"type":"number","description":"Answers that met the bar."},"asks":{"type":"number","description":"Answers collected — the denominator."},"point":{"type":"number","description":"hits / asks, 0..1 (0 when nothing was asked — check asks)."},"lo":{"type":"number","description":"95% Wilson interval, lower bound."},"hi":{"type":"number","description":"95% Wilson interval, upper bound."}},"required":["hits","asks","point","lo","hi"],"description":"Answers to this target's questions that named the client."}},"required":["id","phrase","standing","named"]}},"counts":{"type":"object","propertyNames":{"type":"string","enum":["landing","trending","building","measuring","not_landing"]},"additionalProperties":{"type":"number"},"required":["landing","trending","building","measuring","not_landing"]},"weekly":{"type":"array","items":{"type":"object","properties":{"weekOf":{"type":"string"},"asks":{"type":"number"},"named":{"type":"number"}},"required":["weekOf","asks","named"]},"description":"All active targets together, per ISO week: of `asks` answers, `named` named the client."}},"required":["windowDays","items","counts","weekly"]}},"required":["period"]}},{"name":"get_reads","description":"Who reads this org's sites — Measure → Reads as data (docs/architecture/measure-console.md): AI answer engines and people, across the org's own sites and its phantom sites, counted one way. `aiReads` are requests from an AI agent fetching a page to answer a person (purpose live) — our own GEO probes taken out on every source; index and training crawls are `crawls`, beside it. `people` are human page loads (a site on Visitors) or beacon visits — a count, never who: identified people and companies are visitors_get_people; `fromAi` the visits that arrived from an AI answer. Each site reads one source (`sites[].source`): a main site on Visitors from the day it moved (`countedFrom`; `since` says when counting starts inside the window), others from our own collection. `change` is like for like — only sites counted for both whole windows — and null until one is. Narrow with `sites` (any of all, own, phantoms, or `sites[].key`s — your main site and two phantoms, say), `path` (one page, with its one site) and `engine` (an operator from `engines`). Windows end yesterday. The same read the Measure screen shows; the per-page detail of a main site is visitors_get_page_traffic.\n---\noutput.people: Human page loads (Visitors) or beacon visits (LetterVisit): a count of visits, anonymous included, not identified people (visitors_get_people). A site with source none counts nothing here, not zero. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window (default 30d): the last 14 / 30 / 90 whole days ending yesterday, against the same length before.","type":"string","enum":["14d","30d","90d"]},"sites":{"description":"Which sites (default all), any of: own = your own sites, phantoms = your phantom sites, or a site's key from `sites[].key` (own:<id>, own:collection:<id> for a main blog not on Visitors yet, or phantom:<id>) — e.g. your main site and two phantoms.","maxItems":200,"type":"array","items":{"type":"string","pattern":"^(all|own|phantoms|own:(collection:)?[0-9a-f-]{36}|phantom:[0-9a-f-]{36})$"}},"path":{"description":"One page, as `pages[].path` gives it. Needs `sites` set to that page's one site key.","type":"string","maxLength":2048},"engine":{"description":"One AI engine's operator, as `engines[].operator` gives it (openai, anthropic, google, perplexity …).","type":"string","pattern":"^[a-z0-9_.-]{1,40}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["14d","30d","90d"]},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"previousWindow":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive YYYY-MM-DD window."},"filter":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"string"}},"path":{"type":["string","null"]},"engine":{"type":["string","null"]}},"required":["sites","path","engine"]},"sites":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"own:<owned site id> or phantom:<phantom id> — pass as `site`."},"brandSubject":{"type":"string","enum":["client","independent"],"description":"Whose brand it publishes under: client = your own site, independent = a phantom site."},"name":{"type":"string"},"host":{"type":["string","null"]},"source":{"type":"string","enum":["visitors","lettervisit","none"],"description":"visitors: the visitor pixel and server snippet; lettervisit: our own collection; none: nothing installed."},"countedFrom":{"description":"The first day counted (a site on Visitors starts over on its move).","type":["string","null"]},"comparable":{"type":"boolean","description":"Counted for the whole previous window, so it is in `change`."}},"required":["key","brandSubject","name","host","source","countedFrom","comparable"]}},"totals":{"type":"object","properties":{"aiReads":{"type":"number","description":"Requests from AI agents fetching a page to answer a person (purpose live), our own probes taken out."},"crawls":{"type":"number","description":"AI and search crawls (index, training): beside AI reads, never added in."},"people":{"type":"number","description":"Human page loads (Visitors) or beacon visits (LetterVisit)."},"fromAi":{"type":"number","description":"Of the people, visits that arrived from an AI answer."}},"required":["aiReads","crawls","people","fromAi"]},"verifiedAiReads":{"description":"Of aiReads, those verified against the vendor's published IP ranges; null when no source can verify.","type":["number","null"]},"ownProbeExcluded":{"type":"number","description":"Live AI requests left out as our own GEO probes."},"since":{"description":"When counting starts inside the window for some site in scope (\"since\" this day); null otherwise.","type":["string","null"]},"change":{"anyOf":[{"type":"object","properties":{"sites":{"type":"number"},"now":{"type":"object","properties":{"aiReads":{"type":"number","description":"Requests from AI agents fetching a page to answer a person (purpose live), our own probes taken out."},"crawls":{"type":"number","description":"AI and search crawls (index, training): beside AI reads, never added in."},"people":{"type":"number","description":"Human page loads (Visitors) or beacon visits (LetterVisit)."},"fromAi":{"type":"number","description":"Of the people, visits that arrived from an AI answer."}},"required":["aiReads","crawls","people","fromAi"]},"then":{"type":"object","properties":{"aiReads":{"type":"number","description":"Requests from AI agents fetching a page to answer a person (purpose live), our own probes taken out."},"crawls":{"type":"number","description":"AI and search crawls (index, training): beside AI reads, never added in."},"people":{"type":"number","description":"Human page loads (Visitors) or beacon visits (LetterVisit)."},"fromAi":{"type":"number","description":"Of the people, visits that arrived from an AI answer."}},"required":["aiReads","crawls","people","fromAi"]}},"required":["sites","now","then"]},{"type":"null"}],"description":"Like for like: only sites counted for both whole windows. Null until one is."},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"aiReads":{"type":"number"},"people":{"type":"number"}},"required":["date","aiReads","people"]}},"siteDays":{"type":"array","items":{"type":"object","properties":{"site":{"type":"string"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"aiReads":{"type":"number"},"people":{"type":"number"}},"required":["date","aiReads","people"]}}},"required":["site","days"]},"description":"The same days per site in scope, from the day each is counted: one line per site, never summed."},"engines":{"type":"array","items":{"type":"object","properties":{"operator":{"type":"string"},"name":{"type":"string"},"aiReads":{"type":"number"},"crawls":{"type":"number"},"change":{"anyOf":[{"type":"object","properties":{"now":{"type":"number"},"then":{"type":"number"}},"required":["now","then"]},{"type":"null"}],"description":"AI reads on the comparable sites, this window and the one before; null when nothing compares."}},"required":["operator","name","aiReads","crawls","change"]}},"pages":{"type":"array","items":{"type":"object","properties":{"site":{"type":"string"},"path":{"type":"string"},"aiReads":{"type":"number"},"crawls":{"type":"number"},"people":{"type":"number"},"fromAi":{"type":"number"},"change":{"anyOf":[{"type":"object","properties":{"now":{"type":"number"},"then":{"type":"number"}},"required":["now","then"]},{"type":"null"}],"description":"AI reads on the comparable sites, this window and the one before; null when nothing compares."}},"required":["site","path","aiReads","crawls","people","fromAi","change"]}},"bySite":{"type":"array","items":{"type":"object","properties":{"aiReads":{"type":"number","description":"Requests from AI agents fetching a page to answer a person (purpose live), our own probes taken out."},"crawls":{"type":"number","description":"AI and search crawls (index, training): beside AI reads, never added in."},"people":{"type":"number","description":"Human page loads (Visitors) or beacon visits (LetterVisit)."},"fromAi":{"type":"number","description":"Of the people, visits that arrived from an AI answer."},"site":{"type":"string"},"change":{"anyOf":[{"type":"object","properties":{"now":{"type":"number"},"then":{"type":"number"}},"required":["now","then"]},{"type":"null"}],"description":"AI reads on the comparable sites, this window and the one before; null when nothing compares."}},"required":["aiReads","crawls","people","fromAi","site","change"]}},"floors":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["bots","verified","live_reads","citations","people"]},"notes":{"type":"array","items":{"type":"string"}}},"required":["measure","notes"]},"description":"What these numbers can't see: AI reads, people and visits from AI answers are all floors."}},"required":["period","window","previousWindow","filter","sites","totals","verifiedAiReads","ownProbeExcluded","since","change","days","siteDays","engines","pages","bySite","floors"]}},{"name":"list_measure_questions","description":"Every question we ask the AI engines for this org, one row each, with how it is doing — Measure → Questions as data. A question is share of voice's unit: a ranking target with all its wordings merged (`t:`, with its `priority`), a phantom post's page probes (`a:`), or one probe (`q:`). Each row: `state` (asked, paused, held — with `held_reason` — or retired but answered in the window), how it was asked (`types`), its brand-naming `answers` and how many `named` you, your `share` of them and its `change` on its own pairs asked in both windows (docs/architecture/share-of-voice.md), the top `rival`, and the answers that `cited` your page and were `breakthroughs` (cited and named you). Targets first by priority, then most named. Open one with get_share_of_voice_question (`key`); pause, resume or keep its probes with control_probe, one call per id in `probe_ids`; a target's priority is set in Memory → Ranking goals (update_ranking_target). Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window (default 30d) for the answers, named, share, change and citations of each question.","type":"string","enum":["14d","30d","90d"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["14d","30d","90d"]},"counted":{"type":"boolean","description":"False until the org's first count against its competitor list completed."},"questions":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"t:<target> / a:<post> / q:<probe> — pass to get_share_of_voice_question."},"text":{"type":"string"},"kind":{"type":"string","enum":["target","post","probe"]},"target_id":{"type":["string","null"]},"article_id":{"type":["string","null"]},"probe_ids":{"type":"array","items":{"type":"string"},"description":"The probes asked under it — control one with control_probe."},"priority":{"anyOf":[{"type":"string","enum":["high","normal","low"]},{"type":"null"}],"description":"A ranking target's priority (its vote)."},"state":{"type":"string","enum":["asked","paused","held","retired"]},"held_reason":{"type":["string","null"]},"types":{"type":"array","items":{"type":"string","enum":["list","general"]}},"answers":{"type":"number","description":"Brand-naming answers in the window."},"named":{"type":"number","description":"Of them, answers that named you."},"share":{"description":"Your voice across them, 0..1.","type":["number","null"]},"change":{"description":"Change on its own pairs asked in both windows (0.01 = 1 pt).","type":["number","null"]},"rival":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"share":{"type":"number"}},"required":["name","share"]},{"type":"null"}]},"cited":{"type":"number","description":"Answers that cited one of your pages."},"breakthroughs":{"type":"number","description":"Answers that cited your page and named you."},"pages":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"url":{"type":"string"},"title":{"type":["string","null"]},"kind":{"type":"string","enum":["client","phantom"]},"site":{"type":"string"},"answers":{"type":"number","description":"This question's answers that cited it."},"breakthroughAnswers":{"type":"number","description":"Of them, the ones that also named you."}},"required":["key","url","title","kind","site","answers","breakthroughAnswers"]},"description":"The pages of yours its answers cited, won first, then most cited (top five)."}},"required":["key","text","kind","target_id","article_id","probe_ids","priority","state","held_reason","types","answers","named","share","change","rival","cited","breakthroughs","pages"]}}},"required":["period","counted","questions"]}},{"name":"list_cited_pages","description":"Your pages that AI answers to our probes (the questions we ask the engines, list_probes) cited — the main site's and each phantom's — page by page, best first: Measure → Pages as data. Not visits: clicks from real AI answers are visitors_get_citations, AI agents fetching pages are get_reads. Each page (`key` = host + path, so tracking parameters collapse onto it; `url` a link an answer actually used) with its site (`kind` client = main site, phantom = `blogId`), the post behind it (`articleId`, `title`), the answers that cited it (`citedAnswers`), the questions they answered (`questions`), and its wins: `breakthroughAnswers` (cited it AND named you, not negatively, on a question that didn't name you first) and `wonQuestions`. `topQuestions`: up to ten questions it was cited for, won first. Ordered by questions won, then answers. `sites` narrows to Reads' site keys (own, phantoms, own:<id>, phantom:<id>). Counts are distinct answers and questions; `totals` never sums answers across pages. The same pages per question are on list_measure_questions' `pages`. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window (default 30d).","type":"string","enum":["14d","30d","90d"]},"sites":{"description":"Which sites (default all), Reads' keys: own = your main site, phantoms = your phantom sites, or a site's key from get_reads' `sites[].key` (own:<id> / phantom:<id>).","maxItems":200,"type":"array","items":{"type":"string","pattern":"^(all|own|phantoms|own:(collection:)?[0-9a-f-]{36}|phantom:[0-9a-f-]{36})$"}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["14d","30d","90d"]},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"]},"sites":{"type":"array","items":{"type":"string"},"description":"The sites asked for; empty is every site."},"totals":{"type":"object","properties":{"pages":{"type":"number"},"wonPages":{"type":"number"}},"required":["pages","wonPages"],"description":"Pages cited, and of them pages that won a question. Answers are never summed across pages."},"pages":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The page: host + path, lowercased, no scheme, www, query or trailing slash."},"url":{"type":"string","description":"A URL an answer actually linked."},"kind":{"type":"string","enum":["client","phantom"],"description":"client = your main site; phantom = one of your phantom sites."},"blogId":{"description":"The phantom it is on; null on the main site.","type":["string","null"]},"site":{"type":"string"},"host":{"type":["string","null"]},"articleId":{"type":["string","null"]},"title":{"type":["string","null"]},"citedAnswers":{"type":"number","description":"Answers that cited it."},"questions":{"type":"number","description":"Questions whose answers cited it."},"breakthroughAnswers":{"type":"number","description":"Of those answers, the ones that also named you."},"wonQuestions":{"type":"number","description":"Questions it won: an answer cited it and named you."},"topQuestions":{"type":"array","items":{"type":"object","properties":{"promptId":{"type":"string"},"question":{"type":"string"},"answers":{"type":"number"},"breakthroughAnswers":{"type":"number"}},"required":["promptId","question","answers","breakthroughAnswers"]}}},"required":["key","url","kind","blogId","site","host","articleId","title","citedAnswers","questions","breakthroughAnswers","wonQuestions","topQuestions"]}}},"required":["period","window","sites","totals","pages"]}},{"name":"list_site_mentions","description":"Do your phantom sites name you? Measure → Pages → Naming you as data: each phantom site with how many of its published posts name you (`naming` of `published`) and the posts that do (`posts`: title, live `url`, `count` of times named, most first). Read from the mention audit each post got when it published, not re-detected; a post published before the audit existed is in `unaudited` — unknown, not \"not naming you\". `sites` narrows to phantoms or `phantom:<id>` keys from get_reads. Whether AI answers then cite those posts is list_cited_pages. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"sites":{"description":"Which phantom sites (default all): phantoms, or a site's key from get_reads' `sites[].key` (phantom:<id>). Your main site (own) has no phantom posts, so it adds nothing.","maxItems":200,"type":"array","items":{"type":"string","pattern":"^(all|own|phantoms|own:(collection:)?[0-9a-f-]{36}|phantom:[0-9a-f-]{36})$"}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"string"},"description":"The sites asked for; empty is every phantom site."},"totals":{"type":"object","properties":{"sites":{"type":"number"},"published":{"type":"number","description":"Published posts across the sites."},"naming":{"type":"number","description":"Of them, the posts that name you."},"unaudited":{"type":"number","description":"Published before the mention audit existed: not known either way."}},"required":["sites","published","naming","unaudited"]},"items":{"type":"array","items":{"type":"object","properties":{"blogId":{"type":"string"},"name":{"type":"string"},"published":{"type":"number"},"naming":{"type":"number"},"unaudited":{"type":"number"},"posts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"count":{"type":"number","description":"Times the post names you."},"publishedAt":{"type":["string","null"]}},"required":["articleId","title","url","count","publishedAt"]},"description":"The posts that name you, most mentions first (up to 50; `naming` counts them all)."}},"required":["blogId","name","published","naming","unaudited","posts"]},"description":"Every phantom site, those naming you most first."}},"required":["sites","totals","items"]}},{"name":"get_share_of_voice_detail","description":"Who is winning the AI answers, and is it moving? Measure → GEO → Competitors as data — the same read the screen shows (GET /api/measure/sov-detail, `letterstory insights competitors`). Share of voice is COMPETITIVE: it reads only the answers that named at least one tracked brand (the client or a tracked competitor); each splits its voice equally among the brands it names, a pair (question × type × engine:model) holds the client's voice across its brand-naming answers ÷ those answers, and the level averages the pairs by vote (a ranking target 3 — High, or the client's Normal 2 / Low 1 — any other general question 2, a vendor list 1). `targets` / `every`: both lines with points, each compared DIRECTLY with today on the pairs asked in both, and the period's change with its likely range. `named`: the same lines on the mention rate — the share of EVERY answer that named the client, on the same pairs, votes and ranges (the Measure card's Named toggle). `detail` (on every question, `detailSet`): every tracked brand's level and change (`standing`), each engine, each question, and `moves` — the questions that moved the change most, either direction. `names` and `questions` give the keys' display text. `period` (14d / 30d / 90d) is the window; the change compares it with the same length before. `counted: false` until the org's first count against its current competitor list completes. The client's screens read `every` (and `named.every`); `targets` stays for agents until target questions are the headline. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Window (default 30d): the comparison reads the last 2 / 4 / 13 complete weeks (14d / 30d / 90d) against the same number of weeks before.","type":"string","enum":["14d","30d","90d"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"counted":{"type":"boolean","description":"False until the org's first count against its current competitor list completed"},"recounting":{"type":"boolean","description":"A recount against a changed list is running; these are the last complete count"},"period":{"type":"string","enum":["14d","30d","90d"]},"periodDays":{"type":"number"},"today":{"type":"string"},"probingStart":{"type":["string","null"]},"targets":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}],"description":"Your target questions; null without active targets with answers"},"every":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}],"description":"Every question we ask — the line the client's screens show until target questions are the headline"},"named":{"type":"object","properties":{"targets":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}]},"every":{"anyOf":[{"type":"object","properties":{"questionSet":{"type":"string","enum":["targets","every"],"description":"targets = the client's active ranking targets now; every = every question we probe, targets included"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Today's window: the period's length, ending today"},"level":{"type":"number","description":"Share of voice, 0..1, on the answers that named at least one tracked brand (the client or a tracked competitor): each such answer splits its voice equally among the brands it names; the client's voice across a pair's brand-naming answers ÷ those answers, averaged over pairs (question × type × engine:model) by vote × reliability — vote: an active ranking target 3 (High; a client-set Normal 2 or Low 1), any other general question 2, a vendor list 1; reliability: n ÷ (n + 4) on the pair's answers, so a one-answer pair cannot outweigh a forty-answer one. Answers naming no tracked brand are outside share of voice (they count in the mention rate). On a `named` line: the share of EVERY answer that named the client, on the same pairs and votes"},"noOne":{"type":"number","description":"The share of today's answers that named no tracked brand — outside share of voice"},"pairs":{"type":"number","description":"Pairs (question × type × engine:model) with a brand-naming answer in today's window"},"answers":{"type":"number","description":"Brand-naming answers in today's window: share of voice's denominator"},"points":{"type":"array","items":{"type":"object","properties":{"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"offsetDays":{"type":"number","description":"Days before today the window ends"},"value":{"description":"Today's level − the comparison with today on the pairs asked in both; null = not measured. Not that window's level (its questions differ): the screens draw value − level, the change vs today","type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"coveredDays":{"type":"number","description":"Days of answers inside the window — fewer than the period near the start of probing"},"missing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]}},"required":["window","offsetDays","value","lo","hi","sharedPairs","coveredDays","missing"]},"description":"The chart, oldest first; the last point is today. Each point is compared DIRECTLY with today, never chained"},"change":{"anyOf":[{"type":"object","properties":{"delta":{"type":"number","description":"Share today − share in the previous period, on the pairs asked in both"},"lo":{"type":"number"},"hi":{"type":"number"},"se":{"type":"number"},"sharedPairs":{"type":"number"},"sharedQuestions":{"type":"number"},"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"}},"required":["delta","lo","hi","se","sharedPairs","sharedQuestions","from","to"]},{"type":"null"}],"description":"The period's change with its likely range (95%, questions resampled); null = cannot be compared"},"changeMissing":{"anyOf":[{"type":"string","enum":["before_probing","too_few_shared"],"description":"Why a comparison is missing: before_probing = fewer than half the window's days fall after probing started; too_few_shared = fewer than 3 pairs on 3 questions, or 20 brand-naming answers a side, in both windows"},{"type":"null"}]},"firstComparisonOn":{"description":"When no earlier point can be drawn yet because probing started too recently: the day the first one will be","type":["string","null"]},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["key","level","change"]},{"type":"null"}],"description":"The tracked competitor with the highest level on the same pairs; `names[key]` is its name"},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"},"selection":{"anyOf":[{"type":"object","properties":{"total":{"type":"number"},"sameQuestions":{"type":"number"},"noLongerAsked":{"type":"number"},"newQuestions":{"type":"number"}},"required":["total","sameQuestions","noLongerAsked","newQuestions"]},{"type":"null"}],"description":"Every question only: the level's change against the previous period's level, split by where it came from. When noLongerAsked carries most of it, the question set moved the level, not the answers"}},"required":["questionSet","window","level","noOne","pairs","answers","points","change","changeMissing","firstComparisonOn","rival","moves","selection"]},{"type":"null"}]}},"required":["targets","every"],"description":"The mention rate (Named): the same lines read on EVERY answer — the share of answers that named the client — with the same pairs, votes, windows, likely ranges and moves. `noOne` is 0 and `rival` null on these lines; `answers` counts every answer"},"names":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Entity key → display name (\"brand\" = the client)"},"questions":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Question key → its text"},"detailSet":{"anyOf":[{"type":"string","enum":["targets","every"]},{"type":"null"}],"description":"The question set the breakdown is on: every question (null when nothing was measured)"},"detail":{"anyOf":[{"type":"object","properties":{"standing":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"points":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"vsToday":{"type":["number","null"]}},"required":["date","vsToday"]},"description":"The brand's earlier windows against today (oldest first, ending today at 0): its share then minus its share today, on the questions × engines asked in both — the Competitors chart. Empty for a brand beyond you and the four leading rivals; null where a window cannot be compared."}},"required":["key","level","change","lo","hi","points"]}},"engines":{"type":"array","items":{"type":"object","properties":{"engine":{"type":"string"},"model":{"type":"string"},"level":{"type":"number"},"noOne":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"pairs":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"level":{"type":"number"}},"required":["key","level"]},{"type":"null"}]}},"required":["engine","model","level","noOne","change","lo","hi","pairs","rival"]}},"questions":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string"},"questionTypes":{"type":"array","items":{"type":"string","enum":["list","general"]},"description":"How it was asked: a ranking target is asked both as a vendor list and as general questions"},"engines":{"type":"number","description":"Engines (engine:model) whose answers named a tracked brand"},"answers":{"type":"number"},"namedYou":{"type":"number"},"share":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"share":{"type":"number"}},"required":["key","share"]},{"type":"null"}]},"change":{"type":["number","null"]}},"required":["questionKey","questionTypes","engines","answers","namedYou","share","rival","change"]}},"moves":{"type":"array","items":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>; `questions[key]` is its text"},"questionType":{"type":"string","enum":["list","general"]},"delta":{"type":"number","description":"This question's part of the period's change"},"now":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"],"description":"Answers naming the client, of the brand-naming answers"},"then":{"type":"object","properties":{"named":{"type":"number"},"answers":{"type":"number"}},"required":["named","answers"]}},"required":["questionKey","questionType","delta","now","then"]},"description":"The questions that moved the period's change most, either direction"}},"required":["standing","engines","questions","moves"]},{"type":"null"}]}},"required":["counted","recounting","period","periodDays","today","probingStart","targets","every","named","names","questions","detailSet","detail"],"description":"The share-of-voice read (both lines) plus the breakdown on the headline question set: every tracked brand's level and change, each engine, each question, and what moved it. counted: false until the org's first count against its current list completes."}},{"name":"get_share_of_voice_question","description":"One question of the share of voice opened up — the evidence behind a row of get_share_of_voice_detail's `detail.questions` (Measure → GEO → Competitors → By question; GET /api/measure/sov-question, `letterstory insights question <key>`). `question_key` is that row's questionKey (t:<target> merges every wording of a ranking target, a:<post> a post's page probes, q:<probe> one probe). Returns `wordings` — every exact wording the engines were asked in the period's window, vendor lists first, each with its answers, how many named a tracked brand and how many named you — and `answers`: the AI answers themselves, newest first, each with its engine:model, the wording asked, the tracked brands it named (`named`, entity keys; `names` gives display names) and the full text. `filter` narrows them (named_you, rival_not_you, no_one); `counts` gives every filter's total; `limit` 0 returns the wordings and counts without answer text. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"question_key":{"type":"string","pattern":"^[taq]:[0-9a-f-]{36}$","description":"The question, as get_share_of_voice_detail keys it (detail.questions[].questionKey)"},"period":{"description":"Window (default 30d): the last 14 / 30 / 90 days, as the detail reads","type":"string","enum":["14d","30d","90d"]},"filter":{"description":"Which answers to list (default all)","type":"string","enum":["all","named_you","rival_not_you","no_one"]},"limit":{"description":"Answers to list, newest first (default 10, max 100); 0 = the wordings and counts only","type":"integer","minimum":0,"maximum":100}},"required":["question_key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"questionKey":{"type":"string","description":"t:<target> | a:<post> | q:<probe>, as get_share_of_voice_detail keys it"},"text":{"description":"The question: a target's phrase, a post's title, a probe's wording","type":["string","null"]},"period":{"type":"string","enum":["14d","30d","90d"]},"periodDays":{"type":"number"},"window":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"description":"Inclusive days, YYYY-MM-DD (UTC)"},"wordings":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string","description":"The exact wording the engines were asked"},"type":{"type":"string","enum":["list","general"],"description":"list = a vendor-list question; general = any other"},"answers":{"type":"number"},"namingAnswers":{"type":"number","description":"Answers that named at least one tracked brand"},"namedYou":{"type":"number"}},"required":["text","type","answers","namingAnswers","namedYou"]},"description":"Every wording asked for this question in the window, vendor lists first, most asked first"},"answers":{"type":"object","properties":{"filter":{"type":"string","enum":["all","named_you","rival_not_you","no_one"],"description":"all; named_you = answers that named you; rival_not_you = a tracked competitor but not you; no_one = no tracked brand (outside share of voice)"},"total":{"type":"number","description":"Answers in the window matching the filter"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"askedAt":{"type":"string","description":"When the probing run asked it (ISO, UTC)"},"engine":{"type":"string"},"model":{"type":"string"},"type":{"type":"string","enum":["list","general"],"description":"list = a vendor-list question; general = any other"},"wording":{"type":"string"},"named":{"type":"array","items":{"type":"string"},"description":"Tracked brands it named, entity keys (\"brand\" = you); `names` gives display names"},"text":{"type":"string","description":"The answer, as the engine gave it (Markdown)"}},"required":["id","askedAt","engine","model","type","wording","named","text"]},"description":"Newest first, up to the limit"}},"required":["filter","total","items"]},"counts":{"type":"object","propertyNames":{"type":"string","enum":["all","named_you","rival_not_you","no_one"],"description":"all; named_you = answers that named you; rival_not_you = a tracked competitor but not you; no_one = no tracked brand (outside share of voice)"},"additionalProperties":{"type":"number"},"required":["all","named_you","rival_not_you","no_one"],"description":"Answers in the window by filter: all, named_you, rival_not_you, no_one"},"names":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Entity key → display name (\"brand\" = the client)"}},"required":["questionKey","text","period","periodDays","window","wordings","answers","counts","names"],"description":"One question of the share of voice opened up, in the period's window: every wording the engines were asked for it with how its answers went, and the answers themselves (newest first, filtered), each with the tracked brands it named."}},{"name":"get_brand_detection","description":"How your brand is detected in AI answers — the terms behind every 'named' count, the named rate and share of voice. `inferred` is what detection would use on its own (your name, domain label, product name, aliases, matched in any case); `override` is an exact set staff set when the inferred one mis-counted (a name that is also an ordinary word: 'Context' matching 'context window'), with when and why (set by 'Letter staff'); `cleared` is when staff went back to the inferred set; `history` lists earlier settings and clears, newest first; `effective` is what answers are matched against now — terms in any case, exact-case terms counted only as written, and excluded phrases that contain a term but are not you. A change is followed by a recount of your history against the new set. Read-only by design: only staff set it, so no one can move their own share of voice — ask your account team to change it. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"inferred":{"type":"object","properties":{"terms":{"type":"array","items":{"type":"string"}}},"required":["terms"],"description":"The set detection infers with no override: org name, domain label, product name, aliases"},"override":{"anyOf":[{"type":"object","properties":{"terms":{"type":"array","items":{"type":"string"}},"exactCaseTerms":{"type":"array","items":{"type":"string"}},"excludePhrases":{"type":"array","items":{"type":"string"}},"setBy":{"description":"Who set it: \"Letter staff\" to a client (a staff member's email only to staff)","type":["string","null"]},"setAt":{"type":["string","null"]},"note":{"description":"Why it was set","type":["string","null"]}},"required":["terms","exactCaseTerms","excludePhrases","setBy","setAt","note"]},{"type":"null"}],"description":"The exact set a person (staff) set because the inferred one mis-counted; null = inferred"},"cleared":{"anyOf":[{"type":"object","properties":{"by":{"description":"Who cleared it (\"Letter staff\" to a client)","type":["string","null"]},"at":{"type":["string","null"]},"note":{"description":"Why it was cleared","type":["string","null"]}},"required":["by","at","note"]},{"type":"null"}],"description":"When staff cleared the exact set, back to the inferred one; null otherwise"},"history":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string","enum":["set","cleared"]},"terms":{"type":"array","items":{"type":"string"}},"exactCaseTerms":{"type":"array","items":{"type":"string"}},"excludePhrases":{"type":"array","items":{"type":"string"}},"by":{"description":"Who decided (\"Letter staff\" to a client)","type":["string","null"]},"at":{"type":["string","null"]},"note":{"type":["string","null"]}},"required":["action","terms","exactCaseTerms","excludePhrases","by","at","note"]},"description":"Earlier settings and clears of the exact set, newest first (bounded)"},"effective":{"type":"object","properties":{"terms":{"type":"array","items":{"type":"string"},"description":"Matched in any case"},"exactCaseTerms":{"type":"array","items":{"type":"string"},"description":"Counted only as written"},"excludePhrases":{"type":"array","items":{"type":"string"},"description":"Text containing a term that is not the brand"}},"required":["terms","exactCaseTerms","excludePhrases"],"description":"What AI answers are matched against now, for the named rate and share of voice"}},"required":["orgId","orgName","inferred","override","cleared","history","effective"],"description":"How the client's brand is detected in AI answers"}},{"name":"get_measurement_seams","description":"Before reading any trend as a trend: the SEAMS this org's series cross (dates on which a number changed meaning without the thing it measures changing — a competitor-list change, ranking targets going live, the question set growing, an engine added or failing, a classifier version, a model switch), its row in the fleet's share-of-voice comparison (level and the fleet period's change on its target questions and every question, compared directly on the pairs asked in both, with probing and phantom age) and the same row on the mention rate (`mentionRate`: the share of every answer that named it), each phantom site's validated live AI reads per day with a verdict that only says up or down when the change clears the noise, and the REASONS behind each delta in four families: measurement changed, ground changed (first citation, first mention, breakthrough), composition changed (sites too young, new sites), instrument wrong (an ordinary-word brand term, a missing alias, a self-brand rival). Same objects the staff Measure view draws; computed every 30 minutes. `shareOfVoice` is null when the org is not actively probing. `period` picks the fleet period (30d default; 14d compares a client once it has 20 days of probing instead of 44). Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"The fleet period the share-of-voice row is read on: last N days vs the N before. Default 30d.","type":"string","enum":["14d","30d","90d"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generatedAt":{"type":"string"},"window":{"type":"object","properties":{"fromStart":{"type":"string"},"toEnd":{"type":"string"},"latestWeek":{"type":"string"}},"required":["fromStart","toEnd","latestWeek"],"description":"The two compared windows: the last 2 complete weeks vs the 2 before."},"seams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"scope":{"type":"string","enum":["fleet","org","site"]},"scopeId":{"type":["string","null"]},"metric":{"type":"string","enum":["access","geo","all"],"description":"Which series the seam breaks: page access, answer-derived (share of voice, named rate, citations), or both."},"kind":{"type":"string"},"date":{"type":"string","description":"YYYY-MM-DD: the first day the new meaning applies."},"endDate":{"description":"Inclusive end for a window seam.","type":"string"},"label":{"type":"string"},"note":{"type":"string","description":"What changed and what a line crossing it may claim."},"source":{"type":"string","enum":["register","derived"]}},"required":["id","scope","scopeId","metric","kind","date","label","note","source"]},"description":"Fleet seams plus this org's and its sites' derived seams, oldest first."},"shareOfVoice":{"anyOf":[{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"paying":{"type":"boolean"},"probingAgeDays":{"type":["number","null"]},"phantomAgeDays":{"description":"Days since the client's oldest phantom site went live","type":["number","null"]},"sites":{"type":"number"},"status":{"type":"string","enum":["compared","too_new","not_counted","too_few_shared"],"description":"compared (its own chart shows the period's change), or why not: the period before not yet half probed, not yet counted, too little in common"},"every":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["name","level","change"]},{"type":"null"}]}},"required":["level","change","lo","hi","sharedPairs","rival"]},{"type":"null"}]},"targets":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["name","level","change"]},{"type":"null"}]}},"required":["level","change","lo","hi","sharedPairs","rival"]},{"type":"null"}]}},"required":["orgId","orgName","paying","probingAgeDays","phantomAgeDays","sites","status","every","targets"]},{"type":"null"}],"description":"This client's row in the fleet's share-of-voice comparison on the requested fleet period (default 30d): level and the period's change on both question sets, probing and phantom age, and whether it is compared. Null when it is not in the snapshot (not actively probing)."},"mentionRate":{"anyOf":[{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"paying":{"type":"boolean"},"probingAgeDays":{"type":["number","null"]},"phantomAgeDays":{"description":"Days since the client's oldest phantom site went live","type":["number","null"]},"sites":{"type":"number"},"status":{"type":"string","enum":["compared","too_new","not_counted","too_few_shared"],"description":"compared (its own chart shows the period's change), or why not: the period before not yet half probed, not yet counted, too little in common"},"every":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["name","level","change"]},{"type":"null"}]}},"required":["level","change","lo","hi","sharedPairs","rival"]},{"type":"null"}]},"targets":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"sharedPairs":{"type":"number"},"rival":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"level":{"type":"number"},"change":{"type":["number","null"]}},"required":["name","level","change"]},{"type":"null"}]}},"required":["level","change","lo","hi","sharedPairs","rival"]},{"type":"null"}]}},"required":["orgId","orgName","paying","probingAgeDays","phantomAgeDays","sites","status","every","targets"]},{"type":"null"}],"description":"This client's row in the fleet's mention-rate comparison (Named: the share of every answer that named the client) on the same period, clients and pairs as `shareOfVoice`. Null when it is not in the snapshot, or the snapshot predates schema 8."},"shareOfVoiceReasons":{"type":"array","items":{"type":"object","properties":{"family":{"type":"string","enum":["measurement","ground","composition","instrument"],"description":"measurement: the instrument changed; ground: the thing measured changed; composition: who is in the pool changed; instrument: the instrument is wrong for this org."},"kind":{"type":"string"},"date":{"type":["string","null"]},"label":{"type":"string"},"detail":{"type":"string"}},"required":["family","kind","date","label","detail"]}},"phantoms":{"type":"array","items":{"type":"object","properties":{"blogId":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"]},"firstReportingDay":{"type":"string"},"daysReporting":{"type":"number"},"ageDaysLive":{"type":["number","null"]},"band":{"type":"string","enum":["new","establishing","maturing","established","unknown"]},"weekly":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","description":"Week start (Monday, UTC) or an aligned index like W3."},"value":{"description":"Null when not measured — a gap, never a zero.","type":["number","null"]},"n":{"type":"number","description":"Sample behind the value: reporting days, or namings."},"state":{"type":"string","enum":["measured","not_measured","excluded"]}},"required":["at","value","n","state"]},"description":"Validated live AI reads per reporting day, by week."},"delta":{"type":"object","properties":{"from":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"value":{"type":["number","null"]},"n":{"type":"number"},"weeks":{"type":"number"}},"required":["start","end","value","n","weeks"]},"to":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"value":{"type":["number","null"]},"n":{"type":"number"},"weeks":{"type":"number"}},"required":["start","end","value","n","weeks"]},"delta":{"type":["number","null"]},"relative":{"type":["number","null"]},"verdict":{"type":"string","enum":["up","down","flat","insufficient"],"description":"up/down only when the change clears the noise floor (Wilson separation for shares, 20% for per-day means)."},"comparable":{"type":"boolean","description":"False when a seam falls inside or between the windows — read `crossed` before the verdict."},"crossed":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"scope":{"type":"string","enum":["fleet","org","site"]},"scopeId":{"type":["string","null"]},"metric":{"type":"string","enum":["access","geo","all"],"description":"Which series the seam breaks: page access, answer-derived (share of voice, named rate, citations), or both."},"kind":{"type":"string"},"date":{"type":"string","description":"YYYY-MM-DD: the first day the new meaning applies."},"endDate":{"description":"Inclusive end for a window seam.","type":"string"},"label":{"type":"string"},"note":{"type":"string","description":"What changed and what a line crossing it may claim."},"source":{"type":"string","enum":["register","derived"]}},"required":["id","scope","scopeId","metric","kind","date","label","note","source"]}}},"required":["from","to","delta","relative","verdict","comparable","crossed"]},"reasons":{"type":"array","items":{"type":"object","properties":{"family":{"type":"string","enum":["measurement","ground","composition","instrument"],"description":"measurement: the instrument changed; ground: the thing measured changed; composition: who is in the pool changed; instrument: the instrument is wrong for this org."},"kind":{"type":"string"},"date":{"type":["string","null"]},"label":{"type":"string"},"detail":{"type":"string"}},"required":["family","kind","date","label","detail"]}}},"required":["blogId","name","domain","firstReportingDay","daysReporting","ageDaysLive","band","weekly","delta","reasons"]}},"notes":{"type":"array","items":{"type":"string"},"description":"Plain-language caveats a reader must repeat with any number above."}},"required":["generatedAt","window","seams","shareOfVoice","mentionRate","shareOfVoiceReasons","phantoms","notes"]}},{"name":"get_client_patterns","description":"Staff only. What separates clients whose AI visibility moves (docs/architecture/client-patterns.md): every client's share of voice, mention rate and page access against itself with ranges, its bucketed factors (segment, kernel, cadence, crowdedness, …), every client factor tested on share of voice change with a shuffle test and correction, post choices each against their own client's average, and the fleet's daily history. Args: period (14d|30d), segment. Refused unless the key was created by Letter staff. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"period":{"description":"Default 30d","type":"string","enum":["14d","30d"]},"segment":{"description":"Only clients in this industry segment","type":"string"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generated":{"type":"string","description":"When the patterns snapshot was computed"},"period":{"type":"string","enum":["14d","30d"]},"clients":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"segment":{"type":"string"},"paying":{"type":"boolean"},"share_of_voice":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]}},"required":["level","change","lo","hi"]},{"type":"null"}]},"mention_rate":{"anyOf":[{"type":"object","properties":{"level":{"type":"number"},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]}},"required":["level","change","lo","hi"]},{"type":"null"}]},"page_access":{"anyOf":[{"type":"object","properties":{"then":{"type":["number","null"]},"now":{"type":["number","null"]},"change":{"type":["number","null"]},"lo":{"type":["number","null"]},"hi":{"type":["number","null"]},"relative":{"type":["number","null"]},"verdict":{"type":"string"}},"required":["then","now","change","lo","hi","relative","verdict"]},{"type":"null"}]},"posts_30d":{"type":"number"},"main_kernel":{"type":"string"},"factors":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["name","segment","paying","share_of_voice","mention_rate","page_access","posts_30d","main_kernel","factors"]},"description":"Every client, each against itself on the period: level, change, likely range, clearly up/down"},"fleet":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The pooled change per outcome, with clients clearly up and down"},"client_factors":{"type":"array","items":{},"description":"Every client factor tested on share of voice change, corrected together"},"post_effects":{"type":"array","items":{},"description":"Post choices, each post against its own client's average"},"links":{"type":"array","items":{},"description":"Whether page-access growth goes with naming growth"},"history":{"type":"array","items":{}}},"required":["generated","period","clients","fleet","client_factors","post_effects","links","history"]}},{"name":"list_client_pattern_reports","description":"Staff only. Fable's analyses of client patterns, newest first: the weekly reports and on-demand runs (kind=reports, the default), or the Ask Fable answers (kind=ask), each with its summary, report, graded hypotheses and the tool calls behind it. Args: kind, limit. Refused unless the key was created by Letter staff. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"kind":{"description":"`reports` (the default) = weekly and run","type":"string","enum":["weekly","run","ask","reports"]},"limit":{"description":"Default 12","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reports":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["weekly","run","ask"]},"status":{"type":"string","enum":["running","done","failed"]},"question":{"type":["string","null"]},"model":{"type":["string","null"]},"snapshot_computed_at":{"type":["string","null"]},"summary":{"type":["string","null"]},"report":{"type":["string","null"]},"hypotheses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable short id; reuse last report's id when re-testing a hypothesis"},"statement":{"type":"string"},"grade":{"type":"string","enum":["strong","suggestive","speculative"]},"status":{"type":"string","enum":["new","holding","weakening","refuted","confirmed"]},"evidence":{"type":"string","description":"The numbers behind it, with n and ranges, and a counter-example"},"test":{"type":"string","description":"The experiment or next read that would settle it"}},"required":["id","statement","grade","status","evidence","test"]}},"steps":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"input":{},"ms":{"type":"number"},"error":{"type":["string","null"]}},"required":["tool","input","ms","error"]}},"error":{"type":["string","null"]},"created_at":{"type":"string"},"finished_at":{"type":["string","null"]}},"required":["id","kind","status","question","model","snapshot_computed_at","summary","report","hypotheses","steps","error","created_at","finished_at"]}}},"required":["reports"]}},{"name":"get_client_pattern_report","description":"Staff only. One Fable report or Ask Fable answer on client patterns, by id, with its hypotheses and tool calls. Args: id. Refused unless the key was created by Letter staff. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"report":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["weekly","run","ask"]},"status":{"type":"string","enum":["running","done","failed"]},"question":{"type":["string","null"]},"model":{"type":["string","null"]},"snapshot_computed_at":{"type":["string","null"]},"summary":{"type":["string","null"]},"report":{"type":["string","null"]},"hypotheses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable short id; reuse last report's id when re-testing a hypothesis"},"statement":{"type":"string"},"grade":{"type":"string","enum":["strong","suggestive","speculative"]},"status":{"type":"string","enum":["new","holding","weakening","refuted","confirmed"]},"evidence":{"type":"string","description":"The numbers behind it, with n and ranges, and a counter-example"},"test":{"type":"string","description":"The experiment or next read that would settle it"}},"required":["id","statement","grade","status","evidence","test"]}},"steps":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"input":{},"ms":{"type":"number"},"error":{"type":["string","null"]}},"required":["tool","input","ms","error"]}},"error":{"type":["string","null"]},"created_at":{"type":"string"},"finished_at":{"type":["string","null"]}},"required":["id","kind","status","question","model","snapshot_computed_at","summary","report","hypotheses","steps","error","created_at","finished_at"]}},"required":["report"]}},{"name":"get_chain_performance","description":"Staff only. How the writing chain performs (docs/architecture/client-patterns.md → Writing chain): pick a dimension — kernel, kernelVersion, arena, arenaVersion or researchPrompt — and an outcome (AI citations or reads in 14 days, naming the client, search impressions or clicks). Returns every change a client made on that dimension with its effect against clients that kept theirs (or the client's own prior trend when everyone changed at once) and what else changed with it, the changes pooled by transition, and each value's own numbers (descriptive). Args: dimension, outcome. Refused unless the key was created by Letter staff. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"dimension":{"description":"kernel | kernelVersion | arena | arenaVersion | researchPrompt; default kernelVersion","type":"string","enum":["kernel","kernelVersion","arena","arenaVersion","researchPrompt"]},"outcome":{"description":"Default cited14","type":"string","enum":["cited14","read14","named","impressions14","clicks14","cited28","impressions28"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"generated":{"type":"string"},"dimension":{"type":"string","enum":["kernel","kernelVersion","arena","arenaVersion","researchPrompt"]},"outcome":{"type":"string"},"attribution":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Posts by how their kernel was attributed (run, service, inferred, config), and how many carry their research versions"},"transitions":{"type":"array","items":{},"description":"Each change pooled across clients: effect, range, how many changes moved with another part of the chain"},"swaps":{"type":"array","items":{},"description":"Every client's change: before, after, the comparison, effect, and what else changed with it"},"values":{"type":"array","items":{},"description":"Every value of the dimension with its posts and outcomes (descriptive)"}},"required":["generated","dimension","outcome","attribution","transitions","swaps","values"]}},{"name":"crm_revenue","description":"Staff only. Letter's revenue retention since 2026-08-10 (docs/architecture/retention.md): the monthly revenue bridge (new, reactivated, expansion, contraction, churn, reclassified, with the clients that moved), monthly NRR and GRR, the headline (last complete) month, where each client's revenue is counted from (a Lago plan, Stripe, deal, comp), concentration and billed products per client. Amounts in cents; rates 0..1. The same numbers as GET /api/superadmin/crm/revenue and `letterstory admin crm revenue`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"since":{"description":"The first capture (2026-08-10)","type":["string","null"]},"asOf":{"description":"The latest capture","type":["string","null"]},"rulesVersion":{"type":"string"},"headline":{"anyOf":[{"type":"object","properties":{"month":{"type":"string"},"nrr":{"type":["number","null"]},"grr":{"type":["number","null"]},"expansionCents":{"type":"number"},"avgCents":{"type":["number","null"]},"avgChange":{"type":["number","null"]}},"required":["month","nrr","grr","expansionCents","avgCents","avgChange"]},{"type":"null"}],"description":"The last complete month; null until one exists"},"periods":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string","description":"YYYY-MM of the start capture"},"start":{"type":"string"},"end":{"type":"string"},"partial":{"type":"boolean","description":"Either end is not a month boundary"},"estimated":{"type":"boolean","description":"Either end predates account snapshots"},"startCents":{"type":"number"},"endCents":{"type":"number"},"customersAtStart":{"type":"number"},"customersAtEnd":{"type":"number"},"moves":{"type":"object","properties":{"new":{"type":"number"},"reactivated":{"type":"number"},"expansion":{"type":"number"},"contraction":{"type":"number"},"churn":{"type":"number"},"reclassified":{"type":"number"}},"required":["new","reactivated","expansion","contraction","churn","reclassified"],"description":"Magnitudes in cents, all positive; reclassified is net"},"nrr":{"type":["number","null"]},"grr":{"type":["number","null"]},"avgStartCents":{"type":["number","null"]},"avgEndCents":{"type":["number","null"]},"movers":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"move":{"type":"string","enum":["new","reactivated","expansion","contraction","churn","reclassified"]},"startCents":{"type":"number"},"endCents":{"type":"number"},"fromSource":{"type":"string","enum":["lago","stripe","deal","comp","none","excluded"],"description":"lago = the org's Lago plan fee; stripe = Stripe MRR; deal = the deal amount; comp, none and excluded count $0"},"toSource":{"type":"string","enum":["lago","stripe","deal","comp","none","excluded"],"description":"lago = the org's Lago plan fee; stripe = Stripe MRR; deal = the deal amount; comp, none and excluded count $0"}},"required":["accountId","name","move","startCents","endCents","fromSource","toSource"]}}},"required":["month","start","end","partial","estimated","startCents","endCents","customersAtStart","customersAtEnd","moves","nrr","grr","avgStartCents","avgEndCents","movers"]},"description":"The monthly revenue bridge, oldest first"},"coverage":{"type":"object","properties":{"counted":{"type":"number"},"lago":{"type":"number","description":"Clients counted from a Lago plan"},"stripe":{"type":"number"},"deal":{"type":"number"},"comp":{"type":"number"},"none":{"type":"number"},"countedCents":{"type":"number"},"unattributedStripeCents":{"type":"number","description":"Billed MRR (Stripe, or a Lago plan) on orgs no CRM account points at"}},"required":["counted","lago","stripe","deal","comp","none","countedCents","unattributedStripeCents"]},"concentration":{"type":"object","properties":{"top5Share":{"type":["number","null"]},"largest":{"anyOf":[{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"cents":{"type":"number"}},"required":["accountId","name","cents"]},{"type":"null"}]},"nrrWithoutLargest":{"description":"The headline month's NRR without the client that was largest at its start","type":["number","null"]}},"required":["top5Share","largest","nrrWithoutLargest"]},"products":{"type":"object","properties":{"one":{"type":"number"},"two":{"type":"number"},"threePlus":{"type":"number"},"noStripe":{"type":"number","description":"Counted from a deal: no billed lines to read"},"byProduct":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"clients":{"type":"number"}},"required":["name","clients"]}}},"required":["one","two","threePlus","noStripe","byProduct"]}},"required":["since","asOf","rulesVersion","headline","periods","coverage","concentration","products"]}},{"name":"billing_desk","description":"Staff only (a billing admin's key). The Billing desk in one read (docs/billing/billing-desk.md): this deployment's billing switches (spend-cap gate, free paywall, Lago signups, forwarding, failed-payment loop), signups (signup grants today and this week, Lago signups by status, stuck ones), failed payments still open, the last nightly Lago reconcile with its age (stale when a night was missed), who the free paywall covers and the orgs waiting on a person, what the gate refused (or would have, in shadow) this week by reason and org, spend-cap requests, scheduled term changes, and staff grants of free tokens. Reads our database only, never Lago live. The same as GET /api/superadmin/billing/desk and `letterstory admin billing desk`. Refused unless the key was created by a Letter billing admin. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"at":{"type":"string"},"switches":{"type":"object","properties":{"tokenGate":{"type":"string","enum":["off","shadow","enforce"],"description":"The Lago spend cap"},"enforcedOrgs":{"type":"number"},"freeTokens":{"type":"string","enum":["off","shadow","enforce"],"description":"The free paywall"},"lagoConfigured":{"type":"boolean"},"lagoSignup":{"type":"object","properties":{"active":{"type":"boolean"},"from":{"type":["string","null"]},"trialOrgs":{"type":"number"}},"required":["active","from","trialOrgs"]},"forwarding":{"type":"object","properties":{"on":{"type":"boolean"},"from":{"type":["string","null"]}},"required":["on","from"]},"dunning":{"type":"string","enum":["on","dry","off"]}},"required":["tokenGate","enforcedOrgs","freeTokens","lagoConfigured","lagoSignup","forwarding","dunning"]},"signups":{"type":"object","properties":{"grants":{"type":"object","properties":{"today":{"type":"number"},"week":{"type":"number"},"agentWeek":{"type":"number"}},"required":["today","week","agentWeek"]},"lagoWeek":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Lago signups started in the last week, by status"},"stuck":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"planCode":{"type":"string"},"status":{"type":"string"},"lastError":{"type":["string","null"]},"createdAt":{"type":"string"},"provisionedAt":{"type":["string","null"]}},"required":["id","orgId","orgName","planCode","status","lastError","createdAt","provisionedAt"]}}},"required":["grants","lagoWeek","stuck"]},"payments":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"invoiceId":{"type":"string"},"invoiceNumber":{"type":["string","null"]},"amountCents":{"type":"number"},"currency":{"type":"string"},"status":{"type":"string"},"lastError":{"type":["string","null"]},"attempts":{"type":"number"},"nextAttemptAt":{"type":["string","null"]},"emailsSent":{"type":"number"},"firstFailedAt":{"type":"string"}},"required":["orgId","orgName","invoiceId","invoiceNumber","amountCents","currency","status","lastError","attempts","nextAttemptAt","emailsSent","firstFailedAt"]}},"reconcile":{"type":"object","properties":{"result":{"anyOf":[{"type":"object","properties":{"ok":{"type":"boolean"},"orgs":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"subscriptionId":{"type":"string"},"lagoTokens":{"type":["number","null"]},"ourTokens":{"type":"number"},"forwardedTokens":{"type":"number"},"unforwardedEvents":{"type":"number"},"staleEvents":{"type":"number"},"heldEvents":{"type":"number"},"refusedEvents":{"type":"number"},"drift":{"description":"lagoTokens - forwardedTokens: anything but 0 is a disagreement","type":["number","null"]},"billedByStripe":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["ok","pending","drift","attention","lago_failed","not_started"]},"error":{"type":"string"}},"required":["orgId","subscriptionId","lagoTokens","ourTokens","forwardedTokens","unforwardedEvents","staleEvents","heldEvents","refusedEvents","drift","billedByStripe","status"]}}},"required":["ok","orgs"]},{"type":"null"}]},"meta":{"type":"object","properties":{"computedAt":{"type":["string","null"]},"stale":{"type":"boolean"},"pending":{"type":"boolean"}},"required":["computedAt","stale","pending"],"description":"The nightly check's age: stale when a night was missed"}},"required":["result","meta"]},"exposure":{"type":"object","properties":{"snapshotOn":{"type":["string","null"]},"counts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"waiting":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"group":{"type":"string"},"origin":{"type":["string","null"]},"paying":{"type":"boolean"},"revenueSource":{"type":["string","null"]},"grantedTokens":{"type":"number"}},"required":["orgId","name","group","origin","paying","revenueSource","grantedTokens"]}}},"required":["snapshotOn","counts","waiting"]},"refusals":{"type":"object","properties":{"since":{"type":"string"},"total":{"type":"number"},"byReason":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"byMode":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"orgs":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"count":{"type":"number"},"lastAt":{"type":"string"},"reasons":{"type":"array","items":{"type":"string"}}},"required":["orgId","orgName","count","lastAt","reasons"]}}},"required":["since","total","byReason","byMode","orgs"]},"capRequests":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"note":{"type":["string","null"]},"requestedBy":{"type":"string"},"createdAt":{"type":"string"}},"required":["orgId","orgName","note","requestedBy","createdAt"]}},"termChanges":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"kind":{"type":"string"},"planCode":{"type":["string","null"]},"ratio":{"type":["number","null"]},"effectiveAt":{"type":"string"},"requestedBy":{"type":["string","null"]},"lastError":{"type":["string","null"]}},"required":["orgId","orgName","kind","planCode","ratio","effectiveAt","requestedBy","lastError"]}},"grants":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","description":"grant:<id> or wallet:staff_<key>: what billing_revoke_grant takes"},"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"kind":{"type":"string","enum":["grant","wallet"],"description":"grant: free-token row; wallet: one-off Lago wallet (org billed on Lago)"},"tokens":{"type":"number"},"key":{"type":"string"},"grantedBy":{"type":"string"},"reason":{"type":["string","null"]},"createdAt":{"type":"string"},"revokedAt":{"type":["string","null"]},"revokedBy":{"type":["string","null"]},"revokeReason":{"type":["string","null"]}},"required":["ref","orgId","orgName","kind","tokens","key","grantedBy","reason","createdAt","revokedAt","revokedBy","revokeReason"]}},"legacyReview":{"type":"object","properties":{"by":{"type":"string","description":"The day every pending org is decided by"},"reviewed":{"type":"number"},"pending":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"markedAt":{"type":"string"},"markedBy":{"type":"string"},"reason":{"type":"string"},"revenueSource":{"type":["string","null"]},"events30d":{"type":"number","description":"Usage events in the last 30 days"},"lastUsedAt":{"type":["string","null"]}},"required":["orgId","name","markedAt","markedBy","reason","revenueSource","events30d","lastUsedAt"]}}},"required":["by","reviewed","pending"]},"emails":{"type":"object","properties":{"on":{"type":"boolean","description":"We send invoice and receipt emails (LETTERBRACE_BILLING_EMAILS); off: Lago sends them"},"problems":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"invoiceId":{"type":"string"},"invoiceNumber":{"type":["string","null"]},"kind":{"type":"string","enum":["invoice","receipt"]},"amountCents":{"type":"number"},"currency":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["sending","sent","delivered","failed","bounced","complained"]},"error":{"type":["string","null"]},"attempts":{"type":"number"},"resendId":{"type":["string","null"]},"sentAt":{"type":["string","null"]},"lastEventAt":{"type":["string","null"]},"createdAt":{"type":"string"}},"required":["orgId","orgName","invoiceId","invoiceNumber","kind","amountCents","currency","to","status","error","attempts","resendId","sentAt","lastEventAt","createdAt"]}}},"required":["on","problems"],"description":"Invoice and receipt emails that never arrived: bounced, marked as spam or failed"}},"required":["at","switches","signups","payments","reconcile","exposure","refusals","capRequests","termChanges","grants","legacyReview","emails"]}},{"name":"billing_grant_tokens","description":"Staff only (a billing admin's key). Give an org free tokens, recorded as given by the key's creator with the reason, and posted to #billing. A free org: more tokens on its free balance. An unassigned org (staff- or CRM-made, never decided): the grant also puts it on the free paywall. An org billed on Lago: a one-off wallet drawn before overage that ends with the plan. Legacy and internal orgs are refused (never gated). $0.01 a token at the standard rate, so 50,000 is $500. The same key is one grant, so a retry gives nothing twice. Args: org_id, tokens (1-1,000,000), why, key. The same as POST /api/superadmin/billing/free-tokens/grants and `letterstory admin billing free grant`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"tokens":{"type":"integer","minimum":1,"maximum":1000000,"description":"Free tokens to give: $0.01 each at the standard rate, so 50,000 is $500."},"why":{"type":"string","minLength":3,"maxLength":500,"description":"Why, kept with the grant and posted to #billing."},"key":{"description":"The grant's key: the same key is one grant, so a retry gives nothing twice. Default: one per org, tokens and reason a day.","type":"string","pattern":"^[A-Za-z0-9:._-]{1,80}$"},"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The org to give tokens to."}},"required":["tokens","why","org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kind":{"type":"string","enum":["grant","wallet"]},"granted":{"type":"boolean","description":"False when this key had already granted: nothing new was given"},"ref":{"type":"string"},"tokens":{"type":"number"},"standing":{"type":"object","properties":{"before":{"type":"string","enum":["lago","legacy","internal","free","unassigned"]},"after":{"type":"string","enum":["lago","legacy","internal","free","unassigned"]}},"required":["before","after"],"description":"An unassigned org becomes free"}},"required":["ok","kind","granted","ref","tokens","standing"]}},{"name":"billing_revoke_grant","description":"Staff only (a billing admin's key). Take a staff grant of free tokens back, kept with who and why and posted to #billing: a grant row is moved out of the org's balance, a Lago wallet is ended. Only staff grants: a signup grant, a reward or a bonus is the customer's. A revoked grant's key is spent. Args: org_id, ref (grant:<id> or wallet:staff_<key>, from billing_desk's grants), why. The same as DELETE /api/superadmin/billing/free-tokens/grants and `letterstory admin billing free revoke`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"ref":{"type":"string","minLength":1,"description":"The grant's reference from the list: grant:<id> or wallet:staff_<key>."},"why":{"type":"string","minLength":3,"maxLength":500,"description":"Why, kept with the revoke."},"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The org the grant was given to."}},"required":["ref","why","org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"revoked":{"type":"object","properties":{"ref":{"type":"string","description":"grant:<id> or wallet:staff_<key>: what billing_revoke_grant takes"},"orgId":{"type":"string"},"orgName":{"type":["string","null"]},"kind":{"type":"string","enum":["grant","wallet"],"description":"grant: free-token row; wallet: one-off Lago wallet (org billed on Lago)"},"tokens":{"type":"number"},"key":{"type":"string"},"grantedBy":{"type":"string"},"reason":{"type":["string","null"]},"createdAt":{"type":"string"},"revokedAt":{"type":["string","null"]},"revokedBy":{"type":["string","null"]},"revokeReason":{"type":["string","null"]}},"required":["ref","orgId","orgName","kind","tokens","key","grantedBy","reason","createdAt","revokedAt","revokedBy","revokeReason"]}},"required":["ok","revoked"]}},{"name":"billing_legacy_decide","description":"Staff only (a billing admin's key). Decide one org in the legacy review (billing_desk's legacyReview: legacy orgs no rule decided, free and never gated until a person decides): keep (stays legacy, reviewed), free (the legacy mark ends and a staff grant puts it on free tokens, the signup grant's amount unless tokens says otherwise) or test (a test org, never gated). Kept with the key's creator, when and why. Args: org_id, decision, why, tokens. The same as POST /api/superadmin/billing/legacy/review and `letterstory admin billing legacy decide`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"decision":{"type":"string","enum":["keep","free","test"],"description":"keep: stays legacy; free: on free tokens (a staff grant); test: a test org, never gated."},"why":{"type":"string","minLength":3,"maxLength":500,"description":"Why, kept with the decision."},"tokens":{"description":"For free: the tokens to start with (default: the signup grant's).","type":"integer","minimum":1,"maximum":1000000},"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The legacy org to decide."}},"required":["decision","why","org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"decision":{"type":"string","enum":["keep","free","test"]},"grantRef":{"description":"For free: the staff grant that put it on free tokens","type":"string"}},"required":["ok","decision"]}},{"name":"billing_set_org_test","description":"Staff only (a billing admin's key). Mark an org a test org (organizations.is_test: never gated, never billed, left out of the fleet's customer numbers), or back with test false. Kept with the key's creator, when and why, and posted to #billing. Refused (HAS_BASIS) for an org that pays us (Stripe, a deal or a live Lago plan). Args: org_id, test (default true), why. The same as POST /api/superadmin/billing/orgs/test and `letterstory admin billing org test`. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"test":{"description":"true (default): a test org, never gated or billed; false: not one any more.","type":"boolean"},"why":{"type":"string","minLength":3,"maxLength":500,"description":"Why, kept with the change."},"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The org to mark."}},"required":["why","org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"orgId":{"type":"string"},"isTest":{"type":"boolean"},"changed":{"type":"boolean","description":"False when the org already was (or wasn't) a test org: nothing written"}},"required":["ok","orgId","isTest","changed"]}},{"name":"billing_paywall_preflight","description":"Staff only (a billing admin's key). Who the free paywall stops the moment LETTERBRACE_FREE_TOKENS is set to enforce, from today's real balances (the gate's own): free orgs at or below 0 tokens (refused at their next start), free orgs running low, orgs the paywall covers that pay us today (they need a legacy mark first), and orgs no rule decided. `ready` is true when nobody paying is covered and nobody is undecided. Reads only. The same as GET /api/superadmin/billing/free-tokens/preflight and `letterstory admin billing free preflight`. Refused unless the key was created by a Letter billing admin. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"at":{"type":"string"},"mode":{"type":"string","enum":["off","shadow","enforce"],"description":"LETTERBRACE_FREE_TOKENS as this deployment reads it"},"ready":{"type":"boolean","description":"Nobody paying is covered and nobody is undecided: safe to set enforce"},"blockers":{"type":"array","items":{"type":"string"}},"covered":{"type":"number","description":"Orgs the free paywall covers"},"refusedNow":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"grantedTokens":{"type":"number"},"usedTokens":{"type":"number"},"heldTokens":{"type":"number"},"availableTokens":{"type":"number","description":"Granted less used less held; at or below 0 the next start is refused"}},"required":["orgId","name","grantedTokens","usedTokens","heldTokens","availableTokens"]},"description":"Refused CARD_REQUIRED at their next start once enforced"},"low":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"grantedTokens":{"type":"number"},"usedTokens":{"type":"number"},"heldTokens":{"type":"number"},"availableTokens":{"type":"number","description":"Granted less used less held; at or below 0 the next start is refused"}},"required":["orgId","name","grantedTokens","usedTokens","heldTokens","availableTokens"]}},"wronglyCovered":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"revenueSource":{"type":["string","null"]},"paying":{"type":"boolean"}},"required":["orgId","name","revenueSource","paying"]}},"undecided":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":["string","null"]},"origin":{"type":["string","null"]}},"required":["orgId","name","origin"]}}},"required":["at","mode","ready","blockers","covered","refusedNow","low","wronglyCovered","undecided"]}},{"name":"crm_churn_stats","description":"Staff only. Who churned, why, and the rate (docs/architecture/retention.md): trailing 30- and 90-day logo and revenue churn, a monthly series (the last month is partial), churns by reason, the churned accounts newest first (undated ones are listed but in no rate), customers Stripe says may have churned (suggestions only: a person records a churn), each churn and likely churn with its cause (voluntary / involuntary / unknown, also split per window and month), and `failing`: payments failing now (null when the billing read failed, never a false all-clear). Test and internal accounts are left out. Args: months (1-36, default 12). The same numbers as GET /api/superadmin/crm/churn and `letterstory admin crm churn`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"months":{"description":"How many calendar months the monthly series covers, the current one included. Default 12.","type":"integer","minimum":1,"maximum":36}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"window":{"type":"object","properties":{"days30":{"type":"object","properties":{"customersAtStart":{"type":"number"},"churned":{"type":"number"},"rate":{"description":"Logo churn, 0..1","type":["number","null"]},"mrrLostCents":{"type":"number"},"mrrAtStartCents":{"type":["number","null"]},"revenueRate":{"description":"Revenue churn, 0..1","type":["number","null"]},"byCause":{"type":"object","properties":{"voluntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"involuntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"unknown":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]}},"required":["voluntary","involuntary","unknown"],"description":"The same churns split by cause; the three add up to the totals"}},"required":["customersAtStart","churned","rate","mrrLostCents","mrrAtStartCents","revenueRate","byCause"]},"days90":{"type":"object","properties":{"customersAtStart":{"type":"number"},"churned":{"type":"number"},"rate":{"description":"Logo churn, 0..1","type":["number","null"]},"mrrLostCents":{"type":"number"},"mrrAtStartCents":{"type":["number","null"]},"revenueRate":{"description":"Revenue churn, 0..1","type":["number","null"]},"byCause":{"type":"object","properties":{"voluntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"involuntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"unknown":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]}},"required":["voluntary","involuntary","unknown"],"description":"The same churns split by cause; the three add up to the totals"}},"required":["customersAtStart","churned","rate","mrrLostCents","mrrAtStartCents","revenueRate","byCause"]}},"required":["days30","days90"]},"monthly":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string","description":"YYYY-MM (UTC)"},"customersAtStart":{"type":"number"},"churned":{"type":"number"},"rate":{"type":["number","null"]},"mrrLostCents":{"type":"number"},"byCause":{"type":"object","properties":{"voluntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"involuntary":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]},"unknown":{"type":"object","properties":{"churned":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["churned","mrrLostCents"]}},"required":["voluntary","involuntary","unknown"],"description":"The same churns split by cause; the three add up to the totals"}},"required":["month","customersAtStart","churned","rate","mrrLostCents","byCause"]},"description":"Oldest first; the last entry is the current, partial month"},"reasons":{"type":"array","items":{"type":"object","properties":{"reason":{"anyOf":[{"type":"string","enum":["price","no_results","competitor","in_house","shut_down","budget","bad_fit","service","other"]},{"type":"string","const":"unrecorded"}]},"count":{"type":"number"},"mrrLostCents":{"type":"number"}},"required":["reason","count","mrrLostCents"]},"description":"Biggest count first"},"churned":{"type":"array","items":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":["string","null"]},"name":{"type":"string"},"domain":{"type":["string","null"]},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"churnedAt":{"type":["string","null"]},"churnReason":{"anyOf":[{"type":"string","enum":["price","no_results","competitor","in_house","shut_down","budget","bad_fit","service","other"]},{"type":"null"}]},"churnMrrCents":{"type":["number","null"]}},"required":["id","orgId","name","domain","stage","churnedAt","churnReason","churnMrrCents"],"description":"The account rollup, as GET /api/superadmin/crm/accounts lists it"},"churnedAt":{"type":"string"},"churnedAtSource":{"type":"string","enum":["recorded","stage_change","undated"],"description":"Undated churns are in no rate"},"reason":{"anyOf":[{"type":"string","enum":["price","no_results","competitor","in_house","shut_down","budget","bad_fit","service","other"]},{"type":"null"}]},"note":{"type":["string","null"]},"mrrLostCents":{"type":"number"},"cause":{"type":"string","enum":["voluntary","involuntary","unknown"],"description":"Why they left: involuntary = a failed payment ended it; voluntary = they asked, or a reason was recorded"},"causeSignal":{"description":"What decided the cause; null when unknown","type":["string","null"]}},"required":["account","churnedAt","churnedAtSource","reason","note","mrrLostCents","cause","causeSignal"]},"description":"Newest first"},"likely":{"type":"array","items":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string"},"orgId":{"type":["string","null"]},"name":{"type":"string"},"domain":{"type":["string","null"]},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"churnedAt":{"type":["string","null"]},"churnReason":{"anyOf":[{"type":"string","enum":["price","no_results","competitor","in_house","shut_down","budget","bad_fit","service","other"]},{"type":"null"}]},"churnMrrCents":{"type":["number","null"]}},"required":["id","orgId","name","domain","stage","churnedAt","churnReason","churnMrrCents"],"description":"The account rollup, as GET /api/superadmin/crm/accounts lists it"},"signal":{"type":"string"},"cause":{"type":"string","enum":["voluntary","involuntary","unknown"],"description":"Why they left: involuntary = a failed payment ended it; voluntary = they asked, or a reason was recorded"},"causeSignal":{"description":"What decided the cause; null when unknown","type":["string","null"]}},"required":["account","signal","cause","causeSignal"]},"description":"Customers Stripe says may have churned: suggestions only, a person records a churn"},"failing":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"accountId":{"description":"Null for a payer bound to no account; the name still says who","type":["string","null"]},"name":{"type":"string"},"orgId":{"type":["string","null"]},"amountCents":{"description":"Monthly money at stake, or the invoice sum for an invoice failure","type":["number","null"]},"since":{"description":"When the money was due (ISO); null when unknown","type":["string","null"]},"attempts":{"description":"Stripe's attempts on the invoice; null for a subscription-derived failure","type":["number","null"]},"reason":{"type":"string"},"action":{"anyOf":[{"type":"string","enum":["draft_nudge","bill","classify","confirm_test","merge","link","clear"]},{"type":"null"}],"description":"The one action; null while Stripe is still retrying"},"nudgeInHours":{"type":["number","null"]},"hostedInvoiceUrl":{"type":["string","null"]},"suggestedContact":{"anyOf":[{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]},"source":{"type":"string","const":"stripe"}},"required":["email","name","source"]},{"type":"null"}],"description":"When no contact on the account has an email: who Stripe bills, from the mirror. Accept it by adding the contact (POST /api/superadmin/crm/accounts/{id}/contacts); null otherwise"}},"required":["accountId","name","orgId","amountCents","since","attempts","reason","action","nudgeInHours","hostedInvoiceUrl","suggestedContact"]}},{"type":"null"}],"description":"Payments failing now (the billing queue's Payment failed rows), biggest first. null = the billing read failed (unknown, not an all-clear); [] = nobody failing"}},"required":["window","monthly","reasons","churned","likely","failing"]}},{"name":"crm_weekly_note","description":"Staff only. The Monday retention note for the last full ISO week (docs/architecture/retention.md, The weekly note): the text #crm-opportunities gets, and the numbers it was written from. The last complete month's NRR and GRR and the month so far; the week's revenue movers (new, back, expansion, contraction, churn) with names and amounts; churns dated in the week with reason and cause; how many churns in the last 12 months have no reason; payments failing now (count and amount, null when billing could not be read); expansion signals whose rule started firing in the week; and each rule's track-record counts, with rates only once mature. Reads only; never posts. The same note as GET /api/superadmin/crm/weekly-note and `letterstory admin crm weekly-note`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"week":{"type":"object","properties":{"key":{"type":"string","description":"ISO week, e.g. 2026-W42"},"from":{"type":"string"},"to":{"type":"string"}},"required":["key","from","to"],"description":"The last full ISO week before asOf, Monday to Sunday (UTC)"},"asOf":{"type":"string"},"text":{"type":"string","description":"The note as #crm-opportunities gets it (Slack mrkdwn)"},"retention":{"type":"object","properties":{"lastMonth":{"anyOf":[{"type":"object","properties":{"month":{"type":"string"},"nrr":{"type":["number","null"]},"grr":{"type":["number","null"]}},"required":["month","nrr","grr"]},{"type":"null"}],"description":"The revenue headline: the last complete month"},"monthSoFar":{"anyOf":[{"type":"object","properties":{"month":{"type":"string"},"to":{"type":"string"},"nrr":{"type":["number","null"]},"grr":{"type":["number","null"]},"estimated":{"type":"boolean"}},"required":["month","to","nrr","grr","estimated"]},{"type":"null"}],"description":"The current month to the latest capture"}},"required":["lastMonth","monthSoFar"]},"movers":{"anyOf":[{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"estimated":{"type":"boolean"},"movers":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"move":{"type":"string","enum":["new","reactivated","expansion","contraction","churn","reclassified"]},"startCents":{"type":"number"},"endCents":{"type":"number"},"fromSource":{"type":"string","enum":["lago","stripe","deal","comp","none","excluded"],"description":"lago = the org's Lago plan fee; stripe = Stripe MRR; deal = the deal amount; comp, none and excluded count $0"},"toSource":{"type":"string","enum":["lago","stripe","deal","comp","none","excluded"],"description":"lago = the org's Lago plan fee; stripe = Stripe MRR; deal = the deal amount; comp, none and excluded count $0"}},"required":["accountId","name","move","startCents","endCents","fromSource","toSource"]}}},"required":["from","to","estimated","movers"]},{"type":"null"}],"description":"The revenue bridge over the week's captures; null when there is none"},"churns":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"churnedAt":{"type":"string"},"reason":{"anyOf":[{"type":"string","enum":["price","no_results","competitor","in_house","shut_down","budget","bad_fit","service","other"]},{"type":"null"}]},"cause":{"type":"string","enum":["voluntary","involuntary","unknown"]},"mrrLostCents":{"type":"number"}},"required":["accountId","name","churnedAt","reason","cause","mrrLostCents"]}},"churnsWithoutReason":{"type":"number","description":"Churns in the last 12 months with no recorded reason"},"failing":{"anyOf":[{"type":"object","properties":{"count":{"type":"number"},"amountCents":{"type":"number","description":"The known amounts summed"},"amountUnknown":{"type":"number","description":"Failing rows with no amount"}},"required":["count","amountCents","amountUnknown"]},{"type":"null"}],"description":"Payments failing now; null when the billing read failed"},"newSignals":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"product":{"type":"string","enum":["phantomstory","kernels","shredder","ledes","traces","proofs","seers","other"]},"ruleLabel":{"type":"string"},"flaggedSince":{"type":"string"}},"required":["accountId","name","product","ruleLabel","flaggedSince"]}},{"type":"null"}],"description":"Signals whose rule started firing in the week; null until snapshots reach back before it"},"trackRecord":{"type":"object","properties":{"since":{"type":["string","null"]},"minOutcomes":{"type":"number"},"losses":{"type":"number"},"expansions":{"type":"number"},"predictors":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"predicts":{"type":"string","enum":["loss","expansion"]},"flagged":{"type":"number"},"hits":{"type":"number"},"matured":{"type":"number","description":"Matured flags: the n of `rate`"},"mature":{"type":"boolean"},"rate":{"description":"Hits among matured flags; null until mature","type":["number","null"]},"baseRate":{"description":"The same outcome among every customer; null until mature","type":["number","null"]}},"required":["key","label","predicts","flagged","hits","matured","mature","rate","baseRate"]}}},"required":["since","minOutcomes","losses","expansions","predictors"]}},"required":["week","asOf","text","retention","movers","churns","churnsWithoutReason","failing","newSignals","trackRecord"]}},{"name":"crm_reach_outs_today","description":"Staff only. The day's team reach-out count toward the target (\"Reach-outs today: N of 5\") — the same number #crm-reminders posts: different countable leads reached that US Eastern day by a logged note, email, Outbox send, call, cake or a message in the lead's linked Slack room. Returns day, count, target, hit, asOf, the counted leads with each reach-out (at, by, byName, route, channel), and slack {rooms, read, failed, leftOver, unread} — unread > 0 means Slack rooms went unread and the count may be low. Logged reach-outs are read fresh; the Slack read is cached up to 3 minutes unless fresh. Args: day (YYYY-MM-DD, default today), fresh. The same numbers as GET /api/superadmin/crm/reach-outs/today and `letterstory admin crm reach-outs`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"day":{"description":"A US Eastern (America/New_York) date, YYYY-MM-DD. Default: today there.","type":"string"},"fresh":{"description":"Re-read Slack now instead of the read cached for up to 3 minutes. Default false.","anyOf":[{"type":"boolean"},{"type":"string","enum":["0","1","true","false"]}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"day":{"type":"string","description":"The US Eastern (America/New_York) date counted, YYYY-MM-DD"},"count":{"type":"number","description":"Different countable leads reached that day — the number #crm-reminders posts"},"target":{"type":"number","description":"The daily target (5)"},"hit":{"type":"boolean","description":"count >= target"},"asOf":{"type":"string","description":"ISO time the day was read up to; the Slack half may be up to 3 minutes older"},"leads":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"reachOuts":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","description":"ISO time it went out"},"by":{"description":"A profile id, an email address or slack:<user id>; null for automation","type":["string","null"]},"byName":{"description":"The person's name where known","type":["string","null"]},"route":{"type":"string","description":"How it was seen: email | outbox | note | slack | call | cake | sequence"},"channel":{"type":"string","description":"email | linkedin | text | dm | call | bookface | other | slack | cake"}},"required":["at","by","byName","route","channel"]}}},"required":["accountId","name","reachOuts"]},"description":"The counted leads, first reached first"},"slack":{"type":"object","properties":{"rooms":{"type":"number","description":"Linked lead rooms to read (capped)"},"read":{"type":"number"},"failed":{"type":"number","description":"Rooms that could not be read (all of them when Slack is not configured)"},"leftOver":{"type":"number","description":"Rooms not reached before the time budget ran out"},"unread":{"type":"number","description":"Linked lead rooms not looked at: failed, out of time or past the cap"}},"required":["rooms","read","failed","leftOver","unread"],"description":"Slack reach-outs count only from the rooms read: unread > 0 means the count may be low"}},"required":["day","count","target","hit","asOf","leads","slack"]}},{"name":"crm_replies_waiting","description":"Staff only. Replies waiting on us: companies that answered something we sent (an Outbox email, a sequence email or a HeyReach LinkedIn message) and that nobody on the desk has reached since, by the reach-out count's own rules (an automated step or a cake is not an answer). Newest reply first, each with accountId, name, stage, ownerId, at, title and answering (who they were answering). Test, internal and archived accounts are left out. Args: days (1-60, default 14). The same list as GET /api/superadmin/crm/replies and `letterstory admin crm replies`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"days":{"description":"How many days back to look for a reply, 1-60. Default 14.","type":"integer","minimum":1,"maximum":60}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"since":{"type":"string","description":"ISO start of the look-back"},"asOf":{"type":"string","description":"ISO time it was read"},"replies":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"stage":{"type":"string"},"ownerId":{"description":"The account owner's profile id, if it has one","type":["string","null"]},"at":{"type":"string","description":"ISO time the newest unanswered reply came in"},"title":{"description":"Its timeline title","type":["string","null"]},"answering":{"description":"Who they were answering: the profile id (or, for a BCC'd email, the sender's email) behind our last reach-out with a person on it before the reply; null when there was none in the look-back","type":["string","null"]}},"required":["accountId","name","stage","ownerId","at","title","answering"]},"description":"Companies whose newest reply has had no reach-out from us since, newest first"}},"required":["since","asOf","replies"]}},{"name":"crm_outcomes","description":"Staff only. Every churn, contraction, expansion, reactivation and save, with its reason (docs/architecture/retention.md, Outcomes): status (draft = suggested, confirmed = a person stands behind it, discarded = undone and kept), cause, notice and effective dates, MRR either side, evidence, who decided it, and labelAt (90 days after notice, else effective). Newest effective first; discarded are left out unless status asks for them. Args: account_id, kind, status, since, until, limit (1-500, default 100). The same list as GET /api/superadmin/crm/outcomes and `letterstory admin crm outcomes`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"account_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"status":{"type":"string","enum":["draft","confirmed","discarded"]},"since":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"until":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"limit":{"description":"1-500; omitted = 100","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"outcomes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"accountId":{"type":"string"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"status":{"type":"string","enum":["draft","confirmed","discarded"]},"primaryReason":{"type":["string","null"]},"contributingReasons":{"type":"array","items":{"type":"string"}},"cause":{"anyOf":[{"type":"string","enum":["voluntary","involuntary","unknown"]},{"type":"null"}]},"noticeAt":{"type":["string","null"]},"effectiveAt":{"type":"string"},"mrrBeforeCents":{"type":["number","null"]},"mrrAfterCents":{"type":["number","null"]},"note":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"at":{"type":["string","null"]},"quote":{"type":["string","null"]},"ref":{"type":["string","null"]}},"required":["source","at","quote","ref"]}},"saveOffer":{"anyOf":[{"type":"string","enum":["discount","pause","downgrade","fix","other"]},{"type":"null"}]},"saveResult":{"anyOf":[{"type":"string","enum":["kept","lost","pending"]},{"type":"null"}]},"draftedBy":{"description":"auto (a machine) | agent (a staff key); null = a person recorded it","type":["string","null"]},"decidedBy":{"description":"The person who confirmed or discarded it","type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"labelAt":{"type":"string","description":"90 days after noticeAt ?? effectiveAt: when the outcome is labelled"}},"required":["id","accountId","kind","status","primaryReason","contributingReasons","cause","noticeAt","effectiveAt","mrrBeforeCents","mrrAfterCents","note","evidence","saveOffer","saveResult","draftedBy","decidedBy","decidedAt","createdAt","updatedAt","labelAt"]}}},"required":["outcomes"]}},{"name":"crm_highlights","description":"Staff only. Customer love: moments a customer or prospect said something glowing about us, newest first, each with the model's strength (1-5) and the team's Keep / Pass verdict (who, where, when; set it with crm_highlight_verdict). Each is a verbatim quote with who said it, and either a clip of up to 30s from a recorded call (clipUrl, signed for an hour) or a Slack message a teammate forwarded to #customer-love. Every one is also posted to #customer-love and is a `highlight` entry on the account's timeline. Args: account_id, days (1-365), limit (1-200, default 50). The same list as GET /api/superadmin/crm/highlights. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"account_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"type":"integer","minimum":1,"maximum":365},"limit":{"description":"Default 50","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"highlights":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"accountId":{"description":"Null until someone places the company","type":["string","null"]},"accountName":{"type":["string","null"]},"contactId":{"type":["string","null"]},"source":{"type":"string","enum":["call","slack"]},"quote":{"type":"string","description":"Their words, verbatim"},"speakerName":{"type":["string","null"]},"speakerTitle":{"type":["string","null"]},"transcriptId":{"type":["string","null"]},"startsAtSec":{"description":"Where the clip starts in the call","type":["number","null"]},"endsAtSec":{"type":["number","null"]},"clipUrl":{"description":"Signed mp4 link, good for an hour","type":["string","null"]},"originUrl":{"description":"The Circleback meeting or the original Slack message","type":["string","null"]},"slackPermalink":{"description":"The #customer-love post","type":["string","null"]},"strength":{"description":"The model's score, 1-5; null for a forwarded message","type":["number","null"]},"verdict":{"anyOf":[{"type":"string","enum":["keep","pass"]},{"type":"null"}],"description":"A person's Keep or Pass; null until given"},"verdictByName":{"type":["string","null"]},"verdictSource":{"anyOf":[{"type":"string","enum":["slack","crm","api","preview"]},{"type":"null"}]},"verdictAt":{"type":["string","null"]},"occurredAt":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","accountId","accountName","contactId","source","quote","speakerName","speakerTitle","transcriptId","startsAtSec","endsAtSec","clipUrl","originUrl","slackPermalink","strength","verdict","verdictByName","verdictSource","verdictAt","occurredAt","createdAt"]}}},"required":["highlights"]}},{"name":"crm_collections","description":"Staff only. What we publish for each client: every collection an org has, classified (main blog, phantom, likely test, …) with why, its destination, post counts (total, live, first published in the last 30 days, last post) and any setup issue, rolled up per org with the internal/test label and the field that said so. Every org with a collection, including ones with no CRM account. Args: org_ids (max 500) or account_stages (CRM stages, e.g. won, onboarding, active), not both; neither = every org. The same function as GET /api/superadmin/crm/collections and `letterstory admin crm collections`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"org_ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"account_stages":{"minItems":1,"type":"array","items":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgs":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"accountId":{"type":["string","null"]},"accountName":{"type":["string","null"]},"internal":{"type":"object","properties":{"value":{"type":"boolean","description":"Not a real customer: test, internal, or a non-customer lifecycle"},"source":{"anyOf":[{"type":"string","enum":["billing_override","billing_exemption","lifecycle"]},{"type":"null"}]},"label":{"type":["string","null"]},"hint":{"anyOf":[{"type":"string","enum":["is_test"]},{"type":"null"}],"description":"An unconfirmed hint, never enough on its own"}},"required":["value","source","label","hint"]},"phantomCount":{"type":"number"},"phantomOfflineCount":{"type":"number"},"mainBlogCount":{"type":"number"},"otherCount":{"type":"number"},"totalPosts":{"type":"number"},"postsLast30d":{"type":"number"},"lastPostAt":{"type":["string","null"]},"issues":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["posts_no_destination","posts_via_api","site_error","phantom_error","onboard_error","sitemap_status"]},"message":{"type":"string","description":"One line, already phrased for a person"},"collectionId":{"description":"Null for an account-level finding","type":["string","null"]}},"required":["kind","message","collectionId"]},"description":"Worst first"},"collections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"label":{"type":"string","enum":["main_blog","likely_main_blog","phantom","phantom_offline","likely_test","empty","other","unknown"]},"reason":{"type":"string","description":"Why this label"},"certain":{"type":"boolean","description":"False when the label is inferred"},"brandSubject":{"type":"string"},"publishControl":{"type":"string"},"profile":{"type":"string","description":"A display badge only; never branch on it"},"isOrgDefault":{"type":"boolean"},"cadenceTarget":{"description":"Posts per cadencePeriod promised; null = no goal set","type":["number","null"]},"cadencePeriod":{"type":"string"},"relinkEnabled":{"type":"boolean"},"relinkedLast30d":{"type":"number"},"phantomStatus":{"type":["string","null"]},"geoProbeEnabled":{"type":"boolean"},"createdAt":{"type":["string","null"]},"destination":{"type":"object","properties":{"kind":{"type":"string","enum":["phantom_site","cms","owned_site","sitemap","base_url","api","none"]},"detail":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["kind","detail","url"]},"totalPosts":{"type":"number"},"publishedPosts":{"type":"number","description":"Live posts (lifecycle_phase = published)"},"postsLast30d":{"type":"number","description":"Posts first published in the trailing 30 days"},"lastPostAt":{"type":["string","null"]},"issues":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["posts_no_destination","posts_via_api","site_error","phantom_error","onboard_error","sitemap_status"]},"message":{"type":"string","description":"One line, already phrased for a person"},"collectionId":{"description":"Null for an account-level finding","type":["string","null"]}},"required":["kind","message","collectionId"]}},"appPath":{"type":"string","description":"The collection's Control Room in the app"}},"required":["id","name","label","reason","certain","brandSubject","publishControl","profile","isOrgDefault","cadenceTarget","cadencePeriod","relinkEnabled","relinkedLast30d","phantomStatus","geoProbeEnabled","createdAt","destination","totalPosts","publishedPosts","postsLast30d","lastPostAt","issues","appPath"]}}},"required":["orgId","orgName","accountId","accountName","internal","phantomCount","phantomOfflineCount","mainBlogCount","otherCount","totalPosts","postsLast30d","lastPostAt","issues","collections"]}},"computedAt":{"type":"string"}},"required":["orgs","computedAt"]}},{"name":"crm_delivery_flags","description":"Staff only. Does what we spend on a client match what they pay? Per org: whether we owe them delivery (paying, comped or agency) or they are not paying, their ranking targets and competitors on file, and findings in two directions — behind (not publishing, blog not publishing, no targets, no competitors, no probing) and unpaid work (probing, publishing, internal jobs). Who pays comes from the billing queue. Args: org_ids (max 500) or account_stages (CRM stages), not both; include: [\"collections\"] also returns the collection summaries the flags came from (crm_collections' orgs), and then accounts is null rather than an error when billing cannot yet say who pays; since (ISO 8601): reflect data at least this new — pass your own last write time. The post-sales scope (account_stages won, onboarding, active, upsell, churned, any order) is served from a snapshot refreshed about every 10 minutes, its freshness in snapshot (computedAt, computedFromAt, stale); a since it does not reach, and every other scope, is computed live and snapshot is null. The same function as GET /api/superadmin/crm/delivery-flags and `letterstory admin crm delivery-flags`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"org_ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"account_stages":{"minItems":1,"type":"array","items":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]}},"include":{"type":"array","items":{"type":"string","enum":["collections"]}},"since":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"accounts":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"accountId":{"type":["string","null"]},"accountName":{"type":["string","null"]},"owedDelivery":{"type":"boolean","description":"They pay, or we comped them, or an agency by policy"},"notPaying":{"type":"boolean","description":"Never billed or failing, and not excused"},"rankingTargets":{"type":"object","properties":{"byUser":{"type":"number"},"inferred":{"type":"number"},"settled":{"type":"number"}},"required":["byUser","inferred","settled"]},"competitors":{"type":"object","properties":{"tracked":{"type":"number"},"blocked":{"type":"number"},"localOnly":{"type":"number"}},"required":["tracked","blocked","localOnly"]},"findings":{"type":"array","items":{"type":"object","properties":{"flag":{"type":"string","enum":["not_publishing","blog_not_publishing","no_targets","no_competitors","no_probing","probing","publishing","internal_jobs"]},"direction":{"type":"string","enum":["behind","unpaid_work"]},"message":{"type":"string"},"collectionId":{"type":["string","null"]}},"required":["flag","direction","message","collectionId"]},"description":"Worst first; empty is the goal"}},"required":["orgId","orgName","accountId","accountName","owedDelivery","notPaying","rankingTargets","competitors","findings"]}},{"type":"null"}],"description":"Null only with include collections, when billing cannot yet say who pays"},"orgs":{"description":"With include collections: the summaries the flags came from","type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"accountId":{"type":["string","null"]},"accountName":{"type":["string","null"]},"internal":{"type":"object","properties":{"value":{"type":"boolean","description":"Not a real customer: test, internal, or a non-customer lifecycle"},"source":{"anyOf":[{"type":"string","enum":["billing_override","billing_exemption","lifecycle"]},{"type":"null"}]},"label":{"type":["string","null"]},"hint":{"anyOf":[{"type":"string","enum":["is_test"]},{"type":"null"}],"description":"An unconfirmed hint, never enough on its own"}},"required":["value","source","label","hint"]},"phantomCount":{"type":"number"},"phantomOfflineCount":{"type":"number"},"mainBlogCount":{"type":"number"},"otherCount":{"type":"number"},"totalPosts":{"type":"number"},"postsLast30d":{"type":"number"},"lastPostAt":{"type":["string","null"]},"issues":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["posts_no_destination","posts_via_api","site_error","phantom_error","onboard_error","sitemap_status"]},"message":{"type":"string","description":"One line, already phrased for a person"},"collectionId":{"description":"Null for an account-level finding","type":["string","null"]}},"required":["kind","message","collectionId"]},"description":"Worst first"},"collections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"label":{"type":"string","enum":["main_blog","likely_main_blog","phantom","phantom_offline","likely_test","empty","other","unknown"]},"reason":{"type":"string","description":"Why this label"},"certain":{"type":"boolean","description":"False when the label is inferred"},"brandSubject":{"type":"string"},"publishControl":{"type":"string"},"profile":{"type":"string","description":"A display badge only; never branch on it"},"isOrgDefault":{"type":"boolean"},"cadenceTarget":{"description":"Posts per cadencePeriod promised; null = no goal set","type":["number","null"]},"cadencePeriod":{"type":"string"},"relinkEnabled":{"type":"boolean"},"relinkedLast30d":{"type":"number"},"phantomStatus":{"type":["string","null"]},"geoProbeEnabled":{"type":"boolean"},"createdAt":{"type":["string","null"]},"destination":{"type":"object","properties":{"kind":{"type":"string","enum":["phantom_site","cms","owned_site","sitemap","base_url","api","none"]},"detail":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["kind","detail","url"]},"totalPosts":{"type":"number"},"publishedPosts":{"type":"number","description":"Live posts (lifecycle_phase = published)"},"postsLast30d":{"type":"number","description":"Posts first published in the trailing 30 days"},"lastPostAt":{"type":["string","null"]},"issues":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["posts_no_destination","posts_via_api","site_error","phantom_error","onboard_error","sitemap_status"]},"message":{"type":"string","description":"One line, already phrased for a person"},"collectionId":{"description":"Null for an account-level finding","type":["string","null"]}},"required":["kind","message","collectionId"]}},"appPath":{"type":"string","description":"The collection's Control Room in the app"}},"required":["id","name","label","reason","certain","brandSubject","publishControl","profile","isOrgDefault","cadenceTarget","cadencePeriod","relinkEnabled","relinkedLast30d","phantomStatus","geoProbeEnabled","createdAt","destination","totalPosts","publishedPosts","postsLast30d","lastPostAt","issues","appPath"]}}},"required":["orgId","orgName","accountId","accountName","internal","phantomCount","phantomOfflineCount","mainBlogCount","otherCount","totalPosts","postsLast30d","lastPostAt","issues","collections"]}},"computedAt":{"type":"string","description":"When the answer was computed: the snapshot's computedAt when served from it"},"snapshot":{"anyOf":[{"type":"object","properties":{"surface":{"type":"string","enum":["crm-delivery","crm-pulse","crm-client-health"]},"computedAt":{"description":"When the snapshot was written","type":["string","null"]},"computedFromAt":{"description":"When its compute started: it reflects data at least this new (compared with since)","type":["string","null"]},"ageSeconds":{"type":["number","null"]},"durationMs":{"type":["number","null"]},"refreshSeconds":{"type":"number"},"pending":{"type":"boolean"},"stale":{"type":"boolean","description":"Older than 3x refreshSeconds: the refresher is failing or starved"},"refreshing":{"type":"boolean"},"lastError":{"type":["string","null"]},"lastAttemptAt":{"type":["string","null"]},"consecutiveFailures":{"type":"number"}},"required":["surface","computedAt","computedFromAt","ageSeconds","durationMs","refreshSeconds","pending","stale","refreshing","lastError","lastAttemptAt","consecutiveFailures"]},{"type":"null"}],"description":"Served from the post-sales snapshot: its freshness. Null when computed live"}},"required":["accounts","computedAt","snapshot"]}},{"name":"crm_pulse","description":"Staff only. How is each client doing? One entry per CRM account, keyed by account id: posts on their own site and on their phantom blogs in the last 30 days, phantom search clicks against the 28 days before, whether they likely do outbound already, the product to offer next, client Slack messages waiting on our reply, and churn risk (high, watch or ok, with reasons). Args: account_ids (max 500) for exactly those accounts; omitted = every un-archived account from won to upsell; since (ISO 8601): reflect data at least this new — pass your own last write time. The default scope is served from a snapshot refreshed about every 10 minutes (stale after 30), its freshness in snapshot (computedAt, computedFromAt, stale); a since it does not reach, and account_ids, are computed live and snapshot is null. The same function as GET /api/superadmin/crm/pulse and `letterstory admin crm pulse`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"account_ids":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"since":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"pulses":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"postsLast30d":{"description":"Posts on the client's own site in the last 30 days, from its sitemap; null when not scanned","type":["number","null"]},"phantomPostsLast30d":{"type":"number","description":"Posts first published on their phantom blogs in the last 30 days"},"clicksTrend":{"description":"Phantom search clicks, last 28 days vs the 28 before, as a ratio (0.18 = +18%); null without a baseline","type":["number","null"]},"outbound":{"type":"string","enum":["likely","unlikely","unknown"],"description":"Whether they likely do outbound already (calls, our outbound products, their site's tooling)"},"nextProduct":{"anyOf":[{"type":"string","enum":["phantomstory","kernels","shredder","ledes","traces","proofs","seers","other"]},{"type":"null"}],"description":"The product to offer next, if any"},"nextProductScore":{"description":"The winning rule's weight (0-1) in the next-product rule table, not a probability","type":["number","null"]},"slackWaiting":{"type":"number","description":"Client Slack messages still waiting on our reply"},"churnRisk":{"type":"object","properties":{"level":{"type":"string","enum":["high","watch","ok"]},"score":{"type":"number"},"reasons":{"type":"array","items":{"type":"string"},"description":"Already phrased for a person"},"ruleIds":{"type":"array","items":{"type":"string"}}},"required":["level","score","reasons"]}},"required":["postsLast30d","phantomPostsLast30d","clicksTrend","outbound","nextProduct","nextProductScore","slackWaiting","churnRisk"]},"description":"Keyed by CRM account id"},"computedAt":{"type":"string","description":"When the answer was computed: the snapshot's computedAt when served from it"},"snapshot":{"anyOf":[{"type":"object","properties":{"surface":{"type":"string","enum":["crm-delivery","crm-pulse","crm-client-health"]},"computedAt":{"description":"When the snapshot was written","type":["string","null"]},"computedFromAt":{"description":"When its compute started: it reflects data at least this new (compared with since)","type":["string","null"]},"ageSeconds":{"type":["number","null"]},"durationMs":{"type":["number","null"]},"refreshSeconds":{"type":"number"},"pending":{"type":"boolean"},"stale":{"type":"boolean","description":"Older than 3x refreshSeconds: the refresher is failing or starved"},"refreshing":{"type":"boolean"},"lastError":{"type":["string","null"]},"lastAttemptAt":{"type":["string","null"]},"consecutiveFailures":{"type":"number"}},"required":["surface","computedAt","computedFromAt","ageSeconds","durationMs","refreshSeconds","pending","stale","refreshing","lastError","lastAttemptAt","consecutiveFailures"]},{"type":"null"}],"description":"Served from the post-sales pulse snapshot: its freshness. Null when computed live"}},"required":["pulses","computedAt","snapshot"]}},{"name":"crm_client_health","description":"Staff only. Can we reach each fleet client in Slack? One row per org running a phantom site: its channel and whether we are in the room (reachable, bot_missing, no_channel), plus the clients set up with a channel and something wrong (invitations expired or unanswered, nobody from the client joined, everyone left), worst first, and how many have a channel at all (covered of total). Args: since (ISO 8601): reflect data at least this new — pass your own last write time. Served from a snapshot refreshed about every 10 minutes, its freshness in snapshot (computedAt, computedFromAt, stale); a since it does not reach is computed live and snapshot is null. The same function as GET /api/superadmin/client-health and `letterstory admin crm client-health`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"since":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"covered":{"type":"number","description":"Fleet clients (orgs running at least one phantom site) with a Slack channel"},"total":{"type":"number","description":"Fleet clients total; covered/total is rollout, not risk"},"missingChannel":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"}},"required":["orgId","orgName"]},"description":"Fleet clients with no Slack channel"},"atRisk":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"channelId":{"type":"string"},"flags":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"invite_expired"},"emails":{"type":"array","items":{"type":"string"}}},"required":["kind","emails"],"description":"Invitation past Slack's 14-day expiry: only a re-send helps"},{"type":"object","properties":{"kind":{"type":"string","const":"invite_pending"},"emails":{"type":"array","items":{"type":"string"}},"oldestDays":{"type":"number"}},"required":["kind","emails","oldestDays"],"description":"Sent, still live, still unanswered"},{"type":"object","properties":{"kind":{"type":"string","const":"nobody_joined"}},"required":["kind"],"description":"We post into the channel and nobody from the client is there"},{"type":"object","properties":{"kind":{"type":"string","const":"all_left"},"emails":{"type":"array","items":{"type":"string"}}},"required":["kind","emails"],"description":"Everyone we invited has since left"}]}}},"required":["orgId","orgName","channelId","flags"]},"description":"Only clients with a channel and something wrong, worst first"},"channels":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"orgName":{"type":"string"},"channelId":{"type":["string","null"]},"state":{"type":"string","enum":["reachable","bot_missing","no_channel"],"description":"reachable: linked and we have seen the Watchdog in the room; bot_missing: the CRM links a room the Watchdog has never seen; no_channel: nothing linked"},"source":{"anyOf":[{"type":"string","enum":["crm","binding"]},{"type":"null"}],"description":"Which store the channel came from"},"lastMessageAt":{"description":"Newest human message either side, when seen","type":["string","null"]}},"required":["orgId","orgName","channelId","state","source","lastMessageAt"]},"description":"One row per fleet client"},"computedAt":{"type":"string","description":"When the answer was computed: the snapshot's computedAt when served from it"},"snapshot":{"anyOf":[{"type":"object","properties":{"surface":{"type":"string","enum":["crm-delivery","crm-pulse","crm-client-health"]},"computedAt":{"description":"When the snapshot was written","type":["string","null"]},"computedFromAt":{"description":"When its compute started: it reflects data at least this new (compared with since)","type":["string","null"]},"ageSeconds":{"type":["number","null"]},"durationMs":{"type":["number","null"]},"refreshSeconds":{"type":"number"},"pending":{"type":"boolean"},"stale":{"type":"boolean","description":"Older than 3x refreshSeconds: the refresher is failing or starved"},"refreshing":{"type":"boolean"},"lastError":{"type":["string","null"]},"lastAttemptAt":{"type":["string","null"]},"consecutiveFailures":{"type":"number"}},"required":["surface","computedAt","computedFromAt","ageSeconds","durationMs","refreshSeconds","pending","stale","refreshing","lastError","lastAttemptAt","consecutiveFailures"]},{"type":"null"}],"description":"Served from the crm-client-health snapshot: its freshness. Null when computed live"}},"required":["covered","total","missingChannel","atRisk","channels","computedAt","snapshot"]}},{"name":"start_signup_fit","description":"Staff only. Start the silent fit read for a work email's company domain (or reuse one from the last 30 days): context.dev brand + homepage + a funding search with highlights, judged by Jev (Claude when Jev is unavailable), then rules decide bad / good / great. Returns at once with the read (status pending while it runs; poll get_signup_fit). A free-mail address is refused with refused: free_mail and starts nothing. Args: email. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The visitor's work email"}},"required":["email"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"refused":{"anyOf":[{"type":"string","enum":["free_mail","invalid"]},{"type":"null"}],"description":"free_mail = ask for a work email"},"read":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","done","failed"],"description":"pending = still reading; failed = adds nothing to the tier"},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"What the read decided"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"}},"answers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["number","null"]},"description":"The judge's yes/no probabilities"},"engine":{"anyOf":[{"type":"string","enum":["jev","claude","rules"]},{"type":"null"}]},"rulesVersion":{"type":["number","null"]},"error":{"type":["string","null"]},"overrideVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"A person's verdict; wins over the read (great = tier 1, good = tier 2, bad = self-serve)"},"overrideNote":{"type":["string","null"]},"overrideByUserId":{"type":["string","null"]},"overrideAt":{"type":["string","null"]},"effectiveVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean","description":"Raised outside money (YC, a round, funding the search found)"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"tierReasons":{"type":"array","items":{"type":"string"},"description":"Why the company landed in its tier"},"tierRulesVersion":{"type":["number","null"]},"tierDecidedAt":{"type":["string","null"]},"demoRequired":{"type":"boolean","description":"The dial, as it is now, makes this company book a demo before using Letter"},"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"],"description":"Where it routes now: the assignee's calendar, or signup"},"demoBookedAt":{"description":"When the cal.com webhook saw a booking","type":["string","null"]},"demoBookedFor":{"description":"When the booked call starts","type":["string","null"]},"demoUnlockedAt":{"description":"When a person opened Letter for the company after its demo","type":["string","null"]},"demoUnlockedBy":{"description":"Who unlocked it (user id)","type":["string","null"]},"companyName":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"decidedAt":{"type":["string","null"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tierFacts":{"description":"What the tier read: yc, funding (Crustdata), crmStage, read","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","domain","status","verdict","confidence","reasons","answers","engine","rulesVersion","error","overrideVerdict","overrideNote","overrideByUserId","overrideAt","effectiveVerdict","tier","funded","assignee","tierReasons","tierRulesVersion","tierDecidedAt","demoRequired","destination","demoBookedAt","demoBookedFor","demoUnlockedAt","demoUnlockedBy","companyName","logoUrl","startedAt","decidedAt"]},{"type":"null"}]},"started":{"type":"boolean","description":"True when this call started a new read"}},"required":["refused","read","started"]}},{"name":"get_signup_fit","description":"Staff only. One silent fit read, by read_id or domain: status, verdict, the override (which wins), the effective verdict, the reasons, the judge's yes/no probabilities and the evidence it read; then the company's Get started tier (1 Mathew, 2 Neha or Kaya, null self-serve), funded, the assignee, why (tierReasons, tierFacts: YC, Crustdata, the CRM, the read), whether the dial now makes it book a demo first (demoRequired) and the destination (book_matthew / book_neha / book_kaya / signup), and its demo: booked (demoBookedAt, demoBookedFor) and unlocked (demoUnlockedAt, demoUnlockedBy). read is null when the domain was never read. Args: read_id or domain. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"read_id":{"description":"The id start_signup_fit returned","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"domain":{"description":"Or the company domain","type":"string","minLength":3,"maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"read":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","done","failed"],"description":"pending = still reading; failed = adds nothing to the tier"},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"What the read decided"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"}},"answers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["number","null"]},"description":"The judge's yes/no probabilities"},"engine":{"anyOf":[{"type":"string","enum":["jev","claude","rules"]},{"type":"null"}]},"rulesVersion":{"type":["number","null"]},"error":{"type":["string","null"]},"overrideVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"A person's verdict; wins over the read (great = tier 1, good = tier 2, bad = self-serve)"},"overrideNote":{"type":["string","null"]},"overrideByUserId":{"type":["string","null"]},"overrideAt":{"type":["string","null"]},"effectiveVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean","description":"Raised outside money (YC, a round, funding the search found)"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"tierReasons":{"type":"array","items":{"type":"string"},"description":"Why the company landed in its tier"},"tierRulesVersion":{"type":["number","null"]},"tierDecidedAt":{"type":["string","null"]},"demoRequired":{"type":"boolean","description":"The dial, as it is now, makes this company book a demo before using Letter"},"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"],"description":"Where it routes now: the assignee's calendar, or signup"},"demoBookedAt":{"description":"When the cal.com webhook saw a booking","type":["string","null"]},"demoBookedFor":{"description":"When the booked call starts","type":["string","null"]},"demoUnlockedAt":{"description":"When a person opened Letter for the company after its demo","type":["string","null"]},"demoUnlockedBy":{"description":"Who unlocked it (user id)","type":["string","null"]},"companyName":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"decidedAt":{"type":["string","null"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tierFacts":{"description":"What the tier read: yc, funding (Crustdata), crmStage, read","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","domain","status","verdict","confidence","reasons","answers","engine","rulesVersion","error","overrideVerdict","overrideNote","overrideByUserId","overrideAt","effectiveVerdict","tier","funded","assignee","tierReasons","tierRulesVersion","tierDecidedAt","demoRequired","destination","demoBookedAt","demoBookedFor","demoUnlockedAt","demoUnlockedBy","companyName","logoUrl","startedAt","decidedAt"]},{"type":"null"}]}},"required":["read"]}},{"name":"list_signup_fits","description":"Staff only. Recent silent fit reads, newest first, each with its verdict, override, tier, assignee, whether a demo comes first, destination, demo booked and unlocked, and reasons (no evidence; get_signup_fit has it). Watch every Get started sort as it happens. Args: verdict (bad|good|great), limit (1-200, default 50). Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"verdict":{"type":"string","enum":["bad","good","great"]},"limit":{"description":"Default 50","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reads":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","done","failed"],"description":"pending = still reading; failed = adds nothing to the tier"},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"What the read decided"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"}},"answers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["number","null"]},"description":"The judge's yes/no probabilities"},"engine":{"anyOf":[{"type":"string","enum":["jev","claude","rules"]},{"type":"null"}]},"rulesVersion":{"type":["number","null"]},"error":{"type":["string","null"]},"overrideVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"A person's verdict; wins over the read (great = tier 1, good = tier 2, bad = self-serve)"},"overrideNote":{"type":["string","null"]},"overrideByUserId":{"type":["string","null"]},"overrideAt":{"type":["string","null"]},"effectiveVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean","description":"Raised outside money (YC, a round, funding the search found)"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"tierReasons":{"type":"array","items":{"type":"string"},"description":"Why the company landed in its tier"},"tierRulesVersion":{"type":["number","null"]},"tierDecidedAt":{"type":["string","null"]},"demoRequired":{"type":"boolean","description":"The dial, as it is now, makes this company book a demo before using Letter"},"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"],"description":"Where it routes now: the assignee's calendar, or signup"},"demoBookedAt":{"description":"When the cal.com webhook saw a booking","type":["string","null"]},"demoBookedFor":{"description":"When the booked call starts","type":["string","null"]},"demoUnlockedAt":{"description":"When a person opened Letter for the company after its demo","type":["string","null"]},"demoUnlockedBy":{"description":"Who unlocked it (user id)","type":["string","null"]},"companyName":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"decidedAt":{"type":["string","null"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tierFacts":{"description":"What the tier read: yc, funding (Crustdata), crmStage, read","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","domain","status","verdict","confidence","reasons","answers","engine","rulesVersion","error","overrideVerdict","overrideNote","overrideByUserId","overrideAt","effectiveVerdict","tier","funded","assignee","tierReasons","tierRulesVersion","tierDecidedAt","demoRequired","destination","demoBookedAt","demoBookedFor","demoUnlockedAt","demoUnlockedBy","companyName","logoUrl","startedAt","decidedAt"]}}},"required":["reads"]}},{"name":"override_signup_fit","description":"Staff only. Set the verdict for a domain by hand (bad / good / great), or clear it with verdict null. The override wins over every read, now and after re-reads, and over the tier: great = tier 1 (Mathew), good = tier 2 funded (Neha or Kaya), bad = self-serve. Records who set it and when. Args: domain, verdict, note. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":3,"maxLength":253},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"null clears the override"},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["domain","verdict"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"read":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","done","failed"],"description":"pending = still reading; failed = adds nothing to the tier"},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"What the read decided"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"}},"answers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["number","null"]},"description":"The judge's yes/no probabilities"},"engine":{"anyOf":[{"type":"string","enum":["jev","claude","rules"]},{"type":"null"}]},"rulesVersion":{"type":["number","null"]},"error":{"type":["string","null"]},"overrideVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"A person's verdict; wins over the read (great = tier 1, good = tier 2, bad = self-serve)"},"overrideNote":{"type":["string","null"]},"overrideByUserId":{"type":["string","null"]},"overrideAt":{"type":["string","null"]},"effectiveVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean","description":"Raised outside money (YC, a round, funding the search found)"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"tierReasons":{"type":"array","items":{"type":"string"},"description":"Why the company landed in its tier"},"tierRulesVersion":{"type":["number","null"]},"tierDecidedAt":{"type":["string","null"]},"demoRequired":{"type":"boolean","description":"The dial, as it is now, makes this company book a demo before using Letter"},"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"],"description":"Where it routes now: the assignee's calendar, or signup"},"demoBookedAt":{"description":"When the cal.com webhook saw a booking","type":["string","null"]},"demoBookedFor":{"description":"When the booked call starts","type":["string","null"]},"demoUnlockedAt":{"description":"When a person opened Letter for the company after its demo","type":["string","null"]},"demoUnlockedBy":{"description":"Who unlocked it (user id)","type":["string","null"]},"companyName":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"decidedAt":{"type":["string","null"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tierFacts":{"description":"What the tier read: yc, funding (Crustdata), crmStage, read","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","domain","status","verdict","confidence","reasons","answers","engine","rulesVersion","error","overrideVerdict","overrideNote","overrideByUserId","overrideAt","effectiveVerdict","tier","funded","assignee","tierReasons","tierRulesVersion","tierDecidedAt","demoRequired","destination","demoBookedAt","demoBookedFor","demoUnlockedAt","demoUnlockedBy","companyName","logoUrl","startedAt","decidedAt"]},{"type":"null"}]}},"required":["read"]}},{"name":"submit_get_started","description":"Staff only. The Get started pop-up was sent (name, work email, company). Draws the company's tier from the YC snapshot, Crustdata, the fit read as it stands and a person's override: tier 1 (Series A or later, $10M+ raised, 50+ people, a live CRM deal or a logo wall) books with Mathew; tier 2 (any outside funding, or paying customers) with Neha or Kaya, split 60/40 by the domain; else self-serve. Under the staff dial (get_get_started_settings) route.demoRequired suggests a demo first, for a caller that routes on it (the homepage): show route.cal. Nothing in the app holds anyone on it: /crossing/letter and the app are self-serve for everyone, and the public /get-started always answers self-serve. Otherwise offer Start now (route.signupUrl, the self-serve /crossing/letter with the email filled in) with Book a call (route.cal) as a second button. route.cal is always present. Starts the fit read if there is none (runReadId) and records one CRM signal (source website, kind form) with the tier and the route. A free-mail address (Gmail and the like) self-serves with no read and no tier. Internal domains are never sent to a demo; a staff +great / +good / +bad address picks the tier. Args: name, email, company, page. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The visitor's work email"},"company":{"type":"string","minLength":1,"maxLength":200},"page":{"description":"Where the form was, e.g. launch.letter.dev","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"pass":{"description":"A self-serve pass code (/get-started?pass=): while it works, never a demo first, carried on to signup","anyOf":[{"type":"string","maxLength":64},{"type":"null"}]}},"required":["name","email","company"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"route":{"type":"object","properties":{"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"demoRequired":{"type":"boolean","description":"The tiers suggest a demo first (for a caller that routes on it; the app holds no one); false = Start now, with Book a call as a second button"},"reasons":{"type":"array","items":{"type":"string"}},"cal":{"type":"object","properties":{"origin":{"type":"string","description":"The Cal.com instance, for the embed"},"link":{"type":"string","description":"The event path on it, for the embed"},"url":{"type":"string","description":"The whole booking URL"}},"required":["origin","link","url"],"description":"Always present: the assignee's calendar, or the general Book a call"},"signupUrl":{"type":"string","description":"The self-serve crossing, email filled in"}},"required":["destination","tier","funded","assignee","demoRequired","reasons","cal","signupUrl"]},"readStatus":{"type":"string","enum":["pending","done","failed","none"]},"readId":{"type":["string","null"]},"signalId":{"type":["string","null"]},"runReadId":{"description":"A fit read this call started (it runs after the response)","type":["string","null"]},"pass":{"anyOf":[{"type":"string","enum":["valid","expired","used_up","revoked","unknown"]},{"type":"null"}],"description":"The self-serve pass the submit carried and its state; only valid skips the demo (null: none)"}},"required":["route","readStatus","readId","signalId","runReadId","pass"]}},{"name":"unlock_demo","description":"Staff only. Record that a company's demo is done, on its Get started read, as who and when (demoUnlockedBy, demoUnlockedAt); undo: true clears both. A record only: since 2026-10-30 nothing holds an org behind a demo, so this opens and closes nothing. Args: domain (or a work email), undo. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":3,"maxLength":253,"description":"The company domain (or a work email's domain)"},"undo":{"description":"true locks it again: clears who unlocked it and when","type":"boolean"}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"read":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","done","failed"],"description":"pending = still reading; failed = adds nothing to the tier"},"verdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"What the read decided"},"confidence":{"type":["number","null"]},"reasons":{"type":"array","items":{"type":"string"}},"answers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["number","null"]},"description":"The judge's yes/no probabilities"},"engine":{"anyOf":[{"type":"string","enum":["jev","claude","rules"]},{"type":"null"}]},"rulesVersion":{"type":["number","null"]},"error":{"type":["string","null"]},"overrideVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}],"description":"A person's verdict; wins over the read (great = tier 1, good = tier 2, bad = self-serve)"},"overrideNote":{"type":["string","null"]},"overrideByUserId":{"type":["string","null"]},"overrideAt":{"type":["string","null"]},"effectiveVerdict":{"anyOf":[{"type":"string","enum":["bad","good","great"]},{"type":"null"}]},"tier":{"anyOf":[{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}]},{"type":"null"}],"description":"1 = Mathew; 2 = Neha or Kaya; null = self-serve"},"funded":{"type":"boolean","description":"Raised outside money (YC, a round, funding the search found)"},"assignee":{"anyOf":[{"type":"string","enum":["mathew","neha","kaya"]},{"type":"null"}],"description":"Who the demo is booked with; null for self-serve"},"tierReasons":{"type":"array","items":{"type":"string"},"description":"Why the company landed in its tier"},"tierRulesVersion":{"type":["number","null"]},"tierDecidedAt":{"type":["string","null"]},"demoRequired":{"type":"boolean","description":"The dial, as it is now, makes this company book a demo before using Letter"},"destination":{"type":"string","enum":["book_matthew","book_neha","book_kaya","signup"],"description":"Where it routes now: the assignee's calendar, or signup"},"demoBookedAt":{"description":"When the cal.com webhook saw a booking","type":["string","null"]},"demoBookedFor":{"description":"When the booked call starts","type":["string","null"]},"demoUnlockedAt":{"description":"When a person opened Letter for the company after its demo","type":["string","null"]},"demoUnlockedBy":{"description":"Who unlocked it (user id)","type":["string","null"]},"companyName":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"decidedAt":{"type":["string","null"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tierFacts":{"description":"What the tier read: yc, funding (Crustdata), crmStage, read","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["id","domain","status","verdict","confidence","reasons","answers","engine","rulesVersion","error","overrideVerdict","overrideNote","overrideByUserId","overrideAt","effectiveVerdict","tier","funded","assignee","tierReasons","tierRulesVersion","tierDecidedAt","demoRequired","destination","demoBookedAt","demoBookedFor","demoUnlockedAt","demoUnlockedBy","companyName","logoUrl","startedAt","decidedAt"]}},"required":["read"]}},{"name":"create_self_serve_pass","description":"Staff only. Mint a self-serve pass: a link (url, https://app.letterstory.com/get-started?pass=CODE). Signup is self-serve for everyone since 2026-10-30, so a pass changes nothing; it only records which link someone came in on. /get-started shows Start now; the crossing carries the pass through signup and redeems it the moment their org exists, stamping the org demo_exempt_at / demo_exempt_by (pass:<ref>, the code's first 8 characters). Each org that redeems it counts one use. An expired, revoked or used-up pass behaves like no pass. Args: label (who it is for), max_uses (absent = unlimited), expires_in_days (absent = never). Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":200,"description":"Who or what it is for, e.g. 'Acme founders'"},"max_uses":{"description":"How many orgs may redeem it; absent = unlimited","type":"integer","minimum":1,"maximum":10000},"expires_in_days":{"description":"Days until it stops working; absent = never","type":"integer","minimum":1,"maximum":365}},"required":["label"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"code":{"type":"string"},"url":{"type":"string","description":"The link to share: /get-started?pass=CODE"},"pass":{"type":"object","properties":{"code":{"type":"string"},"ref":{"type":"string","description":"The code's first 8 characters: what an exempt org's demo_exempt_by names (pass:<ref>)"},"url":{"type":"string","description":"The link to share: /get-started?pass=CODE"},"label":{"type":"string"},"state":{"type":"string","enum":["valid","expired","used_up","revoked","unknown"]},"createdBy":{"type":["string","null"]},"createdAt":{"type":"string"},"expiresAt":{"description":"null = never","type":["string","null"]},"maxUses":{"description":"null = unlimited","type":["number","null"]},"uses":{"type":"number","description":"Orgs that redeemed it"},"revokedAt":{"type":["string","null"]},"revokedBy":{"type":["string","null"]}},"required":["code","ref","url","label","state","createdBy","createdAt","expiresAt","maxUses","uses","revokedAt","revokedBy"]}},"required":["code","url","pass"]}},{"name":"list_self_serve_passes","description":"Staff only. Self-serve passes, newest first: code, ref (what an exempt org's demo_exempt_by names), link, label, state (valid | expired | used_up | revoked), uses against max_uses, expiry, and who made or revoked each. Args: include_revoked (default false), limit (1-500, default 100). Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"include_revoked":{"description":"Include revoked passes (default false)","type":"boolean"},"limit":{"description":"Default 100","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"passes":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"ref":{"type":"string","description":"The code's first 8 characters: what an exempt org's demo_exempt_by names (pass:<ref>)"},"url":{"type":"string","description":"The link to share: /get-started?pass=CODE"},"label":{"type":"string"},"state":{"type":"string","enum":["valid","expired","used_up","revoked","unknown"]},"createdBy":{"type":["string","null"]},"createdAt":{"type":"string"},"expiresAt":{"description":"null = never","type":["string","null"]},"maxUses":{"description":"null = unlimited","type":["number","null"]},"uses":{"type":"number","description":"Orgs that redeemed it"},"revokedAt":{"type":["string","null"]},"revokedBy":{"type":["string","null"]}},"required":["code","ref","url","label","state","createdBy","createdAt","expiresAt","maxUses","uses","revokedAt","revokedBy"]}}},"required":["passes"]}},{"name":"revoke_self_serve_pass","description":"Staff only. Stop a self-serve pass working from now on; who and when are recorded. Orgs that already redeemed it keep the record (their row says pass:<ref>); a pass changes nothing since signup became self-serve for everyone. Args: code. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"code":{"type":"string","minLength":16,"maxLength":64,"description":"The pass code (the link's ?pass=)"}},"required":["code"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"pass":{"type":"object","properties":{"code":{"type":"string"},"ref":{"type":"string","description":"The code's first 8 characters: what an exempt org's demo_exempt_by names (pass:<ref>)"},"url":{"type":"string","description":"The link to share: /get-started?pass=CODE"},"label":{"type":"string"},"state":{"type":"string","enum":["valid","expired","used_up","revoked","unknown"]},"createdBy":{"type":["string","null"]},"createdAt":{"type":"string"},"expiresAt":{"description":"null = never","type":["string","null"]},"maxUses":{"description":"null = unlimited","type":["number","null"]},"uses":{"type":"number","description":"Orgs that redeemed it"},"revokedAt":{"type":["string","null"]},"revokedBy":{"type":["string","null"]}},"required":["code","ref","url","label","state","createdBy","createdAt","expiresAt","maxUses","uses","revokedAt","revokedBy"]}},"required":["pass"]}},{"name":"get_get_started_settings","description":"Staff only. The Get started dials (crm_settings, the same as GET /api/superadmin/crm/settings): get_started_demo_first (off = everyone self-serves | tier1 | funded = tier 1 and funded tier 2 | all = tiers 1 and 2 | everyone = every company, booked by tier 2's split; never an existing customer, a company already demoed or our own domain) — companies at or above it are sent to a demo first by submit_get_started; get_started_calendars (Cal.com links for mathew, neha, kaya and default, the general Book a call); get_started_tier2_weights (tier 2's Neha / Kaya split); get_started_thresholds (what makes tier 1). Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"settings":{"type":"object","properties":{"get_started_demo_first":{"type":"string","enum":["off","tier1","funded","all","everyone"]},"get_started_calendars":{"type":"object","properties":{"mathew":{"type":["string","null"]},"neha":{"type":["string","null"]},"kaya":{"type":["string","null"]},"default":{"type":["string","null"]}},"required":["mathew","neha","kaya","default"]},"get_started_tier2_weights":{"type":"object","properties":{"neha":{"type":"number"},"kaya":{"type":"number"}},"required":["neha","kaya"]},"get_started_thresholds":{"type":"object","properties":{"tier1Stage":{"type":"string","enum":["pre_seed","seed","series_a","series_b","series_c","growth","public"]},"tier1RaisedUsd":{"type":"number"},"tier1Employees":{"type":"number"}},"required":["tier1Stage","tier1RaisedUsd","tier1Employees"]}},"required":["get_started_demo_first","get_started_calendars","get_started_tier2_weights","get_started_thresholds"]}},"required":["settings"]}},{"name":"set_get_started_settings","description":"Staff only. Change the Get started dials; anything absent is left alone, and who changed it is recorded. get_started_demo_first sets which tiers submit_get_started suggests a demo first for (nothing in the app holds anyone on it); a host with a null calendar is never assigned (tier 2 then goes to Neha). Args: get_started_demo_first, get_started_calendars, get_started_tier2_weights, get_started_thresholds. The same rows and schemas as PUT /api/superadmin/crm/settings. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"get_started_demo_first":{"description":"off (everyone self-serves) | tier1 | funded (tier 1 + funded tier 2) | all (tiers 1 and 2) | everyone (every company, booked by tier 2's split): who the homepage sends to a demo first","type":"string","enum":["off","tier1","funded","all","everyone"]},"get_started_calendars":{"description":"Cal.com links by host: mathew, neha, kaya, default (the general Book a call; null = Neha's)","type":"object","properties":{"mathew":{"anyOf":[{"type":"string","maxLength":300,"format":"uri"},{"type":"null"}]},"neha":{"anyOf":[{"type":"string","maxLength":300,"format":"uri"},{"type":"null"}]},"kaya":{"anyOf":[{"type":"string","maxLength":300,"format":"uri"},{"type":"null"}]},"default":{"anyOf":[{"type":"string","maxLength":300,"format":"uri"},{"type":"null"}]}},"required":["mathew","neha","kaya","default"]},"get_started_tier2_weights":{"description":"Tier 2's split by weight, e.g. { neha: 60, kaya: 40 }","type":"object","properties":{"neha":{"type":"integer","minimum":0,"maximum":100},"kaya":{"type":"integer","minimum":0,"maximum":100}},"required":["neha","kaya"]},"get_started_thresholds":{"description":"What makes tier 1: tier1Stage (or later), tier1RaisedUsd, tier1Employees","type":"object","properties":{"tier1Stage":{"type":"string","enum":["pre_seed","seed","series_a","series_b","series_c","growth","public"]},"tier1RaisedUsd":{"type":"integer","minimum":0,"maximum":9007199254740991},"tier1Employees":{"type":"integer","minimum":1,"maximum":9007199254740991}},"required":["tier1Stage","tier1RaisedUsd","tier1Employees"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"settings":{"type":"object","properties":{"get_started_demo_first":{"type":"string","enum":["off","tier1","funded","all","everyone"]},"get_started_calendars":{"type":"object","properties":{"mathew":{"type":["string","null"]},"neha":{"type":["string","null"]},"kaya":{"type":["string","null"]},"default":{"type":["string","null"]}},"required":["mathew","neha","kaya","default"]},"get_started_tier2_weights":{"type":"object","properties":{"neha":{"type":"number"},"kaya":{"type":"number"}},"required":["neha","kaya"]},"get_started_thresholds":{"type":"object","properties":{"tier1Stage":{"type":"string","enum":["pre_seed","seed","series_a","series_b","series_c","growth","public"]},"tier1RaisedUsd":{"type":"number"},"tier1Employees":{"type":"number"}},"required":["tier1Stage","tier1RaisedUsd","tier1Employees"]}},"required":["get_started_demo_first","get_started_calendars","get_started_tier2_weights","get_started_thresholds"]}},"required":["settings"]}},{"name":"crm_highlight_verdict","description":"Staff only. Keep or Pass on a customer-love highlight (crm_highlights), or null to take either back — the same as the ✓ Keep / ✗ Pass buttons under each clip in #customer-love and in the CRM. A Pass takes the moment off the account's timeline and it never posts again; every verdict is shown to the detector as an example, so this is how its picks improve. Recorded as the key's creator. Args: highlight_id, verdict (keep | pass | null). The same as POST /api/superadmin/crm/highlights/{id}/verdict. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"verdict":{"anyOf":[{"type":"string","enum":["keep","pass"]},{"type":"null"}]},"highlight_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["verdict","highlight_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}},{"name":"crm_highlight_speaker","description":"Staff only. Set a customer-love highlight's (crm_highlights) speaker from its CRM contact: the contact's name instead of Circleback's label (\"Arnav\", not \"arnavgupta\"), and a title — the one it has, else the contact's, else a sure web lookup at the contact's own company, which also fills the contact's empty title. Or pass speaker_name / speaker_title to set them as given. Writes the highlight and its timeline entry; the #customer-love post is not edited. dry_run says what would change and looks nothing up. Args: highlight_id, speaker_name, speaker_title, lookup (default true), dry_run. The same as POST /api/superadmin/crm/highlights/{id}/speaker. Refused unless the key was created by Letter staff. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{"speaker_name":{"description":"Omitted: the CRM contact's name","type":"string","minLength":1,"maxLength":200},"speaker_title":{"description":"Omitted: kept, else the contact's, else looked up","type":"string","minLength":1,"maxLength":120},"lookup":{"description":"Default true: look a missing title up on the web","type":"boolean"},"dry_run":{"description":"Say what would change; write nothing and look nothing up","type":"boolean"},"highlight_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["highlight_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"changed":{"type":"boolean","description":"Whether anything changed (dry run: would change, before any title lookup)"},"needsLookup":{"type":"boolean","description":"Dry run: no title yet, and a contact a lookup could find one for"},"skipped":{"anyOf":[{"type":"string","enum":["unplaced","no_contact"]},{"type":"null"}],"description":"unplaced: no account to find the contact on; no_contact: no single contact matches the speaker (a stale link to another account's contact is still dropped, so changed can be true)"},"before":{"type":"object","properties":{"speakerName":{"type":["string","null"]},"speakerTitle":{"type":["string","null"]},"contactId":{"type":["string","null"]}},"required":["speakerName","speakerTitle","contactId"]},"after":{"type":"object","properties":{"speakerName":{"type":["string","null"]},"speakerTitle":{"type":["string","null"]},"contactId":{"type":["string","null"]}},"required":["speakerName","speakerTitle","contactId"]}},"required":["id","changed","needsLookup","skipped","before","after"]}},{"name":"crm_renewals","description":"Staff only. Live customers whose contract renews within N days (renews_at, with the notice deadline), and those with a Stripe cancel scheduled that still stands, soonest first (docs/architecture/retention.md, Contracts and renewals). Args: days (1-365, default 90). The same list as GET /api/superadmin/crm/renewals and `letterstory admin crm renewals`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"days":{"description":"1-365; omitted = 90","type":"integer","minimum":1,"maximum":365}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"days":{"type":"number"},"renewals":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"orgId":{"type":["string","null"]},"renewsAt":{"description":"YYYY-MM-DD","type":["string","null"]},"noticeBy":{"description":"renewsAt minus noticeDays","type":["string","null"]},"termMonths":{"type":["number","null"]},"cancelScheduledAt":{"description":"When Stripe's cancel was scheduled (still standing)","type":["string","null"]},"cancelsAt":{"description":"When that cancel takes effect","type":["string","null"]},"daysLeft":{"type":["number","null"]}},"required":["accountId","name","stage","orgId","renewsAt","noticeBy","termMonths","cancelScheduledAt","cancelsAt","daysLeft"]}}},"required":["days","renewals"]}},{"name":"crm_track_record","description":"Staff only. How often each churn-risk rule, risk level (high; watch = watch or high) and next-product rule was right (docs/architecture/retention.md, Insights), judged on the daily account snapshots and confirmed outcomes: accounts flagged and when first, hits within 90 days of the first flag (risk: churn or contraction; next product: an expansion, and productHits when it added the rule's product), outcomes it missed, the base rate among all customers, and outcome rates for flags a person acted on within 14 days vs not (observational: the desk acts on the accounts it worries about most). Every rate is null until the predictor is mature: 15 confirmed outcomes of its kind and a flag 90 days old. Args: since (YYYY-MM-DD). The same numbers as GET /api/superadmin/crm/track-record and `letterstory admin crm track-record`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"since":{"description":"First snapshot day to read (YYYY-MM-DD); omitted = every snapshot","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"since":{"description":"The first snapshot day read","type":["string","null"]},"asOf":{"type":"string"},"windowDays":{"type":"number","description":"A flag is judged on outcomes within this many days of it (90)"},"actedDays":{"type":"number","description":"A person acted when they touched the account within this many days (14)"},"minOutcomes":{"type":"number","description":"Confirmed outcomes of a kind before any rate is shown (15)"},"outcomes":{"type":"object","properties":{"churn":{"type":"number"},"contraction":{"type":"number"},"expansion":{"type":"number"}},"required":["churn","contraction","expansion"]},"base":{"type":"object","properties":{"loss":{"type":"object","properties":{"flags":{"type":"number"},"hits":{"type":"number"},"rate":{"description":"hits / flags; null until the predictor is mature","type":["number","null"]},"mature":{"type":"boolean"}},"required":["flags","hits","rate","mature"]},"expansion":{"type":"object","properties":{"flags":{"type":"number"},"hits":{"type":"number"},"rate":{"description":"hits / flags; null until the predictor is mature","type":["number","null"]},"mature":{"type":"boolean"}},"required":["flags","hits","rate","mature"]}},"required":["loss","expansion"],"description":"The outcome rate among every customer from their first snapshot, same window"},"predictors":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"risk_rule:<id> | risk_level:high|watch (watch = watch or high) | next_product:<id>"},"family":{"type":"string","enum":["risk_rule","risk_level","next_product"]},"rule":{"type":"string"},"label":{"type":"string","description":"What the desk calls it: the rule's label from its rule table, or the risk level's"},"product":{"anyOf":[{"type":"string","enum":["phantomstory","kernels","shredder","ledes","traces","proofs","seers","other"]},{"type":"null"}]},"predicts":{"type":"string","enum":["loss","expansion"],"description":"loss = churn or contraction"},"flagged":{"type":"number","description":"Distinct accounts flagged"},"firstFlaggedOn":{"type":["string","null"]},"matured":{"type":"number","description":"Flags whose 90-day window has passed"},"hits":{"type":"number","description":"Flags followed by the outcome within 90 days, matured or not"},"maturedRate":{"type":"object","properties":{"flags":{"type":"number"},"hits":{"type":"number"},"rate":{"description":"hits / flags; null until the predictor is mature","type":["number","null"]}},"required":["flags","hits","rate"]},"productHits":{"description":"Next product only: expansions that added the rule's product","type":["number","null"]},"missed":{"type":"number","description":"Churns (risk) or expansions (next product) with no flag before them"},"baseRate":{"type":["number","null"]},"acted":{"type":"object","properties":{"flags":{"type":"number"},"hits":{"type":"number"},"rate":{"description":"hits / flags; null until the predictor is mature","type":["number","null"]}},"required":["flags","hits","rate"],"description":"Matured flags a person acted on within 14 days"},"notActed":{"type":"object","properties":{"flags":{"type":"number"},"hits":{"type":"number"},"rate":{"description":"hits / flags; null until the predictor is mature","type":["number","null"]}},"required":["flags","hits","rate"]},"mature":{"type":"boolean"},"notMatureBecause":{"type":["string","null"]},"flags":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"firstFlaggedOn":{"type":"string"},"matured":{"type":"boolean"},"acted":{"type":"boolean"},"outcome":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"on":{"type":"string"}},"required":["id","kind","on"]},{"type":"null"}]}},"required":["accountId","firstFlaggedOn","matured","acted","outcome"]}}},"required":["key","family","rule","label","product","predicts","flagged","firstFlaggedOn","matured","hits","maturedRate","productHits","missed","baseRate","acted","notActed","mature","notMatureBecause","flags"]}}},"required":["since","asOf","windowDays","actedDays","minOutcomes","outcomes","base","predictors"]}},{"name":"crm_at_risk","description":"Staff only. Customers the live pulse rates watch or high for churn, riskiest first, with the pulse's reasons and rule ids, revenue, and the closest past churn (docs/architecture/retention.md, Insights): the confirmed churn whose account looked most alike at the same tenure on five plain features (risk rules shared, revenue band, live phantoms, posting trend, quiet days), with every feature's value now and then and whether it matched. A case to read, not a probability; null when no past churn matches 3 of 5. The same list as GET /api/superadmin/crm/at-risk and `letterstory admin crm at-risk`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"asOf":{"type":"string"},"pastCases":{"type":"number","description":"Confirmed churns with a snapshot before them"},"accounts":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"level":{"type":"string","enum":["high","watch"]},"score":{"type":"number"},"reasons":{"type":"array","items":{"type":"string"}},"ruleIds":{"type":"array","items":{"type":"string"}},"ruleLabels":{"type":"array","items":{"type":"string"},"description":"The rules' labels, in the same order as ruleIds"},"revenueCents":{"type":"number"},"revenueSource":{"type":"string"},"closestCase":{"anyOf":[{"type":"object","properties":{"outcomeId":{"type":"string"},"accountId":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"on":{"type":"string","description":"The outcome's day (notice, else effective)"},"primaryReason":{"type":["string","null"]},"cause":{"anyOf":[{"type":"string","enum":["voluntary","involuntary","unknown"]},{"type":"null"}]},"comparedOn":{"type":"string","description":"The past account's snapshot the features were read from"},"tenureDaysNow":{"type":["number","null"]},"tenureDaysThen":{"type":["number","null"]},"sharedRules":{"type":"array","items":{"type":"string"}},"sharedRuleLabels":{"type":"array","items":{"type":"string"},"description":"The shared rules' labels, in the same order"},"matched":{"type":"number","description":"Features matched, of five"},"features":{"type":"array","items":{"type":"object","properties":{"feature":{"type":"string","enum":["risk_rules","revenue_band","phantoms_live","posting_trend","quiet_days"]},"label":{"type":"string","description":"The feature's name"},"now":{"type":"string"},"then":{"type":"string"},"nowLabel":{"type":"string","description":"`now` as the desk reads it: rule labels, bands with their unit (\"$500–1,500/mo\", \"Under 7 days\")"},"thenLabel":{"type":"string","description":"`then`, read the same way"},"matched":{"type":"boolean"}},"required":["feature","label","now","then","nowLabel","thenLabel","matched"]}}},"required":["outcomeId","accountId","name","kind","on","primaryReason","cause","comparedOn","tenureDaysNow","tenureDaysThen","sharedRules","sharedRuleLabels","matched","features"]},{"type":"null"}],"description":"The most similar past case, matched on plain features; null when none matches 3 of 5. Not a probability"}},"required":["accountId","name","stage","level","score","reasons","ruleIds","ruleLabels","revenueCents","revenueSource","closestCase"]}}},"required":["asOf","pastCases","accounts"]}},{"name":"crm_expansion_signals","description":"Staff only. Customers whose latest account snapshot has a next-product verdict, highest rule weight first (docs/architecture/retention.md, Insights): the product and rule, the live pulse's reasons (empty when the pulse has moved to another rule since), revenue, products they have, past expansions (confirmed outcomes and revenue-bridge movers) whose account had the same rule before expanding, and the closest past expansion. The score is the rule's weight, not a probability. The same list as GET /api/superadmin/crm/expansion-signals and `letterstory admin crm expansion-signals`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"asOf":{"description":"The latest snapshot day","type":["string","null"]},"signals":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"domain":{"description":"The account's domain; null when the desk has none","type":["string","null"]},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"capturedOn":{"type":"string"},"flaggedSince":{"type":"string","description":"The first day of the account's unbroken run of snapshots with this rule"},"product":{"type":"string","enum":["phantomstory","kernels","shredder","ledes","traces","proofs","seers","other"]},"rule":{"type":"string"},"ruleLabel":{"type":"string","description":"The rule's label; the id for a rule only history knows"},"score":{"type":"number","description":"The rule's weight, 0..1; not a probability"},"reasons":{"type":"array","items":{"type":"string"},"description":"The live pulse's reasons; [] when it no longer picks this rule"},"liveRule":{"type":["string","null"]},"liveRuleLabel":{"type":["string","null"]},"revenueCents":{"type":"number"},"revenueSource":{"type":"string"},"products":{"type":"array","items":{"type":"string","enum":["phantomstory","kernels","shredder","ledes","traces","proofs","seers","other"]}},"beforePastExpansions":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","enum":["outcome","mover"]},"outcomeId":{"type":["string","null"]},"on":{"type":"string"},"flaggedOn":{"type":"string"}},"required":["accountId","name","source","outcomeId","on","flaggedOn"]}},"closestCase":{"anyOf":[{"type":"object","properties":{"outcomeId":{"type":"string"},"accountId":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"on":{"type":"string","description":"The outcome's day (notice, else effective)"},"primaryReason":{"type":["string","null"]},"cause":{"anyOf":[{"type":"string","enum":["voluntary","involuntary","unknown"]},{"type":"null"}]},"comparedOn":{"type":"string","description":"The past account's snapshot the features were read from"},"tenureDaysNow":{"type":["number","null"]},"tenureDaysThen":{"type":["number","null"]},"sharedRules":{"type":"array","items":{"type":"string"}},"sharedRuleLabels":{"type":"array","items":{"type":"string"},"description":"The shared rules' labels, in the same order"},"matched":{"type":"number","description":"Features matched, of five"},"features":{"type":"array","items":{"type":"object","properties":{"feature":{"type":"string","enum":["risk_rules","revenue_band","phantoms_live","posting_trend","quiet_days"]},"label":{"type":"string","description":"The feature's name"},"now":{"type":"string"},"then":{"type":"string"},"nowLabel":{"type":"string","description":"`now` as the desk reads it: rule labels, bands with their unit (\"$500–1,500/mo\", \"Under 7 days\")"},"thenLabel":{"type":"string","description":"`then`, read the same way"},"matched":{"type":"boolean"}},"required":["feature","label","now","then","nowLabel","thenLabel","matched"]}}},"required":["outcomeId","accountId","name","kind","on","primaryReason","cause","comparedOn","tenureDaysNow","tenureDaysThen","sharedRules","sharedRuleLabels","matched","features"]},{"type":"null"}],"description":"The most similar past case, matched on plain features; null when none matches 3 of 5. Not a probability"}},"required":["accountId","name","domain","stage","capturedOn","flaggedSince","product","rule","ruleLabel","score","reasons","liveRule","liveRuleLabel","revenueCents","revenueSource","products","beforePastExpansions","closestCase"]}}},"required":["asOf","signals"]}},{"name":"crm_case_file","description":"Staff only. One account's case file (docs/architecture/retention.md, Insights): the 90 days before an outcome (outcome_id) or before today, with the snapshot series one point a week (revenue, posts, clicks, AI citations, Slack waiting, quiet days, risk level and rules), the dated events (stage changes, billing events such as payment_failed and cancel_scheduled, interaction titles, call summaries with a few short quotes; never bodies), the outcome with its reason, cause and note, and the risk rules that fired before it with when they first fired. Args: account_id, outcome_id. The same file as GET /api/superadmin/crm/accounts/[id]/case and `letterstory admin crm case`. Refused unless the key was created by Letter staff. Requires capability account:read.","capability":"account:read","inputSchema":{"type":"object","properties":{"account_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"outcome_id":{"description":"An outcome on this account; the file covers the 90 days before it. Omitted = before today","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["account_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"accountId":{"type":"string"},"name":{"type":"string"},"stage":{"type":"string","enum":["lead","demo_scheduled","demo_done","negotiating","won","onboarding","active","upsell","churned","lost","not_a_fit"]},"from":{"type":"string"},"until":{"type":"string","description":"The outcome's day (notice, else effective), or today"},"outcome":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"accountId":{"type":"string"},"kind":{"type":"string","enum":["churn","contraction","expansion","reactivation","save"]},"status":{"type":"string","enum":["draft","confirmed","discarded"]},"primaryReason":{"type":["string","null"]},"contributingReasons":{"type":"array","items":{"type":"string"}},"cause":{"anyOf":[{"type":"string","enum":["voluntary","involuntary","unknown"]},{"type":"null"}]},"noticeAt":{"type":["string","null"]},"effectiveAt":{"type":"string"},"mrrBeforeCents":{"type":["number","null"]},"mrrAfterCents":{"type":["number","null"]},"note":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"at":{"type":["string","null"]},"quote":{"type":["string","null"]},"ref":{"type":["string","null"]}},"required":["source","at","quote","ref"]}},"saveOffer":{"anyOf":[{"type":"string","enum":["discount","pause","downgrade","fix","other"]},{"type":"null"}]},"saveResult":{"anyOf":[{"type":"string","enum":["kept","lost","pending"]},{"type":"null"}]},"draftedBy":{"description":"auto (a machine) | agent (a staff key); null = a person recorded it","type":["string","null"]},"decidedBy":{"description":"The person who confirmed or discarded it","type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"labelAt":{"type":"string","description":"90 days after noticeAt ?? effectiveAt: when the outcome is labelled"}},"required":["id","accountId","kind","status","primaryReason","contributingReasons","cause","noticeAt","effectiveAt","mrrBeforeCents","mrrAfterCents","note","evidence","saveOffer","saveResult","draftedBy","decidedBy","decidedAt","createdAt","updatedAt","labelAt"]},{"type":"null"}]},"series":{"type":"array","items":{"type":"object","properties":{"on":{"type":"string"},"revenueCents":{"type":"number"},"phantomPosts30d":{"type":"number"},"sitePosts30d":{"type":["number","null"]},"clicks28d":{"type":["number","null"]},"aiCitations28d":{"type":["number","null"]},"slackWaiting":{"type":"number"},"quietDays":{"type":["number","null"]},"riskLevel":{"type":"string","enum":["high","watch","ok"]},"riskRules":{"type":"array","items":{"type":"string"}},"riskRuleLabels":{"type":"array","items":{"type":"string"},"description":"The rules' labels, in the same order as riskRules"}},"required":["on","revenueCents","phantomPosts30d","sitePosts30d","clicks28d","aiCitations28d","slackWaiting","quietDays","riskLevel","riskRules","riskRuleLabels"]},"description":"One snapshot a week, oldest first"},"events":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["stage_change","billing","interaction","transcript"]},"title":{"type":"string"},"detail":{"type":["string","null"]},"quotes":{"type":"array","items":{"type":"string"}},"ref":{"type":"string"}},"required":["at","kind","title","detail","quotes","ref"]},"description":"Titles, summaries and short quotes, oldest first; never bodies"},"signalsBefore":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"label":{"type":"string","description":"The rule's label; the id for a rule only history knows"},"firstFiredOn":{"type":"string"},"lastFiredOn":{"type":"string"},"days":{"type":"number"}},"required":["rule","label","firstFiredOn","lastFiredOn","days"]}},"firstWatchOn":{"type":["string","null"]},"firstHighOn":{"type":["string","null"]}},"required":["accountId","name","stage","from","until","outcome","series","events","signalsBefore","firstWatchOn","firstHighOn"]}},{"name":"get_ai_visibility_bank","description":"The AI Visibility Bank — the evidence behind every AI-answer number in Measure: which questions got the brand NAMED, which got an owned page CITED, and the BREAKTHROUGHS where one answer did both, each told as theme → question → answers and priced by the question's tier (focused / shortlist / category). Plus the capped newest-first ledger of the answers themselves and the per-pillar ground ladder. All time by default; pass period (14d/30d/90d) for a window ending today. source picks whose evidence: phantoms (default), marketing (the client's own site) or all (the portfolio). get_measure_summary's NAMED/CITED counts include the client's own site unless the org is phantoms_only, so pass source=all to match them; the default can read empty on an org with no phantoms. Raw answers (receipts) are capped at answers_per_prompt per question — default 1 here, 0 for counts and questions only; counts are never capped. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"source":{"description":"Whose evidence: phantoms = the phantom network's pages (default), marketing = the client's own site, all = the whole portfolio (every owned citation + every naming, one ledger). The marketing site and the phantoms are never summed except under all.","type":"string","enum":["phantoms","marketing","all"]},"phantom_ids":{"description":"With source=phantoms, narrow to these phantom sites (consolidated into one ledger). Omit for every phantom the org owns.","maxItems":100,"type":"array","items":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"period":{"description":"Narrow the ledger and the stories to a window ending today. Omit for ALL TIME, the default (a breakthrough never expires). The ground ladder is always all-time: its tiers are rungs reached.","type":"string","enum":["14d","30d","90d"]},"answers_per_prompt":{"description":"Cap the raw answers (receipts) kept under each question in the stories, newest first. Counts are unaffected. 0 = counts and questions only. The web app omits it (every answer); the MCP tool defaults to 1 to keep the payload small.","type":"integer","minimum":0,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"bank":{"type":"object","properties":{"citations":{"type":"array","items":{"type":"object","properties":{"responseId":{"type":"string"},"answeredAt":{"type":"string"},"engine":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"question":{"type":"string","description":"The exact question asked."},"breakthrough":{"type":"boolean","description":"This same answer also named the client."},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]}}},"required":["responseId","answeredAt","engine","tier","question","breakthrough","posts"]},"description":"Answers that cited an owned page, newest first (capped at 100)."},"mentions":{"type":"array","items":{"type":"object","properties":{"responseId":{"type":"string"},"answeredAt":{"type":"string"},"engine":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"question":{"type":"string","description":"The exact question asked."},"breakthrough":{"type":"boolean","description":"This same answer also named the client."},"entity":{"type":"string"},"snippet":{"type":["string","null"]}},"required":["responseId","answeredAt","engine","tier","question","breakthrough","entity","snippet"]},"description":"Answers that named the client, newest first (capped at 100; negative namings excluded)."},"totals":{"type":"object","properties":{"citedAnswers":{"type":"number"},"mentionAnswers":{"type":"number"}},"required":["citedAnswers","mentionAnswers"],"description":"Distinct-answer totals across the whole scope — what the capped lists aren't showing."}},"required":["citations","mentions","totals"],"description":"The evidence ledger under the counts."},"stories":{"type":"object","properties":{"named":{"type":"object","properties":{"kind":{"type":"string","enum":["named","cited","breakthrough"]},"reachPrompts":{"type":"number","description":"REACH — distinct earned questions (the headline number)."},"reachThemes":{"type":"number"},"volumeAnswers":{"type":"number","description":"VOLUME — distinct earned answers."},"associationPrompts":{"type":"number","description":"Questions that already said the client's name — shown, never counted as reach."},"associationAnswers":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]},"description":"Owned posts across the story (cited / breakthrough)."},"themes":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"promptCount":{"type":"number"},"answerCount":{"type":"number"},"hasBreakthrough":{"type":"boolean"},"lastSeen":{"type":"string"},"prompts":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"answerCount":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"firstSeen":{"type":"string"},"lastSeen":{"type":"string"},"hasBreakthrough":{"type":"boolean"},"selfNamed":{"type":"boolean"},"events":{"type":"array","items":{"type":"object","properties":{"answeredAt":{"type":"string"},"engine":{"type":"string"},"question":{"type":"string"},"named":{"type":"boolean"},"citedOwned":{"type":"boolean"},"breakthrough":{"type":"boolean"},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]}},"snippet":{"type":["string","null"]}},"required":["answeredAt","engine","question","named","citedOwned","breakthrough","posts","snippet"]},"description":"Raw answers (receipts), newest first — capped by answers_per_prompt."}},"required":["question","tier","answerCount","engines","firstSeen","lastSeen","hasBreakthrough","selfNamed","events"]}}},"required":["key","label","tier","promptCount","answerCount","hasBreakthrough","lastSeen","prompts"]},"description":"Theme → question → answer, best first."}},"required":["kind","reachPrompts","reachThemes","volumeAnswers","associationPrompts","associationAnswers","engines","posts","themes"]},"cited":{"type":"object","properties":{"kind":{"type":"string","enum":["named","cited","breakthrough"]},"reachPrompts":{"type":"number","description":"REACH — distinct earned questions (the headline number)."},"reachThemes":{"type":"number"},"volumeAnswers":{"type":"number","description":"VOLUME — distinct earned answers."},"associationPrompts":{"type":"number","description":"Questions that already said the client's name — shown, never counted as reach."},"associationAnswers":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]},"description":"Owned posts across the story (cited / breakthrough)."},"themes":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"promptCount":{"type":"number"},"answerCount":{"type":"number"},"hasBreakthrough":{"type":"boolean"},"lastSeen":{"type":"string"},"prompts":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"answerCount":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"firstSeen":{"type":"string"},"lastSeen":{"type":"string"},"hasBreakthrough":{"type":"boolean"},"selfNamed":{"type":"boolean"},"events":{"type":"array","items":{"type":"object","properties":{"answeredAt":{"type":"string"},"engine":{"type":"string"},"question":{"type":"string"},"named":{"type":"boolean"},"citedOwned":{"type":"boolean"},"breakthrough":{"type":"boolean"},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]}},"snippet":{"type":["string","null"]}},"required":["answeredAt","engine","question","named","citedOwned","breakthrough","posts","snippet"]},"description":"Raw answers (receipts), newest first — capped by answers_per_prompt."}},"required":["question","tier","answerCount","engines","firstSeen","lastSeen","hasBreakthrough","selfNamed","events"]}}},"required":["key","label","tier","promptCount","answerCount","hasBreakthrough","lastSeen","prompts"]},"description":"Theme → question → answer, best first."}},"required":["kind","reachPrompts","reachThemes","volumeAnswers","associationPrompts","associationAnswers","engines","posts","themes"]},"breakthrough":{"type":"object","properties":{"kind":{"type":"string","enum":["named","cited","breakthrough"]},"reachPrompts":{"type":"number","description":"REACH — distinct earned questions (the headline number)."},"reachThemes":{"type":"number"},"volumeAnswers":{"type":"number","description":"VOLUME — distinct earned answers."},"associationPrompts":{"type":"number","description":"Questions that already said the client's name — shown, never counted as reach."},"associationAnswers":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]},"description":"Owned posts across the story (cited / breakthrough)."},"themes":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"promptCount":{"type":"number"},"answerCount":{"type":"number"},"hasBreakthrough":{"type":"boolean"},"lastSeen":{"type":"string"},"prompts":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"tier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}]},"answerCount":{"type":"number"},"engines":{"type":"array","items":{"type":"string"}},"firstSeen":{"type":"string"},"lastSeen":{"type":"string"},"hasBreakthrough":{"type":"boolean"},"selfNamed":{"type":"boolean"},"events":{"type":"array","items":{"type":"object","properties":{"answeredAt":{"type":"string"},"engine":{"type":"string"},"question":{"type":"string"},"named":{"type":"boolean"},"citedOwned":{"type":"boolean"},"breakthrough":{"type":"boolean"},"posts":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]}},"required":["url","title"]}},"snippet":{"type":["string","null"]}},"required":["answeredAt","engine","question","named","citedOwned","breakthrough","posts","snippet"]},"description":"Raw answers (receipts), newest first — capped by answers_per_prompt."}},"required":["question","tier","answerCount","engines","firstSeen","lastSeen","hasBreakthrough","selfNamed","events"]}}},"required":["key","label","tier","promptCount","answerCount","hasBreakthrough","lastSeen","prompts"]},"description":"Theme → question → answer, best first."}},"required":["kind","reachPrompts","reachThemes","volumeAnswers","associationPrompts","associationAnswers","engines","posts","themes"]}},"required":["named","cited","breakthrough"],"description":"The same evidence told as stories: named (the brand was named), cited (an owned page was cited), breakthrough (both, in one answer)."},"ladder":{"type":"array","items":{"type":"object","properties":{"clusterId":{"type":"string"},"clusterName":{"type":"string"},"mentionTier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}],"description":"Broadest tier at which an answer named the client on this ground."},"citationTier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}],"description":"Broadest tier at which an answer cited this ground's posts."},"breakthroughTier":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}],"description":"Broadest tier at which cite + name landed in one answer."},"graduationTo":{"anyOf":[{"type":"string","enum":["focused","shortlist","category"]},{"type":"null"}],"description":"An active graduation probe is climbing toward this tier."},"nextAction":{"type":"string"}},"required":["clusterId","clusterName","mentionTier","citationTier","breakthroughTier","graduationTo","nextAction"]},"description":"Per-pillar ground ladder (single-org scope; always all-time — rungs reached)."}},"required":["bank","stories","ladder"]}},{"name":"list_probes","description":"List this org's AI-answer probes (a ranking target's carry target_id; a switched-off one says who holds it off and why — held_by / held_reason) with their rotation lifecycle (retiring / queued_for_retirement / proving / resting / earning / protected; null when paused). source='ai' probes rotate; 'user' probes are kept/hand-added and rotation never retires them. Once the org's ranking targets are live (target_focus.active), probes outside the focus carry held_by_target_focus with the reason they aren't being asked. Control one with control_probe, or add one with add_probe. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional: only probes bound to this collection's phantom site","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"active_only":{"description":"Optional: only currently-active (unpaused) probes","type":"boolean"},"probe_kind":{"description":"Optional: filter by probe kind","type":"string","enum":["mention","citation","page"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"target_focus":{"type":"object","properties":{"active":{"type":"boolean","description":"True once the org's ranking targets are live: cycles ask the target questions, citation/page probes on target-bound pillars and hand-added probes, and hold the rest"},"live_since":{"description":"When the target questions first produced answers (ISO)","type":["string","null"]},"company_overlap_until":{"description":"When the company brand-naming set stops running beside the target questions (ISO)","type":["string","null"]}},"required":["active","live_since","company_overlap_until"],"description":"The org's target focus; set geo_config.probe_focus = 'all' to ask the full portfolio"},"probes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string","description":"The probe question"},"probe_kind":{"type":"string","description":"mention | citation | page"},"scope":{"type":"string","description":"phantom | company | site"},"source":{"description":"ai (rotatable) | user (kept/hand-added, protected) | graduation | …","type":["string","null"]},"is_active":{"type":"boolean"},"target_id":{"description":"The ranking target this probe measures, if any","type":["string","null"]},"held_by":{"description":"Who switched it off and keeps it off: user (paused, or its phrasing retired / reworded) | intent_check (judged off its target's intent) | target_edit (written for a target phrase since changed); null = it follows its target. control_probe resume lifts it.","type":["string","null"]},"held_reason":{"description":"Why it is held, in one line","type":["string","null"]},"lifecycle_state":{"description":"retiring | queued_for_retirement | proving | resting | earning | protected (null if paused)","type":["string","null"]},"lifecycle_detail":{"description":"One sentence: what rotation does with it next, and why","type":["string","null"]},"held_by_target_focus":{"description":"Why this active probe is not being asked while the org's ranking targets are live (target focus), or null when it runs","type":["string","null"]}},"required":["id","text","probe_kind","scope","source","is_active","target_id","held_by","held_reason","lifecycle_state","lifecycle_detail","held_by_target_focus"]}}},"required":["target_focus","probes"]}},{"name":"control_probe","description":"Control one probe (the SAME levers as Measure → AI answers → Questions we ask, via lib/phantom-geo/probe-controls.ts): pause/resume; keep = protect an AI probe from rotation without pausing it (promotes it to a kept/user probe); edit = change the question (requires text). Keep is a no-op on an already-protected probe. Requires capability measure:write.","capability":"measure:write","inputSchema":{"type":"object","properties":{"probe_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Probe id from list_probes"},"action":{"type":"string","enum":["pause","resume","keep","edit"],"description":"pause/resume; keep = protect an AI probe from rotation without pausing; edit = change the question"},"text":{"description":"Required for action=edit: the new question","type":"string","minLength":8,"maxLength":500}},"required":["probe_id","action"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"noop":{"description":"true when the control had nothing to change (e.g. keep on a protected probe)","type":"boolean"}},"required":["ok"]}},{"name":"start_first_probes","description":"Ask AI assistants about the org's ranking targets now, before it has a blog or a phantom: adds one hand-added company probe per active company-wide ranking target (up to 5; 'Which companies or products would you recommend for <phrase>?', kept, never rotated out) and starts one real GEO cycle in the background, the same cycle the cron and a blog's onboarding run. Answers land in Measure → AI answers as they come. Needs at least one active ranking target (add_ranking_target), or `fill: true` to add the suggested ones first (suggest_ranking_targets, up to 5, returned as targets_added); asks nothing again while a first run is under way or the org already has answers. Spends the engine's own budget: billing standing, the monthly run cap and Lettertrace being configured still apply, and a run that can't start leaves the probes added for the next cycle. Requires capability measure:write.","capability":"measure:write","inputSchema":{"type":"object","properties":{"fill":{"description":"With no active ranking targets, add the suggested ones first (suggest_ranking_targets, up to 5, source 'inferred'), then ask about them. Default false: no targets is an error.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"status":{"type":"string","enum":["started","already_running","already_ran"],"description":"started: the probes were added and the first cycle is under way; the others asked nothing new"},"probes_added":{"type":"number","description":"How many ranking-target probes were added (0 when nothing was started)"},"targets_added":{"description":"The ranking targets `fill` chose and added (empty when the org already had targets)","type":"array","items":{"type":"string"}},"note":{"type":"string"}},"required":["ok","status","probes_added","note"]}},{"name":"add_probe","description":"Hand-add an AI-answer probe (source='user' — rotation and cutover both leave it alone). scope is 'site'|'company' for the marketing lens, or a phantom_blogs.id to bind a phantom probe. Same path as the 'Add a probe' form in Measure → AI answers → Questions we ask. Requires capability measure:write.","capability":"measure:write","inputSchema":{"type":"object","properties":{"text":{"type":"string","minLength":12,"maxLength":400,"description":"The question to ask the engines"},"probe_kind":{"type":"string","enum":["mention","citation"],"description":"mention = does it name us; citation = does it cite our ground"},"scope":{"type":"string","description":"'site' | 'company' for the marketing lens, or a phantom_blogs.id to bind a phantom probe"},"cluster_id":{"description":"Optional: bind the probe to a topic group / pillar","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["text","probe_kind","scope"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"id":{"type":"string","description":"The new probe id"}},"required":["ok","id"]}},{"name":"get_font_upload_url","description":"Mint a short-lived signed upload URL for a new custom font (custom-fonts bucket). PUT the font bytes to the returned URL with the given content type, then the object is live at `public_url`. Call remove_font on the old font's path once the new one is in place, if replacing. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"format":{"type":"string","enum":["ttf","otf","woff","woff2"],"description":"Font file format for the new custom font"}},"required":["format"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"bucket":{"type":"string","description":"Supabase Storage bucket the object lives in."},"path":{"type":"string","description":"Object key to PUT the bytes to."},"token":{"type":"string","description":"Short-lived signed-upload token for the PUT."},"content_type":{"type":"string","description":"Content-Type header to send with the PUT."},"public_url":{"type":"string","description":"Public URL the object is served from once uploaded."}},"required":["bucket","path","token","content_type","public_url"]}},{"name":"remove_logo","description":"Remove a logo object from the organization-logos bucket by its storage path (as returned by get_logo_upload_url). Refuses a path outside the caller's own org. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"file_path":{"type":"string","minLength":1,"maxLength":500,"description":"Storage path of the logo to remove, as returned by get_logo_upload_url's `path` (format: `{org_id}/logo-...`). Must belong to the caller's own org."}},"required":["file_path"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"path":{"type":"string"}},"required":["removed","path"]}},{"name":"remove_font","description":"Remove a font object from the custom-fonts bucket by its storage path (as returned by get_font_upload_url). Refuses a path outside the caller's own org. Requires capability brand:write.","capability":"brand:write","inputSchema":{"type":"object","properties":{"file_path":{"type":"string","minLength":1,"maxLength":500,"description":"Storage path of the font to remove, as returned by get_font_upload_url's `path` (format: `{org_id}/...`). Must belong to the caller's own org."}},"required":["file_path"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"path":{"type":"string"}},"required":["removed","path"]}},{"name":"list_canvas_types","description":"List the caller's organization's active Canvas types (style templates for Full AI, Semi-Structured, Line Art, Generative Diagrams, Editorial Image, and Table/Venn), optionally filtered to one category. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{"category":{"description":"Filter to canvas types in this category","type":"string","enum":["non_deterministic","semi_deterministic","line_art","generative","editorial_image","web_search","deterministic"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"creator_id":{"type":["string","null"]},"category":{"type":"string","description":"Template family, e.g. non_deterministic, semi_deterministic, deterministic."},"kind":{"type":"string","description":"Concrete kind, e.g. full_ai, semi_structured, line_art, table, venn."},"name":{"type":"string"},"description":{"type":["string","null"]},"layout":{"description":"Free-form template layout JSON (structure varies by kind)."},"status":{"type":"string","description":"Lifecycle status, e.g. active."},"source":{"type":"string","description":"Origin: custom (user-owned), derived (phantom-site-linked), etc."},"bloom_brand_id":{"description":"Provisioned Bloom brand id, when the active provider uses brands.","type":["string","null"]},"bloom_brand_status":{"type":["string","null"]},"derived_phantom_blog_id":{"description":"Linked phantom blog when source is 'derived'.","type":["string","null"]},"pending_style_update":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","creator_id","category","kind","name","description","layout","status","source","bloom_brand_id","bloom_brand_status","derived_phantom_blog_id","pending_style_update","created_at","updated_at"]}}},"required":["items"]}},{"name":"get_canvas_type","description":"Fetch a single Canvas type by id from the caller's organization. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{"canvas_type_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Canvas type UUID (from list_canvas_types)"}},"required":["canvas_type_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"creator_id":{"type":["string","null"]},"category":{"type":"string","description":"Template family, e.g. non_deterministic, semi_deterministic, deterministic."},"kind":{"type":"string","description":"Concrete kind, e.g. full_ai, semi_structured, line_art, table, venn."},"name":{"type":"string"},"description":{"type":["string","null"]},"layout":{"description":"Free-form template layout JSON (structure varies by kind)."},"status":{"type":"string","description":"Lifecycle status, e.g. active."},"source":{"type":"string","description":"Origin: custom (user-owned), derived (phantom-site-linked), etc."},"bloom_brand_id":{"description":"Provisioned Bloom brand id, when the active provider uses brands.","type":["string","null"]},"bloom_brand_status":{"type":["string","null"]},"derived_phantom_blog_id":{"description":"Linked phantom blog when source is 'derived'.","type":["string","null"]},"pending_style_update":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","creator_id","category","kind","name","description","layout","status","source","bloom_brand_id","bloom_brand_status","derived_phantom_blog_id","pending_style_update","created_at","updated_at"]}},{"name":"create_canvas_type","description":"Create a new Canvas type (a named style template) in the caller's organization. category and kind must be paired (non_deterministic↔full_ai, semi_deterministic↔semi_structured, line_art↔line_art, generative↔generative_diagrams, editorial_image↔editorial_image, deterministic↔table|venn). For a full_ai type, best-effort provisions its Bloom brand up front when Bloom is the active image provider (failure doesn't fail creation — it retries lazily on upload/generate). Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"category":{"type":"string","enum":["non_deterministic","semi_deterministic","line_art","generative","editorial_image","web_search","deterministic"],"description":"Canvas category (non_deterministic, semi_deterministic, line_art, generative, editorial_image, deterministic) — paired with kind"},"kind":{"type":"string","enum":["full_ai","semi_structured","line_art","generative_diagrams","editorial_image","web_search","table","venn"],"description":"Canvas kind (full_ai, semi_structured, line_art, generative_diagrams, editorial_image, table, venn) — must match category"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Canvas type name"},"description":{"description":"Optional description","type":"string","maxLength":2000},"layout":{"description":"Optional layout config (shape depends on kind — e.g. a SemiStructuredTemplate or DeterministicStyleTemplate), max 500KB","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["category","kind","name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"creator_id":{"type":["string","null"]},"category":{"type":"string","description":"Template family, e.g. non_deterministic, semi_deterministic, deterministic."},"kind":{"type":"string","description":"Concrete kind, e.g. full_ai, semi_structured, line_art, table, venn."},"name":{"type":"string"},"description":{"type":["string","null"]},"layout":{"description":"Free-form template layout JSON (structure varies by kind)."},"status":{"type":"string","description":"Lifecycle status, e.g. active."},"source":{"type":"string","description":"Origin: custom (user-owned), derived (phantom-site-linked), etc."},"bloom_brand_id":{"description":"Provisioned Bloom brand id, when the active provider uses brands.","type":["string","null"]},"bloom_brand_status":{"type":["string","null"]},"derived_phantom_blog_id":{"description":"Linked phantom blog when source is 'derived'.","type":["string","null"]},"pending_style_update":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","creator_id","category","kind","name","description","layout","status","source","bloom_brand_id","bloom_brand_status","derived_phantom_blog_id","pending_style_update","created_at","updated_at"]}},{"name":"update_canvas_type","description":"Update a Canvas type's name, description, layout, or status; omitted fields are left unchanged. Editing the layout of a 'derived' (phantom-site-linked) canvas decouples it to 'custom' — its site-linked aesthetic becomes user-owned from then on. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"canvas_type_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Canvas type UUID (from list_canvas_types)"},"name":{"description":"New name","type":"string","minLength":1,"maxLength":200},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"description":"New description (null to clear)"},"layout":{"description":"New layout config, max 500KB. Editing the layout of a 'derived' (phantom-site-linked) canvas decouples it to 'custom'.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"status":{"description":"New status","type":"string","enum":["active","archived"]}},"required":["canvas_type_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"creator_id":{"type":["string","null"]},"category":{"type":"string","description":"Template family, e.g. non_deterministic, semi_deterministic, deterministic."},"kind":{"type":"string","description":"Concrete kind, e.g. full_ai, semi_structured, line_art, table, venn."},"name":{"type":"string"},"description":{"type":["string","null"]},"layout":{"description":"Free-form template layout JSON (structure varies by kind)."},"status":{"type":"string","description":"Lifecycle status, e.g. active."},"source":{"type":"string","description":"Origin: custom (user-owned), derived (phantom-site-linked), etc."},"bloom_brand_id":{"description":"Provisioned Bloom brand id, when the active provider uses brands.","type":["string","null"]},"bloom_brand_status":{"type":["string","null"]},"derived_phantom_blog_id":{"description":"Linked phantom blog when source is 'derived'.","type":["string","null"]},"pending_style_update":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","creator_id","category","kind","name","description","layout","status","source","bloom_brand_id","bloom_brand_status","derived_phantom_blog_id","pending_style_update","created_at","updated_at"]}},{"name":"list_canvas_visuals","description":"List produced Canvas visuals (canvas_visuals) in the caller's organization, optionally filtered by canvas_type_id or kind. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{"canvas_type_id":{"description":"Filter to canvas_visuals produced from this canvas type","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"description":"Filter to a specific visual kind","type":"string","enum":["full_ai","semi_structured","table","venn","line_art","generative_diagrams","editorial_image"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"canvas_type_id":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"kind":{"type":"string"},"name":{"type":["string","null"]},"data":{"description":"Free-form canvas visual content JSON."},"settings":{"description":"Free-form canvas visual settings JSON."},"status":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","canvas_type_id","creator_id","kind","name","data","settings","status","created_at","updated_at"]}}},"required":["items"]}},{"name":"get_canvas_visual","description":"Fetch a single produced Canvas visual (canvas_visuals row) by id from the caller's organization. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{"canvas_visual_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Canvas visual UUID (from list_canvas_visuals)"}},"required":["canvas_visual_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"canvas_type_id":{"type":["string","null"]},"creator_id":{"type":["string","null"]},"kind":{"type":"string"},"name":{"type":["string","null"]},"data":{"description":"Free-form canvas visual content JSON."},"settings":{"description":"Free-form canvas visual settings JSON."},"status":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","canvas_type_id","creator_id","kind","name","data","settings","status","created_at","updated_at"]}},{"name":"list_image_models","description":"List the image models currently available to generate with (their provider is configured with credentials) — id, label, provider, and whether it supports reference images. Requires capability canvas:read.","capability":"canvas:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable catalog id used in requests, e.g. \"replicate:...\"."},"label":{"type":"string"},"providerId":{"type":"string","description":"Registered image provider id."},"model":{"description":"Concrete model id; omitted for single-model providers.","type":"string"},"supportsReferenceImages":{"type":"boolean"}},"required":["id","label","providerId","supportsReferenceImages"]}}},"required":["items"]}},{"name":"letter_models_list","description":"Staff only. List Letter's own models (the models hub: Ballpoint) with each one's task, status, inputs and output. Call it before letter_models_generate to see what a model takes. Refused unless the key was created by Letter staff (a build's letterpress-1 has the models through its own sandbox proxy). Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"models":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"task":{"type":"string"},"status":{"type":"string"},"inputs":{"description":"What generate takes, field by field","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"output":{"type":"string"}},"required":["id","name"],"additionalProperties":{}},"description":"Every model the hub serves"}},"required":["models"]}},{"name":"letter_models_generate","description":"Staff only. Call one of Letter's own models: ballpoint-1 writes landing-page heroes (an H1 and subheadline per angle) from `input` { product, audience, tone, angles?, temperature? }; ballpoint-blog-1 writes blog titles from { copy, n?, temperature? }. `input` is that model's request body, as letter_models_list lists it; the answer is the model's own. One angle can fail on its own while the rest succeed: call again for just that one. A model that scaled to zero answers `model_waking`: try again in a minute. Each call is metered to this organization. Refused unless the key was created by Letter staff. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"model":{"type":"string","minLength":1,"maxLength":100,"description":"Model id from letter_models_list, e.g. ballpoint-1"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The model's request body, as letter_models_list lists its `inputs`"}},"required":["model","input"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"model":{"type":"string","description":"The model that answered"}},"required":["model"],"additionalProperties":{},"description":"The model's own answer: ballpoint-1 gives `results` (one { angle, h1, subhead } or { angle, error } per angle); ballpoint-blog-1 gives `titles`."}},{"name":"register_push_device","description":"Register this device for push notifications to the signed-in member (Letterpress for iOS): Letter pushes to it when a build they started opens its pull request, has its preview ready, fails, or needs them. `token` is the APNs device token in hex; `environment` is sandbox for a development build, production for TestFlight and the App Store. Call it on every launch while notifications are allowed (tokens change); a token registered by another member before is theirs no longer. `push_configured` false: this deployment can't push yet, and the device is kept for when it can. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"token":{"type":"string","pattern":"^[0-9a-f]{16,200}$","description":"The APNs device token, hex (as iOS hands it to the app, bytes to hex)"},"environment":{"type":"string","enum":["sandbox","production"],"description":"sandbox for a development (Debug) build of the app, production for TestFlight and the App Store"}},"required":["token","environment"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"device":{"type":"object","properties":{"environment":{"type":"string","enum":["sandbox","production"],"description":"The APNs host the token is pushed through"},"registered_at":{"type":"string","description":"When it was registered (or seen again)"}},"required":["environment","registered_at"]},"push_configured":{"type":"boolean","description":"Whether this deployment can push yet (its APNs key is set); the device is kept either way"}},"required":["device","push_configured"]}},{"name":"unregister_push_device","description":"Stop push notifications to this device for the signed-in member (Letterpress for iOS signing out). Only the caller's own registration is removed: a device another member has signed in on since stays theirs. Requires capability provisioning:write.","capability":"provisioning:write","inputSchema":{"type":"object","properties":{"token":{"type":"string","pattern":"^[0-9a-f]{16,200}$","description":"The APNs device token, hex (as iOS hands it to the app, bytes to hex)"}},"required":["token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"unregistered":{"type":"boolean","description":"True when the caller had the device and it is no longer pushed to; false when nothing of theirs had it"}},"required":["unregistered"]}},{"name":"generate_cover_variants","description":"Generate a post's cover from its headline across several image models in parallel and return one preview URL per model, so you can compare and pick. Full AI cover templates only. Nothing is attached to the post — call select_cover_variant with the winner. Costs one image generation per model. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID whose headline the cover is generated from"},"model_ids":{"description":"Image model catalog ids to generate from in parallel (from list_image_models), max 4. Omitted → every configured model, capped at 4.","minItems":1,"maxItems":4,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"reference_image_urls":{"description":"Public image URLs to condition the look on (e.g. from extract_blog_reference_images). SSRF-validated server-side.","maxItems":6,"type":"array","items":{"type":"string","minLength":1,"maxLength":2048}}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"canvas_type_id":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"catalog_id":{"type":"string"},"label":{"type":"string"},"provider":{"type":"string"},"model":{"type":"string"},"url":{"description":"Preview URL of the stored generated cover (success only).","type":"string"},"error":{"description":"Why this model produced no preview (failure only).","type":"string"}},"required":["catalog_id","label"]}}},"required":["canvas_type_id","variants"]}},{"name":"select_cover_variant","description":"Commit a previewed variant from generate_cover_variants as the post's cover: no regeneration — the exact previewed image is attached to the post (metadata.cover_image + cover_image_url) and recorded as a canvas visual. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID the variant was generated for"},"image_url":{"type":"string","minLength":1,"maxLength":2048,"description":"URL of the previewed variant to commit (from generate_cover_variants)"},"provider":{"type":"string","minLength":1,"maxLength":100,"description":"Provider that produced the variant (from the same result)"},"model":{"description":"Model that produced the variant, when the provider has one","type":"string","maxLength":200}},"required":["article_id","image_url","provider"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cover_image":{"type":"object","properties":{"url":{"type":"string"},"alt":{"type":"string"},"kind":{"type":"string","const":"full_ai"},"canvas_visual_id":{"type":"string"},"canvas_type_id":{"type":"string"}},"required":["url","alt","kind","canvas_visual_id","canvas_type_id"]}},"required":["cover_image"]}},{"name":"generate_article_cover","description":"Mint one post's cover headlessly with the collection's cover template and the server's default image provider, and attach it to the post — the CLI equivalent of the app's Generate cover button. Use generate_cover_variants instead to choose the image model yourself. Returns { generated: false, reason } when the post already has a cover (pass regenerate) or the collection isn't set up for covers. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Article UUID to mint a cover for"},"regenerate":{"description":"Replace an existing cover (default false — a post that already has one is left alone)","type":"boolean"},"stock_only":{"description":"Only set a cover when a stock photo clears the ranker's bar; never spend on an image model","type":"boolean"}},"required":["article_id"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"generated":{"type":"boolean","const":true},"url":{"type":"string"},"provider":{"type":"string"}},"required":["generated","url","provider"]},{"type":"object","properties":{"generated":{"type":"boolean","const":false},"reason":{"type":"string","description":"Why nothing was generated (already has a cover, not set up, etc.)."}},"required":["generated","reason"]}],"type":"object"}},{"name":"arrange_composition","description":"AI-arrange a Semi-Structured Canvas composition: pick from the type's stickers and place them within the template's AI region per a prompt. Text-model only (no image spend) — a layout-planning step independent of any image model. Returns the produced elements; the caller places them into a canvas_visuals draft in the app (there is no tool that writes canvas_visuals yet). Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"canvas_type_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Semi-structured canvas type UUID (from list_canvas_types) whose template and stickers to arrange"},"prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Instructions for how to arrange the composition"}},"required":["canvas_type_id","prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"elements":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string","enum":["sticker","ai","image"],"description":"Where the element came from."},"sticker_id":{"description":"When source is 'sticker': the canvas_stickers.id it was placed from.","type":"string"},"image_url":{"description":"Public URL of the placed sticker image, if resolvable.","type":"string"},"x":{"type":"number"},"y":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"},"rotation":{"type":"number"},"opacity":{"type":"number"},"z_index":{"type":"number"}},"required":["id","source","x","y","width","height","z_index"]}}},"required":["elements"]}},{"name":"classify_sticker","description":"(Re-)run vision classification for a single Canvas sticker — describes what it depicts, tags, style, dominant colors, and suggested usage, so arrange_composition knows when to use it. Low-risk, cheap vision call. On failure, marks the sticker's classification_status 'failed' and returns an error. Requires capability canvas:write.","capability":"canvas:write","inputSchema":{"type":"object","properties":{"sticker_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Canvas sticker UUID to (re-)classify"}},"required":["sticker_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sticker":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"canvas_type_id":{"type":"string"},"name":{"type":["string","null"]},"storage_path":{"type":"string"},"width":{"type":["number","null"]},"height":{"type":["number","null"]},"bloom_image_id":{"type":["string","null"]},"classification":{"anyOf":[{"type":"object","properties":{"description":{"type":"string","description":"One sentence describing what the sticker depicts."},"tags":{"type":"array","items":{"type":"string"},"description":"Lowercase keywords (subjects, objects, themes)."},"style":{"description":"Visual style, e.g. \"flat vector illustration\".","type":"string"},"dominant_colors":{"description":"Dominant colors (names or hex).","type":"array","items":{"type":"string"}},"suggested_usage":{"type":"string","description":"When/where to use this sticker in a composition."},"transparent_background":{"type":"boolean"}},"required":["description","tags","suggested_usage"]},{"type":"null"}]},"classification_status":{"type":"string","description":"pending, ready, or failed."},"classified_at":{"type":["string","null"]},"uploaded_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"url":{"type":"string","description":"Public URL of the sticker image."}},"required":["id","org_id","canvas_type_id","name","storage_path","width","height","bloom_image_id","classification","classification_status","classified_at","uploaded_by","created_at","updated_at","url"],"description":"The updated canvas_stickers row plus its public url."}},"required":["sticker"]}},{"name":"list_visuals","description":"List table/venn visuals in the caller's organization, newest first, optionally filtered to those attached to one article. Requires capability visual:read.","capability":"visual:read","inputSchema":{"type":"object","properties":{"article_id":{"description":"Optional article UUID to scope to visuals attached to that article","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"article_id":{"description":"Article this visual is attached to, if any.","type":["string","null"]},"creator_id":{"type":["string","null"]},"type":{"type":"string","description":"Visual type: table or venn."},"title":{"type":["string","null"]},"data":{"description":"The table/venn structure JSON."},"settings":{"anyOf":[{},{"type":"null"}],"description":"Per-visual style settings JSON."},"status":{"type":"string","description":"draft or published."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","article_id","creator_id","type","title","data","settings","status","created_at","updated_at"]}}},"required":["items"]}},{"name":"get_visual","description":"Fetch a single table/venn visual by id from the caller's organization. Requires capability visual:read.","capability":"visual:read","inputSchema":{"type":"object","properties":{"visual_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Visual UUID (from list_visuals)"}},"required":["visual_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"article_id":{"description":"Article this visual is attached to, if any.","type":["string","null"]},"creator_id":{"type":["string","null"]},"type":{"type":"string","description":"Visual type: table or venn."},"title":{"type":["string","null"]},"data":{"description":"The table/venn structure JSON."},"settings":{"anyOf":[{},{"type":"null"}],"description":"Per-visual style settings JSON."},"status":{"type":"string","description":"draft or published."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","article_id","creator_id","type","title","data","settings","status","created_at","updated_at"]}},{"name":"create_visual","description":"Create a table or venn visual in the caller's organization, optionally attached to an article (which must belong to the same org). Requires capability visual:write.","capability":"visual:write","inputSchema":{"type":"object","properties":{"article_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"type":{"default":"table","type":"string","enum":["table","venn"]},"title":{"type":"string","maxLength":500},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["data"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"article_id":{"description":"Article this visual is attached to, if any.","type":["string","null"]},"creator_id":{"type":["string","null"]},"type":{"type":"string","description":"Visual type: table or venn."},"title":{"type":["string","null"]},"data":{"description":"The table/venn structure JSON."},"settings":{"anyOf":[{},{"type":"null"}],"description":"Per-visual style settings JSON."},"status":{"type":"string","description":"draft or published."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","article_id","creator_id","type","title","data","settings","status","created_at","updated_at"]}},{"name":"update_visual","description":"Update a table/venn visual's title, data, settings, or status; omitted fields are left unchanged. Requires capability visual:write.","capability":"visual:write","inputSchema":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"settings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"status":{"type":"string","enum":["draft","published"]},"visual_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Visual UUID (from list_visuals)"}},"required":["visual_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"article_id":{"description":"Article this visual is attached to, if any.","type":["string","null"]},"creator_id":{"type":["string","null"]},"type":{"type":"string","description":"Visual type: table or venn."},"title":{"type":["string","null"]},"data":{"description":"The table/venn structure JSON."},"settings":{"anyOf":[{},{"type":"null"}],"description":"Per-visual style settings JSON."},"status":{"type":"string","description":"draft or published."},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","org_id","article_id","creator_id","type","title","data","settings","status","created_at","updated_at"]}},{"name":"generate_table_visual","description":"Generate a table structure from article content with Claude, scoped to and usage-gated against the caller's organization. Returns the structure only — call create_visual to persist it. Requires capability visual:write.","capability":"visual:write","inputSchema":{"type":"object","properties":{"article_content":{"type":"string","minLength":1,"description":"Source article content (plain text or HTML) to derive a table structure from"},"prompt":{"description":"Optional additional instructions for the table's structure/content","type":"string","maxLength":2000}},"required":["article_content"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"table_structure":{"type":"object","properties":{"title":{"type":"string"},"corner_cell":{"type":"string"},"column_headers":{"type":"array","items":{"type":"string"}},"rows":{"type":"array","items":{"type":"object","properties":{"header":{"type":"string"},"cells":{"type":"array","items":{"type":"string"}}},"required":["header","cells"]}},"insert_at":{"description":"Anchor text/heading to insert near.","type":"string"},"insert_position":{"type":"string","enum":["section_start","section_end"]}},"required":["column_headers","rows"],"description":"The generated table; persist via create_visual."}},"required":["table_structure"]}},{"name":"generate_venn_visual","description":"Generate a Venn diagram structure from article content with Claude, scoped to and usage-gated against the caller's organization. Returns the structure only — call create_visual to persist it. Requires capability visual:write.","capability":"visual:write","inputSchema":{"type":"object","properties":{"article_content":{"type":"string","minLength":1,"description":"Source article content (plain text or HTML) to derive a Venn diagram structure from"},"prompt":{"description":"Optional additional instructions for the Venn diagram's structure/content","type":"string","maxLength":2000}},"required":["article_content"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"venn_structure":{"type":"object","properties":{"title":{"type":"string"},"circles":{"type":"object","properties":{"a":{"type":"object","properties":{"label":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"description":{"type":"string"}},"required":["label","items"]},"b":{"type":"object","properties":{"label":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"description":{"type":"string"}},"required":["label","items"]},"c":{"description":"Optional third circle for three-circle diagrams.","type":"object","properties":{"label":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"description":{"type":"string"}},"required":["label","items"]}},"required":["a","b"]},"intersections":{"type":"array","items":{"type":"object","properties":{"sets":{"type":"array","items":{"type":"string"},"description":"Circle keys in the overlap, e.g. [\"A\",\"B\"]."},"items":{"type":"array","items":{"type":"string"}},"label":{"type":"string"}},"required":["sets","items"]}},"diagram_type":{"type":"string","enum":["two_circle","three_circle"]},"insert_at":{"type":"string"},"insert_position":{"type":"string","enum":["section_start","section_end"]}},"required":["circles","intersections","diagram_type"],"description":"The generated Venn diagram; persist via create_visual."}},"required":["venn_structure"]}},{"name":"list_held_drafts","description":"List drafts parked in the holding pen: composed seer drafts whose extracted substance couldn't be confirmed against the source (kind: compose) and articles parked by the accuracy hold queue (kind: accuracy). Neither publishes until released via resolve_held_draft. Requires capability seer:read.","capability":"seer:read","inputSchema":{"type":"object","properties":{"collection_id":{"description":"Optional collection UUID to scope the holding pen to","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"drafts":{"type":"array","items":{"type":"object","properties":{"articleId":{"type":"string"},"title":{"type":"string"},"collectionId":{"type":["string","null"]},"createdAt":{"type":"string"},"reason":{"type":"string","description":"Why the draft is held."},"dropped":{"type":"number","description":"Count of dropped/violating claims."},"unsure":{"type":"number","description":"Count of unverifiable claims."},"kind":{"type":"string","enum":["compose","accuracy"],"description":"compose = groundedness pen; accuracy = hold queue."}},"required":["articleId","title","collectionId","createdAt","reason","dropped","unsure","kind"]}}},"required":["drafts"]}},{"name":"resolve_held_draft","description":"Resolve a held draft: release clears the hold AND publishes it; discard keeps it unpublished and drops it off the pen. Covers both hold kinds (compose groundedness and accuracy). Returns `published` — false when the hold cleared but the publish was blocked by something else (usually a pending reviewer), with `reason`. Requires capability seer:write.","capability":"seer:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Held draft's article UUID (from list_held_drafts)"},"action":{"type":"string","enum":["release","discard"],"description":"release = clear the hold so it can publish; discard = keep it unpublished and off the pen"}},"required":["article_id","action"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"action":{"type":"string","enum":["release","discard"]},"published":{"type":"boolean","description":"False when the hold cleared but publish was blocked (see reason)."},"reason":{"description":"Why the publish didn't follow a release (usually a pending reviewer).","type":"string"}},"required":["ok","action","published"]}},{"name":"get_seer_connections","description":"GitHub and Notion connection status for the caller's org. GitHub connects via set_github_connection (a PAT); Notion via set_notion_connection (an internal-integration secret). Both are headless: no browser needed. The app's browser OAuth flow for Notion is an alternative, not a requirement. Requires capability seer:read.","capability":"seer:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"github":{"type":"object","properties":{"connected":{"type":"boolean"},"account_login":{"type":["string","null"]},"connected_at":{"type":["string","null"]},"configured":{"type":"boolean","description":"Whether this deployment can store GitHub tokens (encryption key present)."}},"required":["connected","account_login","connected_at","configured"]},"notion":{"type":"object","properties":{"connected":{"type":"boolean"},"workspace_name":{"type":["string","null"]},"workspace_id":{"type":["string","null"]},"connected_at":{"type":["string","null"]},"configured":{"type":"boolean","description":"Whether this deployment can store a Notion token (encryption key present)."}},"required":["connected","workspace_name","workspace_id","connected_at","configured"]}},"required":["github","notion"]}},{"name":"set_github_connection","description":"Validate a GitHub personal access token against GitHub and store it encrypted for the ORG (org-wide: every GitHub seer AND every GitHub publish target uses this one token). Public-repo seers work without one; this unlocks private repos and a higher GitHub rate limit for reading. To publish (set_github_publish_target + publish_to_connector), the token also needs Contents (read/write) and Pull requests (read/write) scope on the target repo — read-only tokens can connect but publishing will fail. Requires capability seer:write.","capability":"seer:write","inputSchema":{"type":"object","properties":{"access_token":{"type":"string","minLength":1,"maxLength":500,"description":"A GitHub personal access token with repo read access; validated against GitHub, stored encrypted"}},"required":["access_token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"account_login":{"description":"The validated GitHub account login.","type":["string","null"]}},"required":["success","account_login"]}},{"name":"find_content_folders","description":"Where the repo that builds the site keeps its blog posts, found and checked rather than guessed: reads the repo's file list once and returns the folders of Markdown posts, best first, each with how many of its files match a live blog page of the site by slug ('38 posts, matching 36 of your 41 live posts') and the content_path_template to pass to set_github_publish_target. The match is the check that it is the folder the site reads: no match, or a handful of files, means it is not. Needs a connected repo that builds the site (list_repo_sites) and GitHub access to it; says so when there is none. Read-only. Requires capability connector:read.","capability":"connector:read","inputSchema":{"type":"object","properties":{"domain":{"description":"The site whose repo to read. Defaults to the organization's own.","type":"string","minLength":1,"maxLength":255}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"repo":{"type":"string","description":"The repo that builds the site, owner/name."},"branch":{"type":"string"},"folders":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The folder, from the repo's root."},"template":{"type":"string","description":"The content_path_template for set_github_publish_target: folder, {slug}, the files' extension."},"files":{"type":"number","description":"Markdown files directly in the folder."},"matched":{"type":"number","description":"How many match a live blog page of the site by slug."},"live":{"type":"number","description":"How many live blog pages the site has."},"sample":{"type":"array","items":{"type":"string"},"description":"A few file names."},"summary":{"type":"string","description":"In words, e.g. '38 posts, matching 36 of your 41 live posts'."}},"required":["path","template","files","matched","live","sample","summary"]},"description":"Candidate folders of Markdown posts, best match first; empty when none was found."},"truncated":{"type":"boolean","description":"GitHub cut the repo's file list short, so a folder may be missing."},"note":{"type":"string"}},"required":["repo","branch","folders","truncated","note"]}},{"name":"apply_starter_automations","description":"Start what a letterbase writes, in one call: applies the starter automations suggest_starter_automations chooses from the client's own evidence (strengths to double down on, near wins, a gap where it is relevant and strong, a format the site already keeps, Best for the rest), each with its reason in the client's numbers, then turns the letterbase's writing on with every draft waiting for review (nothing publishes until a person approves). What the weekly cap or a duplicate refuses is reported in `skipped`, never fatal. Nothing is written by suggest_starter_automations; this is the accept. Needs the letterbase's blog read in (onboard_collection, which does this itself when called with start_writing). Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The letterbase to apply the starter automations to (list_collections)."},"ground":{"description":"The evidence to choose from, as suggest_starter_automations takes it: answers (a GEO plan's), search (an SEO plan's). Unset: everything.","type":"string","enum":["answers","search"]},"publish":{"description":"true: writing turns on publishing each post on its own (a Plan or build start, so it is never a second step); unset or false: every draft waits for a person's review. Either way it is changed later in the letterbase's Automations.","type":"boolean"}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"collection_id":{"type":"string"},"basis":{"type":"string","enum":["evidence","positioning"],"description":"evidence: chosen from the client's own results; positioning: a guess."},"applied":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string"},"pace_per_week":{"type":"number"},"ground":{"description":"strength, near_win or gap, when it came from evidence.","type":["string","null"]},"reason":{"type":"string","description":"Why, with the client's own numbers."}},"required":["name","kind","pace_per_week","ground","reason"]}},"skipped":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"why":{"type":"string"}},"required":["name","why"]},"description":"What the weekly cap or a duplicate refused."},"removed":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string"}},"required":["name","kind"]},"description":"A Best that ran before and the set left out: removed, so the week is the set's alone."},"weekly_total":{"type":"number"},"writing":{"type":"string","enum":["on","unchanged","not_started"],"description":"on: writing is on, every draft waiting for review."}},"required":["collection_id","basis","applied","skipped","removed","weekly_total","writing"]}},{"name":"list_answer_findings","description":"What AI assistants said to the org's probes, per question, in words and numbers: where the company is named (a strength to double down on, or a near win) and where a rival is named and it isn't (a gap), strengths first. Only what is relevant to the product is listed (a ranking target the company chose, or what its positioning speaks to): a rival named on an unrelated question is not a gap. Read from the completed runs, the same evidence the starter automations are chosen from, so what this shows is what suggest_starter_automations and apply_starter_automations act on. Empty until a first cycle has answered (start_first_probes). Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"topic":{"type":"string","description":"What it is about, in words."},"answers":{"type":"number"},"named":{"type":"number","description":"In how many answers the company was named."},"rivals":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"answers":{"type":"number"}},"required":["name","answers"]}},"relevance":{"type":"number","description":"0..1: how much the product speaks to it (1 for a ranking target the company chose). Only relevant questions are listed."},"kind":{"anyOf":[{"type":"string","enum":["strength","near_win","building","gap"]},{"type":"null"}],"description":"null: too few answers to say yet."},"line":{"type":"string","description":"One line with the numbers."}},"required":["question","topic","answers","named","rivals","relevance","kind","line"]}},"answers_total":{"type":"number"}},"required":["findings","answers_total"]}},{"name":"list_search_findings","description":"What Google Search Console says about the org's own queries over the last 28 days, in words and numbers: queries it ranks at the top for and is clicked on (a strength to double down on) and queries it ranks just off the top for, positions 4-20 (a near win, the cheapest ground to take), near wins first. Only what is relevant to the product is listed, judged by meaning against its positioning, not by shared words; the company's own name is left out. Read from the stored sync, so it is quick, and it works before a blog exists; the starter automations are chosen from the same queries. Empty until Search Console is connected and has synced. Requires capability measure:read.","capability":"measure:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"query":{"type":"string","description":"The query as people typed it."},"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":"number","description":"Impression-weighted average position over the last 28 days."},"relevance":{"type":"number","description":"0..1: how relevant the query is to the product, by meaning. Only relevant queries are listed."},"kind":{"type":"string","enum":["strength","near_win"],"description":"strength: ranks at the top and is clicked; near_win: ranks just off the top (4-20)."},"line":{"type":"string","description":"One line with the numbers."}},"required":["query","impressions","clicks","position","relevance","kind","line"]}},"impressions_total":{"type":"number"}},"required":["findings","impressions_total"]}},{"name":"set_github_publish_target","description":"Configure a repo publish_to_connector can open PRs against — for a customer whose repo IS their CMS (content-as-files: they pull from it at build time, or you PR into it directly, rather than storing content in Letter). Requires GitHub to reach the repo first: the Letter GitHub App installed on the repo's account (preferred), or connect_cms with provider github / set_github_connection with a token that has Contents + Pull requests write scope on this repo. The target is checked against GitHub before it is saved: a repo the token can't reach or push to, a missing base_branch, or a content_path_template without {slug} is refused; softer findings come back as warnings. frontmatter is the repo's frontmatter contract — the key each field is written under (e.g. summary → description, cover_image → coverImage, null leaves one out), date_format ('date' writes YYYY-MM-DD) and defaults every post carries (e.g. category) — and import_from_cms reads the same keys back; omit it for title, slug, date, author, summary, tags, cover_image. A target is GitHub's field mapping: bind_collection_cms wires it to a collection (the Planner then opens a PR per post it publishes) and import_from_cms reads the existing posts back out of the same path. Re-running with the same `name` updates that target in place rather than creating a duplicate. Also configurable in the app under Settings → Integrations → GitHub (https://app.letterstory.com/settings?tab=integrations). Requires capability connector:publish.","capability":"connector:publish","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"repo_owner":{"type":"string","minLength":1,"maxLength":200},"repo_name":{"type":"string","minLength":1,"maxLength":200},"base_branch":{"description":"Defaults to 'main' when omitted.","type":"string","minLength":1,"maxLength":200},"content_path_template":{"description":"Defaults to 'content/posts/{slug}.md' when omitted.","type":"string","minLength":1,"maxLength":500},"frontmatter":{"description":"The repo's frontmatter contract: keys to write fields under (e.g. summary → description), date_format, and defaults (e.g. category). Omit for title, slug, date, author, summary, tags, cover_image.","type":"object","properties":{"keys":{"description":"The key each field is written under; null leaves the field out.","type":"object","properties":{"title":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"slug":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"date":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"author":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"summary":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"tags":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"cover_image":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]}},"additionalProperties":false},"date_format":{"description":"'date' writes YYYY-MM-DD; 'iso' (default) the timestamp.","type":"string","enum":["iso","date"]},"defaults":{"description":"Fields written on every post, e.g. { category: 'Engineering' }.","type":"object","propertyNames":{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},"additionalProperties":{"type":"string","maxLength":500}}},"additionalProperties":false}},"required":["name","repo_owner","repo_name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"target_id":{"type":"string"},"connector":{"type":"string","const":"github"},"name":{"type":"string"},"repo_owner":{"type":"string"},"repo_name":{"type":"string"},"base_branch":{"type":"string"},"content_path_template":{"type":"string"},"frontmatter":{"type":"object","properties":{"keys":{"description":"The key each field is written under; null leaves the field out.","type":"object","properties":{"title":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"slug":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"date":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"author":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"summary":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"tags":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]},"cover_image":{"anyOf":[{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},{"type":"null"}]}}},"date_format":{"description":"'date' writes YYYY-MM-DD; 'iso' (default) the timestamp.","type":"string","enum":["iso","date"]},"defaults":{"description":"Fields written on every post, e.g. { category: 'Engineering' }.","type":"object","propertyNames":{"type":"string","pattern":"^[A-Za-z_][A-Za-z0-9_-]{0,63}$"},"additionalProperties":{"type":"string","maxLength":500}}},"description":"The target's frontmatter contract; {} writes the default set."},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-blocking findings from checking the target against GitHub (e.g. the post directory is empty on the base branch). Blocking problems — repo unreachable, no push access, missing branch, no {slug} — refuse the save instead."}},"required":["target_id","connector","name","repo_owner","repo_name","base_branch","content_path_template","frontmatter","warnings"]}},{"name":"delete_github_publish_target","description":"Remove a GitHub publish target (from list_publish_targets or set_github_publish_target). Idempotent. Requires capability connector:publish.","capability":"connector:publish","inputSchema":{"type":"object","properties":{"target_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Publish target UUID (from list_publish_targets)"}},"required":["target_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"target_id":{"type":"string"}},"required":["deleted","target_id"]}},{"name":"open_integration_pr","description":"Open (or update) a PR in the org's own repo pre-loaded with starter code for the custom-front-end integration (a thin API client plus a webhook-triggered revalidate route for a Next.js App Router app) — instead of the customer hand-copying it from /docs/onboard/custom-front-end. Writes as the Letter GitHub App when it is installed on the repo's account (see list_github_app_repos), otherwise needs set_github_connection first with a token that has Contents + Pull requests write scope on this repo. Nothing goes live until the customer reviews and merges; Letter only opens the PR. Re-running with the same repo adds more commits to the same PR rather than opening a duplicate. Requires capability connector:publish.","capability":"connector:publish","inputSchema":{"type":"object","properties":{"repo_owner":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9][A-Za-z0-9-]{0,38}$"},"repo_name":{"type":"string","minLength":1,"maxLength":200},"base_branch":{"description":"Defaults to 'main' when omitted.","type":"string","minLength":1,"maxLength":200}},"required":["repo_owner","repo_name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"pr_url":{"type":"string"},"pr_number":{"type":"number"},"branch":{"type":"string"},"files":{"type":"array","items":{"type":"string"},"description":"Repo-relative paths written onto the PR's branch."}},"required":["pr_url","pr_number","branch","files"]}},{"name":"connect_github","description":"Connect GitHub: whether the Letter GitHub App is installed for this organization (and on which accounts), and the link a PERSON must open to connect an account and pick the repo that builds the site. GitHub's consent is given in the browser by a member; this tool never connects anything itself. Then list_github_app_repos shows the repos. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","description":"The Letter GitHub App is installed for this organization on at least one account"},"accounts":{"type":"array","items":{"type":"string"},"description":"The GitHub accounts it is installed on"},"url":{"type":"string","description":"A page a PERSON opens (signed in as a member) to connect a GitHub account and pick repos; a key can't"},"message":{"type":"string"}},"required":["connected","accounts","url","message"]}},{"name":"connect_gitlab","description":"Connect GitLab (gitlab.com): whether this organization's GitLab is connected, as whom, and the page a PERSON opens to connect it — by signing in to GitLab (work shows as them), or by pasting a group or project access token named Letter (work shows as Letter, as with the GitHub App). This tool never connects anything itself. Then list_gitlab_repos shows the projects, and start_repo_change takes provider: gitlab. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","description":"The organization's GitLab is connected"},"kind":{"anyOf":[{"type":"string","enum":["oauth","access_token"]},{"type":"null"}],"description":"oauth: a member signed in, and work shows as them. access_token: a bot token, and work shows as Letter."},"account":{"description":"The GitLab user Letter acts as","type":["string","null"]},"tokenName":{"description":"access_token: the name GitLab shows for the bot","type":["string","null"]},"expiresAt":{"description":"access_token: when it would stop working; Letter rotates it a week before","type":["string","null"]},"url":{"type":"string","description":"Settings → Integrations, where a PERSON signs in to GitLab or pastes a bot token; a key can't"},"signInUrl":{"type":"string","description":"Signs a PERSON in to GitLab straight away (work then shows as them)"},"message":{"type":"string"}},"required":["connected","kind","account","tokenName","expiresAt","url","signInUrl","message"]}},{"name":"list_gitlab_repos","description":"List the GitLab projects Letter can build in for this organization: the ones its GitLab connection can push to (Developer or above), most recently active first, 100 a page. Each path is group/subgroup/project; start_repo_change takes it with provider gitlab, repo_owner group/subgroup and repo_name project. Empty, or an error, when GitLab isn't connected (connect_gitlab). Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"search":{"description":"Only projects whose name or path matches.","type":"string","minLength":1,"maxLength":100},"page":{"description":"Page of 100, most recently active first.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"path":{"type":"string","description":"group/subgroup/project: pass as repo_owner (all but the last part) and repo_name"},"defaultBranch":{"type":["string","null"]},"webUrl":{"type":"string"}},"required":["id","path","defaultBranch","webUrl"]}},"message":{"type":"string"}},"required":["projects","message"]}},{"name":"disconnect_gitlab","description":"Disconnect the organization's GitLab: Letter stops building in its projects. A signed-in connection's access is revoked; a pasted bot token stays the customer's to revoke in GitLab. Merge requests already opened stay open. Requires capability account:write.","capability":"account:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean"},"message":{"type":"string"}},"required":["disconnected","message"]}},{"name":"list_github_app_repos","description":"List every repo the Letter GitHub App can reach for this organization — the repos start_repo_change can open pull requests into. The customer installs the app and picks the repos on GitHub (from the Repos tool, https://app.letterstory.com/repos), and only the repos they pick are listed or changeable; an empty list means it isn't installed yet. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"defaultBranch":{"type":"string"},"private":{"type":"boolean"},"installationId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["owner","name","fullName","defaultBranch","private","installationId"]}},"message":{"type":"string"}},"required":["repos","message"]}},{"name":"list_repo_branches","description":"List the branches of one repo the Letter GitHub App reaches, default branch first — the branches start_repo_change can use as base_branch. The repo must be one list_github_app_repos returns. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"repo_owner":{"type":"string","minLength":1,"pattern":"^[A-Za-z0-9][A-Za-z0-9-]{0,38}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["repo_owner","repo_name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"defaultBranch":{"type":"string"},"branches":{"type":"array","items":{"type":"string"},"description":"Default branch first, the rest by name."},"truncated":{"type":"boolean","description":"True when the repo has more than 1,000 branches and the list stops there."}},"required":["defaultBranch","branches","truncated"]}},{"name":"list_open_pull_requests","description":"Every OPEN pull request in the organization's repos (the ones the Letter GitHub App reaches), or in one repo (repo_owner + repo_name) — whoever opened it, not only Letter. Longest-idle first, each with daysSinceUpdate, draft, author and, when a Letter run opened it, that run's id and status. For \"what's outstanding?\" and \"which can we close?\": long-idle PRs, drafts, and PRs whose Letter run was cancelled or superseded are the usual candidates. Read-only; closing happens on GitHub. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"repo_owner":{"description":"With repo_name: just this repo. Leave both out for every repo the GitHub App reaches.","type":"string","minLength":1,"pattern":"^[A-Za-z0-9][A-Za-z0-9-]{0,38}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"pullRequests":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"number":{"type":"number"},"title":{"type":"string"},"url":{"type":"string"},"author":{"type":["string","null"]},"draft":{"type":"boolean"},"baseBranch":{"type":"string"},"headBranch":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"daysSinceUpdate":{"type":"number"},"letterRun":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"summary":{"type":"string","description":"What the run was asked: the first line of its instructions"},"fixes":{"type":"array","items":{"type":"string"},"description":"Observation keys it fixes (facts.og_missing, audit.broken-internal-link); empty for a free-text change"}},"required":["id","status","summary","fixes"]},{"type":"null"}],"description":"The Letter run that opened it, how that run ended and what it fixes; null for anyone else's PR."}},"required":["repo","number","title","url","author","draft","baseBranch","headBranch","createdAt","updatedAt","daysSinceUpdate","letterRun"]},"description":"Open PRs, longest-idle first."},"repos":{"type":"number","description":"How many repos were read."},"truncated":{"type":"boolean","description":"True when the org has more repos than one read covers (25); name a repo to read it."},"message":{"type":"string"}},"required":["pullRequests","repos","truncated","message"]}},{"name":"start_repo_change","description":"Make a change to one of the organization's own repos (usually its marketing site) from plain-language instructions, and open a pull request for it. Claude Code checks the repo out in a throwaway sandbox, makes the change (it can install and build to check its work), and the Letter GitHub App opens a PR with Claude's summary and the file list. Attach any files the change needs (images, documents, page drafts) in `files`: by https url, inline base64, or an upload from create_repo_change_upload; the sandbox gets them by name and Claude Code places them. Nothing goes live until it merges: the customer reviews and merges, unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Runs in the background for several minutes and spends model credit (capped per run) — poll get_repo_change. Check list_open_pull_requests first: if an open Letter PR already does this, follow up on it (revise_repo_change) instead of starting another; the result lists the other changes open on the repo as `alsoOpen`. The repo must be reachable by the Letter GitHub App (list_github_app_repos). With preview: true it stops before the PR instead: the changed site goes live at run.preview.url (phase `review`), and publish_repo_change (Make PR) opens the PR or cancel_repo_change discards it. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$","description":"Who owns the repo: on GitHub, the account the Letter GitHub App is installed on; on GitLab, the group path (group/subgroup) of a project the organization's GitLab connection can push to."},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"base_branch":{"description":"Branch to change and open the PR against. Defaults to the organization's default branch where the repo has it, else the repo's default branch.","type":"string","minLength":1,"maxLength":200},"instructions":{"type":"string","minLength":10,"maxLength":20000,"description":"What to change, in plain words — e.g. 'Rewrite the homepage hero to lead with the 14-day free trial and add an llms.txt at the site root.' The first line becomes the PR title. Refer to attached files by name (e.g. 'add hero.png as the new hero image')."},"files":{"description":"Files the change needs — images, documents, a page draft. The sandbox gets each at /data/run/files/<name>, outside the repo; the agent copies them where the instructions say, except a `reference` file, which it only reads.","maxItems":20,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"action":{"description":"What to do: build (make the change and serve a preview; the PR opens on publish_repo_change), build_pr (make the change and open the PR straight away), build_merge (open the PR and merge it straight away when the repo's branch rules allow; otherwise it stays open with the reason), slack (post the change to the organization's bound Slack channel, tagging its own coding agent, which makes it; the run ends as `sent`), or linear (file it as an issue in the organization's connected Linear team for the coding agent that builds from Linear; the run ends as `sent`). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"preview":{"description":"Preview before the PR: serve the changed site at a preview URL (run.preview.url) and wait in phase `review` for publish_repo_change (Make PR) or cancel_repo_change (Discard). Review expires after 72 hours with no PR. Same as action build (true) or build_pr (false); `action` wins when both are given. With neither, the organization's default (get_repo_change_settings).","type":"boolean"},"with_preview":{"description":"With action build_pr or build_merge: serve the changed site at a preview URL too (run.preview.url, opened on the first change), and open the PR as soon as the preview is up, or has failed to start. The preview stays up next to the PR until it expires 72 hours later. Ignored for build (which always previews), slack and linear. Default false, except for a change to a site (start_site_change, Fix), which comes with one unless this is false.","type":"boolean"},"harness":{"description":"The coding agent that makes the change: claude-code (Claude Code) or letterpress-1 (Letter's own harness); codex (OpenAI's Codex CLI) on a test organization only. Omitted: this deployment's default. Recorded on the run (run.harness), and a revision keeps it.","type":"string","enum":["claude-code","letterpress-1","codex"]},"verifier":{"description":"Review the change before it goes out: an adversarial reviewer checks the diff against the request, the brand and the attached files, and the agent fixes what's blocking (up to a few rounds). true or false wins; omitted, this deployment's setting decides. The outcome is on the run (run.verifier), and a revision keeps the choice.","type":"boolean"},"again":{"description":"Start it even when an open change on the repo already asks for the same thing (otherwise that change comes back, to follow up on).","type":"boolean"}},"required":["repo_owner","repo_name","instructions"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"start_site_change","description":"CHANGE THE ORGANIZATION'S OWN SITE — a new page, an index page above the blog, a new section, a fix to a template, missing sitemap entries — from plain-language instructions, in the repo verified to build that site (list_repo_sites). With none, a build is refused but the change can still be handed off: action handoff returns the instructions to copy into any agent, and slack / linear post or file them for the organization's own agent (nothing is built). This is what Instances → Build and Fix run for a site change: Letter hands the build sandbox the site's brand (a brief on the brand learned from the site, and the brand measured from it) and its URL patterns so the change matches what is there, and the sandbox only builds — it produces a pull request (or, with action slack, the change is posted to the org's Slack channel for its own agent). primitives_access gives the sandbox READ-ONLY access to the organization's Letter primitives for changes that build on them (a blog's posts, a writing kernel); set them up first, the sandbox never writes one. Spends model credit; it stays open for review unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Poll get_repo_change. Check list_open_pull_requests and list_repo_changes first: if a change already does this, follow up on it (revise_repo_change); the result lists the others open on the repo as `alsoOpen`. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"instructions":{"type":"string","minLength":10,"maxLength":8000,"description":"What to build or fix on the site, in plain words: what, where (the URL path, following the site's existing patterns), and what it should say. e.g. 'Add an index page at /blog/guides listing every guide post above the blog's post list.'"},"title":{"description":"A short title; it becomes the pull request's first line.","type":"string","minLength":1,"maxLength":120},"host":{"description":"The site to change (a mapped host). Defaults to the organization's own domain.","type":"string","minLength":1,"maxLength":253},"files":{"description":"Files the change needs (a brief, a screenshot, a CSV, an image to place), as start_repo_change takes them. The sandbox gets each at /data/run/files/<name> beside the site's brand.md and site.md; refer to them by name in the instructions. A `reference` file is read, never put in the repo. Only a change built in a repo gets them: slack, linear and handoff send the instructions alone.","maxItems":18,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."},"url":{"description":"An https URL the sandbox downloads as-is.","type":"string","format":"uri"},"upload_path":{"description":"The `path` create_repo_change_upload returned, after PUTting the bytes to its upload_url.","type":"string","maxLength":400},"content_base64":{"description":"The file's bytes, base64-encoded. Up to 3MB decoded.","type":"string"},"reference":{"description":"Read-only context for the agent (a brief, notes, a spec): it reads the file and never puts it in the repo. Default false: the agent places the file in the repo where it belongs.","type":"boolean"}},"required":["name"]}},"action":{"description":"How the change happens: build (make it and serve a preview; the PR opens on publish_repo_change), build_pr (open the PR straight away), build_merge, slack (post it to the organization's Slack channel for its own coding agent), linear (file it as an issue in the organization's Linear team), or handoff (return the instructions in `handoff.instructions`, to copy into any agent; records nothing). slack and linear work with no repo connected too (the instructions go out, no repo change is made). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear","handoff"]},"with_preview":{"description":"With action build_pr or build_merge: serve the changed site at a preview URL too (run.preview.url, opened on the first change), and open the PR as soon as the preview is up, or has failed to start. The preview stays up next to the PR until it expires 72 hours later. Ignored for build (which always previews), slack and linear. Default false, except for a change to a site (start_site_change, Fix), which comes with one unless this is false.","type":"boolean"},"harness":{"description":"The coding agent that makes the change: claude-code (Claude Code) or letterpress-1 (Letter's own harness). Omitted: this deployment's default. Recorded on the run (run.harness), and a revision keeps it.","type":"string","enum":["claude-code","letterpress-1"]},"primitives_access":{"description":"Give the build sandbox READ-ONLY access to the organization's Letter primitives (its blog and posts, writing kernel, phantoms) through the MCP server, for changes that build on them. They must already be configured; the sandbox can never write one. Default false.","type":"boolean"},"again":{"description":"Start it even when an open change on the repo already asks for the same thing (otherwise that change comes back, to follow up on).","type":"boolean"},"covers":{"description":"The observation keys this change fixes (quick.missing-llms-txt), when it fixes ones Fix knows. While it runs Fix shows them as being fixed instead of offering a second fix, and once it merges the site is checked again for them. Omitted: a change Fix knows nothing about.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z0-9._-]+$"}}},"required":["instructions"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},{"type":"null"}],"description":"The repo change; null when the change was handed off (action handoff, or slack / linear with no repo)."},"handoff":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["prompt","slack","linear"]},"instructions":{"type":"string","description":"The change as instructions for a coding agent: what, where, how."},"label":{"type":"string"},"permalink":{"description":"The Slack message, when sent there.","type":["string","null"]},"issue":{"anyOf":[{"type":"object","properties":{"identifier":{"type":"string"},"url":{"type":"string"}},"required":["identifier","url"]},{"type":"null"}],"description":"The Linear issue, when filed."}},"required":["kind","instructions","label","permalink","issue"]},{"type":"null"}],"description":"Set when the change was handed off instead of built: the instructions, and where they went."},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}}},"required":["run","handoff","message"]}},{"name":"create_repo_change_upload","description":"Reserve upload URLs for files a repo change needs (images, documents, page drafts) that are too big to send inline. PUT each file's bytes to its upload_url, then pass its path as `upload_path` in start_repo_change's `files`. Up to 10MB per file; files under 3MB can go inline as `content_base64` instead, and public files as `url`. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"file_names":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9][A-Za-z0-9._ -]*$","description":"The file's name in the sandbox (/data/run/files/<name>); refer to it by this name in the instructions."}}},"required":["file_names"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"uploads":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string","description":"Pass as `upload_path` in start_repo_change's files."},"upload_url":{"type":"string","description":"PUT the file's bytes here. Valid for two hours."}},"required":["name","path","upload_url"]}},"message":{"type":"string"}},"required":["uploads","message"]}},{"name":"fix_audit_issue","description":"Open a pull request that fixes one site-audit issue type (e.g. missing-title, from get_audit_issues) on every page the audit found it on — or several (issue_types) in one PR. Letter writes the instructions from the audit itself (why it matters, how to fix, each affected page with its evidence) and runs a repo change in the repo verified to build the audited site (list_repo_sites) — never a guessed one; with no verified repo it refuses. Calling it again while a fix for the same issue is running returns that run. Spends model credit; it stays open for review unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Poll get_repo_change. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"audit_id":{"description":"The site audit; defaults to the organization's latest.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"issue_type":{"description":"The audit issue type to fix on every affected page, e.g. missing-title (see get_audit_issues).","type":"string","minLength":1,"maxLength":100},"issue_types":{"description":"Several issue types of the same audit to fix together in ONE pull request (list_suggested_fixes ranks them). Give this or issue_type.","minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":100}},"base_branch":{"description":"Branch to change and open the PR against. Defaults to the organization's default branch where the repo has it, else the repo's default branch.","type":"string","minLength":1,"maxLength":200},"action":{"description":"What to do: build (make the change and serve a preview; the PR opens on publish_repo_change), build_pr (make the change and open the PR straight away), build_merge (open the PR and merge it straight away when the repo's branch rules allow; otherwise it stays open with the reason), slack (post the change to the organization's bound Slack channel, tagging its own coding agent, which makes it; the run ends as `sent`), or linear (file it as an issue in the organization's connected Linear team for the coding agent that builds from Linear; the run ends as `sent`). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"preview":{"description":"Preview before the PR: serve the changed site at a preview URL (run.preview.url) and wait in phase `review` for publish_repo_change (Make PR) or cancel_repo_change (Discard). Review expires after 72 hours with no PR. Same as action build (true) or build_pr (false); `action` wins when both are given. With neither, the organization's default (get_repo_change_settings).","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]},"reused":{"type":"boolean","description":"True when a fix for this issue was already running and this is that run."}},"required":["run","message","reused"]}},{"name":"fix_site_findings","description":"Open ONE pull request that fixes any of a quick check's findings (The Site's quick.* observations, e.g. missing-meta-description, missing-llms-txt, ai-answer-bots-blocked; fix_site_observations takes them by observation id). Checks the site again first, so only what is still true is fixed (the rest comes back in `skipped`), writes the instructions from the check's own evidence, and runs a repo change in the repo verified to build the site (list_repo_sites) — never a guessed one; with no verified repo it refuses. Calling it again while a fix for the same findings is running returns that run. Spends model credit; it stays open for review unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Poll get_repo_change. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"finding_ids":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"string","enum":["homepage-noindex","robots-blocks-search","javascript-only-homepage","ai-answer-bots-blocked-http","ai-answer-bots-blocked","missing-homepage-title","generic-homepage-title","inner-pages-reuse-homepage-title","missing-meta-description","placeholder-meta-description","missing-link-preview","missing-llms-txt","llms-txt-empty","llms-txt-missing-key-pages","llms-full-txt-missing","missing-organization-schema","missing-sitemap","sitemap-invalid","ai-training-bots-blocked","soft-404","duplicate-host","sitemap-no-lastmod","robots-missing-sitemap-line","missing-homepage-h1"]},"description":"The quick-check findings to fix, e.g. missing-meta-description (propose_account_plan names them). All go into one PR."},"domain":{"description":"The site to check and fix; defaults to the organization's domain.","type":"string","minLength":1,"maxLength":253},"base_branch":{"description":"Branch to change and open the PR against. Defaults to the organization's default branch where the repo has it, else the repo's default branch.","type":"string","minLength":1,"maxLength":200},"action":{"description":"What to do: build (make the change and serve a preview; the PR opens on publish_repo_change), build_pr (make the change and open the PR straight away), build_merge (open the PR and merge it straight away when the repo's branch rules allow; otherwise it stays open with the reason), slack (post the change to the organization's bound Slack channel, tagging its own coding agent, which makes it; the run ends as `sent`), or linear (file it as an issue in the organization's connected Linear team for the coding agent that builds from Linear; the run ends as `sent`). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"preview":{"description":"Preview before the PR: serve the changed site at a preview URL (run.preview.url) and wait in phase `review` for publish_repo_change (Make PR) or cancel_repo_change (Discard). Review expires after 72 hours with no PR. Same as action build (true) or build_pr (false); `action` wins when both are given. With neither, the organization's default (get_repo_change_settings).","type":"boolean"}},"required":["finding_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]},"reused":{"type":"boolean","description":"True when a fix for these findings was already running and this is that run."},"fixing":{"type":"array","items":{"type":"string","enum":["homepage-noindex","robots-blocks-search","javascript-only-homepage","ai-answer-bots-blocked-http","ai-answer-bots-blocked","missing-homepage-title","generic-homepage-title","inner-pages-reuse-homepage-title","missing-meta-description","placeholder-meta-description","missing-link-preview","missing-llms-txt","llms-txt-empty","llms-txt-missing-key-pages","llms-full-txt-missing","missing-organization-schema","missing-sitemap","sitemap-invalid","ai-training-bots-blocked","soft-404","duplicate-host","sitemap-no-lastmod","robots-missing-sitemap-line","missing-homepage-h1"]},"description":"The findings the PR fixes"},"skipped":{"type":"array","items":{"type":"string","enum":["homepage-noindex","robots-blocks-search","javascript-only-homepage","ai-answer-bots-blocked-http","ai-answer-bots-blocked","missing-homepage-title","generic-homepage-title","inner-pages-reuse-homepage-title","missing-meta-description","placeholder-meta-description","missing-link-preview","missing-llms-txt","llms-txt-empty","llms-txt-missing-key-pages","llms-full-txt-missing","missing-organization-schema","missing-sitemap","sitemap-invalid","ai-training-bots-blocked","soft-404","duplicate-host","sitemap-no-lastmod","robots-missing-sitemap-line","missing-homepage-h1"]},"description":"Asked for, but a fresh check no longer finds them"}},"required":["run","message","reused","fixing","skipped"]}},{"name":"letterprove_open_install_pr","description":"Install Letterprove (Proofs) in one of the organization's repos as a pull request. Letterprove publishes signed proof of how many companies sign in to the org's own product, from the email domain of its signed-in users (counts, never names). It does not identify, track or de-anonymize site visitors: that is Visitors (visitors_open_install_pr, visitors_get_people), and this PR never makes visitors identifiable. The coding agent adds the script tag (this vendor's publishable key) to every page and calls Letterprove.identify/login/signup where the app already knows the signed-in user's email. Name the repo where users sign in (often the app, not the marketing site); list_repo_sites shows which repo builds the Proofs domain. Needs Proofs set up (a domain). Without `action` the PR merges once the repo's checks pass (an install's default), unless the org's default_action keeps changes from going straight to a PR (build, slack, linear). Calling it again while an install is running returns that run. Spends model credit. Poll get_repo_change; after merging, letterprove_get_status shows the script loading. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$","description":"Who owns the repo where users sign in: a GitHub account the Letter GitHub App reaches, or a GitLab group path."},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$","description":"The repo to install Letterprove in: the one with the app's sign-in code, which may not be the marketing site's repo."},"base_branch":{"description":"Branch to change and open the PR against. Defaults to the organization's default branch where the repo has it, else the repo's default branch.","type":"string","minLength":1,"maxLength":200},"action":{"description":"What to do: build (make the change and serve a preview; the PR opens on publish_repo_change), build_pr (make the change and open the PR straight away), build_merge (open the PR and merge it straight away when the repo's branch rules allow; otherwise it stays open with the reason), slack (post the change to the organization's bound Slack channel, tagging its own coding agent, which makes it; the run ends as `sent`), or linear (file it as an issue in the organization's connected Linear team for the coding agent that builds from Linear; the run ends as `sent`). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]}},"required":["repo_owner","repo_name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]},"reused":{"type":"boolean","description":"True when a Letterprove install was already running and this is that run."}},"required":["run","message","reused"]}},{"name":"visitors_open_install_pr","description":"Install Visitors (LetterVisit, lettervisit.com) on the site as a pull request: the script that counts and, with identification on, de-anonymizes the site's visitors (visitors_get_people), and the server snippet v2 that sees AI agents (which never run JavaScript). This is not Letterprove, which counts the org's own signed-in product users for proof. The coding agent adds the site's script tag (publishable key) once, in the shared layout of the repo that builds the site, and the server snippet for the repo's stack (Next.js, Express or another Node server), replacing any older forward to lettervisit.com. A repo with no server code gets the tag only, and the PR says why. The repo is the one verified to build the site (list_repo_sites, set_repo_site); name repo_owner and repo_name only to use another. site_id is optional when the organization has one Visitors site (visitors_list_sites). Refused, saying what to fix, when no repo is verified to build the site. Without `action` the PR merges once the repo's checks pass (an install's default), unless the org's default_action keeps changes from going straight to a PR (build, slack, linear). Calling it again while an install is running for the site returns that run. Spends model credit. Poll get_repo_change; once merged, Letter looks for the tag on the site itself for about an hour (the run's verify_state; found, the site is proven), and visitors_verify_site finds the tag and visitors_get_script shows the script and server snippet (status.server_snippet_version 2) reporting. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site (visitors_list_sites). Optional when the organization has one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"description":"Who owns the repo that builds the site (a GitHub account, or a GitLab group path). Leave out to use the repo verified to build it (list_repo_sites).","type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"description":"The repo that builds the site; give it with repo_owner, or leave both out.","type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"base_branch":{"description":"Branch to change and open the PR against. Defaults to the organization's default branch where the repo has it, else the repo's default branch.","type":"string","minLength":1,"maxLength":200},"action":{"description":"What to do: build (make the change and serve a preview; the PR opens on publish_repo_change), build_pr (make the change and open the PR straight away), build_merge (open the PR and merge it straight away when the repo's branch rules allow; otherwise it stays open with the reason), slack (post the change to the organization's bound Slack channel, tagging its own coding agent, which makes it; the run ends as `sent`), or linear (file it as an issue in the organization's connected Linear team for the coding agent that builds from Linear; the run ends as `sent`). Defaults to the organization's default action (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]},"reused":{"type":"boolean","description":"True when a Visitors install was already running for this site and this is that run."}},"required":["run","message","reused"]}},{"name":"list_suggested_fixes","description":"Every fix Letter has found and nobody has acted on yet: exactly what Instances → Fix lists for each of the organization's sites, in Fix's order (get_site_tree's focus, every item: audit issues, the quick check, page readings, links, AI answers; what a member dismissed is left out). Once Proofs has a domain, the Letterprove install (kind letterprove_install) comes first: it is the Proofs integration in the product's sign-in code, not a site fix and not visitor identification. Each names the repo it goes to (null when none builds the site: fix_site_observations then needs action handoff, slack or linear) and how to start it (`start.tool` with `start.args`; a null args means pick the repo first). `risky` items move pages: start them only on purpose. A fix leaves the list once it is running, has a PR, or merged and waits on the site's re-check; a failed or cancelled attempt stays, as lastRun. || item detail (suggestions.ts:186): \"Add the Letterprove script and call identify where your app knows a signed-in user's email, so companies that sign in to your product count toward your proof. Does not identify site visitors.\" Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["site_observations","letterprove_install"]},"title":{"type":"string"},"detail":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"siteHost":{"type":"string"},"repo":{"type":["string","null"]},"pages":{"type":["number","null"]},"risky":{"type":"boolean","description":"Moves pages and adds redirects: start it only on purpose"},"key":{"description":"The observation key behind it (audit.<issue type> for a site audit's issue); null for an install","type":["string","null"]},"lastRun":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},{"type":"null"}]},"start":{"type":"object","properties":{"tool":{"type":"string","enum":["fix_site_observations","letterprove_open_install_pr"]},"args":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},{"type":"null"}],"description":"The arguments for `tool`; null means pick the repo first"}},"required":["tool","args"]}},"required":["id","kind","title","detail","severity","siteHost","repo","pages","risky","key","lastRun","start"]}}},"required":["suggestions"]}},{"name":"list_repo_sites","description":"Which website each connected repo builds, as decided automatically from evidence: config that names the host, crawled page paths that map to the repo's files, and page titles found in them. One row per repo and site checked (the org's domain and every site it has audited), with the verdict (serves / not_matched), a 0-1 score, the evidence and when it was checked. Fixes for a site only go to a repo that serves it. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives."},"repo":{"type":"string"},"repoOwner":{"type":"string"},"repoName":{"type":"string"},"siteHost":{"type":"string"},"verdict":{"type":"string","enum":["serves","not_matched"]},"branch":{"description":"The branch that deploys the site when it isn't the repo's default; fixes for the site start from it.","type":["string","null"]},"score":{"type":"number","description":"0-1: how much of the crawled site the repo accounts for."},"evidence":{"type":"object","properties":{"signals":{"type":"array","items":{"type":"string","enum":["host_in_config","titles","routes"]}},"pagesChecked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pagesMatched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"configMentions":{"type":"array","items":{"type":"string"},"description":"Repo files that name the host."},"titleConfirmed":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"}},"required":["url","file"]}},"examples":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"},"kind":{"type":"string","enum":["file","dynamic"]}},"required":["url","file","kind"]}},"treeTruncated":{"type":"boolean"},"routesContradict":{"description":"The host is named in config, but five or more crawled pages have no file in the repo: not served.","type":"boolean"},"detector":{"description":"The detector version that decided it; an older one is re-checked before a change starts.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["signals","pagesChecked","pagesMatched","configMentions","titleConfirmed","examples","treeTruncated"]},"decidedBy":{"type":"string","enum":["detector","member"],"description":"detector: decided from evidence. member: a person set or corrected it; never overwritten automatically."},"checkedAt":{"type":"string"}},"required":["provider","repo","repoOwner","repoName","siteHost","verdict","branch","score","evidence","decidedBy","checkedAt"]}}},"required":["sites"]}},{"name":"detect_repo_sites","description":"Re-check which website each connected repo builds (see list_repo_sites). Runs automatically when GitHub is connected, when the Repos tool or an audit is opened, and before an audit fix; call it after giving the app access to a new repo or auditing a new site. Reads repos through a read-only token; skips repos checked in the last day at the same commit unless force. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"force":{"description":"Re-check every repo now, even ones checked in the last day at the same commit.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"checked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Repos already checked in the last day at the same commit."},"failed":{"type":"array","items":{"type":"object","properties":{"repo":{"type":"string"},"error":{"type":"string"}},"required":["repo","error"]}},"sites":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives."},"repo":{"type":"string"},"repoOwner":{"type":"string"},"repoName":{"type":"string"},"siteHost":{"type":"string"},"verdict":{"type":"string","enum":["serves","not_matched"]},"branch":{"description":"The branch that deploys the site when it isn't the repo's default; fixes for the site start from it.","type":["string","null"]},"score":{"type":"number","description":"0-1: how much of the crawled site the repo accounts for."},"evidence":{"type":"object","properties":{"signals":{"type":"array","items":{"type":"string","enum":["host_in_config","titles","routes"]}},"pagesChecked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pagesMatched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"configMentions":{"type":"array","items":{"type":"string"},"description":"Repo files that name the host."},"titleConfirmed":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"}},"required":["url","file"]}},"examples":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"},"kind":{"type":"string","enum":["file","dynamic"]}},"required":["url","file","kind"]}},"treeTruncated":{"type":"boolean"},"routesContradict":{"description":"The host is named in config, but five or more crawled pages have no file in the repo: not served.","type":"boolean"},"detector":{"description":"The detector version that decided it; an older one is re-checked before a change starts.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["signals","pagesChecked","pagesMatched","configMentions","titleConfirmed","examples","treeTruncated"]},"decidedBy":{"type":"string","enum":["detector","member"],"description":"detector: decided from evidence. member: a person set or corrected it; never overwritten automatically."},"checkedAt":{"type":"string"}},"required":["provider","repo","repoOwner","repoName","siteHost","verdict","branch","score","evidence","decidedBy","checkedAt"]}}},"required":["checked","skipped","failed","sites"]}},{"name":"set_repo_site","description":"Set which website a connected repo builds (builds: true, the default), or correct a wrong automatic label (builds: false). Recorded as the member's decision and never overwritten by detection until reset_repo_site. Site-level changes such as fix_audit_issue go to the repo that builds the site, a member's answer first. When one repo hosts several sites a branch each, give branch (the branch that deploys this site): fixes for the site start from it. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"site_host":{"type":"string","minLength":3,"maxLength":253,"description":"The website's host, e.g. acme.com (a URL is fine; www. is dropped)."},"builds":{"description":"true (default): this repo builds the site. false: it doesn't — corrects a wrong automatic label.","type":"boolean"},"branch":{"description":"The branch that deploys this site, when it isn't the repo's default (one repo, a site per branch). Fixes for the site start from it. null goes back to the default branch.","anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]}},"required":["repo_owner","repo_name","site_host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site":{"type":"object","properties":{"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives."},"repo":{"type":"string"},"repoOwner":{"type":"string"},"repoName":{"type":"string"},"siteHost":{"type":"string"},"verdict":{"type":"string","enum":["serves","not_matched"]},"branch":{"description":"The branch that deploys the site when it isn't the repo's default; fixes for the site start from it.","type":["string","null"]},"score":{"type":"number","description":"0-1: how much of the crawled site the repo accounts for."},"evidence":{"type":"object","properties":{"signals":{"type":"array","items":{"type":"string","enum":["host_in_config","titles","routes"]}},"pagesChecked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pagesMatched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"configMentions":{"type":"array","items":{"type":"string"},"description":"Repo files that name the host."},"titleConfirmed":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"}},"required":["url","file"]}},"examples":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"file":{"type":"string"},"kind":{"type":"string","enum":["file","dynamic"]}},"required":["url","file","kind"]}},"treeTruncated":{"type":"boolean"},"routesContradict":{"description":"The host is named in config, but five or more crawled pages have no file in the repo: not served.","type":"boolean"},"detector":{"description":"The detector version that decided it; an older one is re-checked before a change starts.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["signals","pagesChecked","pagesMatched","configMentions","titleConfirmed","examples","treeTruncated"]},"decidedBy":{"type":"string","enum":["detector","member"],"description":"detector: decided from evidence. member: a person set or corrected it; never overwritten automatically."},"checkedAt":{"type":"string"}},"required":["provider","repo","repoOwner","repoName","siteHost","verdict","branch","score","evidence","decidedBy","checkedAt"]},"message":{"type":"string"}},"required":["site","message"]}},{"name":"get_repo_change_settings","description":"The organization's defaults for repo changes: default_action (what a fix does when the caller doesn't say — build: preview first, PR on publish_repo_change; build_pr: open the PR straight away (built-in default); build_merge: open it and merge it; slack: post it to the organization's bound Slack channel for its own coding agent to make; linear: file it in the connected Linear team), default_branch (where changes go when the repo has that branch; null = each repo's default branch), slack_agent (who a Slack send tags) and slack_channel_id (where sends go; null = no channel bound yet). Every tool that starts a change (start_repo_change, start_site_change, fix_audit_issue, fix_site_findings, fix_site_observations, letterprove_open_install_pr, visitors_open_install_pr) uses them unless given action / base_branch; the two installs merge where default_action is build_pr. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"default_action":{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"default_branch":{"type":["string","null"]},"slack_agent":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string"},"name":{"type":["string","null"]}},"required":["user_id","name"]},{"type":"null"}],"description":"Who a change sent to Slack tags; null tags nobody."},"slack_channel_id":{"description":"The bound Slack channel changes are sent to; null means action slack can't work until a channel is bound.","type":["string","null"]},"slack_channel_name":{"description":"That channel's name when it was picked with slack_channel; null for a channel staff linked.","type":["string","null"]},"slack_connected":{"type":"boolean","description":"Whether the organization has connected its own Slack workspace."},"slack_team":{"description":"The connected Slack workspace's name.","type":["string","null"]},"slack_reconnect_needed":{"type":"boolean","description":"The connection predates channel picking; reconnect Slack before list_slack_fix_targets works."},"slack_listening_channels":{"type":"array","items":{"type":"string"},"description":"Channels where Letter answers every message, no @-mention needed."},"slack_listen_all":{"type":"boolean","description":"Letter answers messages meant for it in every other channel it's in, no @-mention needed."},"autopilot_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where autopilot announces what it starts; null = the fix channel."},"autopilot":{"type":"object","properties":{"enabled":{"type":"boolean"},"weekly_limit":{"type":"number"},"min_severity":{"type":"string","enum":["critical","warning"]}},"required":["enabled","weekly_limit","min_severity"],"description":"Whether Letter starts the top fixes on its own, how many a week, and how severe they must be."},"auto_merge":{"type":"object","properties":{"after_hours":{"anyOf":[{"type":"number","enum":[1,12,24,72]},{"type":"null"}],"description":"1, 12, 24 or 72; null = off."},"since":{"description":"When auto-merge was turned on; a PR opened before then waits from here.","type":["string","null"]}},"required":["after_hours","since"],"description":"Whether Letter merges the pull requests it opened on its own, and after how long."},"editor_auto_merge":{"type":"boolean","description":"Letter Edit (⌘L on the site): true (the default), its changes open their pull request and merge once ready; false, they open their pull request and stop there."},"updated_by":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"message":{"type":"string"}},"required":["default_action","default_branch","slack_agent","slack_channel_id","slack_channel_name","slack_connected","slack_team","slack_reconnect_needed","slack_listening_channels","slack_listen_all","autopilot_channel","autopilot","auto_merge","editor_auto_merge","updated_by","updated_at","message"]}},{"name":"set_repo_change_settings","description":"Change the organization's repo-change defaults: default_action (build, build_pr, build_merge, slack or linear), default_branch (a branch name, or null for each repo's own default) slack_agent (who a change sent to Slack tags, e.g. Devin — looked up in the org's Slack workspace — or a member id from list_slack_fix_targets; null tags nobody) and/or slack_channel (where sends go: a channel id from list_slack_fix_targets, which Letter joins and links to the org), autopilot_channel (where autopilot announces what it starts; null = the fix channel), and/or autopilot (true/false: Letter starts the top suggested fixes on its own, hourly, at most one in flight) with autopilot_weekly_limit (1-20) and autopilot_min_severity (critical | warning), and/or auto_merge_after_hours (1 | 12 | 24 | 72: Letter merges the pull requests it opened once they've been open that long, bringing a PR that fell behind up to date and remaking one that conflicts; null turns it off). A field left out keeps its value. Applies to every fix started without its own action / base_branch, in the app and over the API. What Letter posts to Slack on its own (new issues, recommended actions, competitors' new pages, identified visitors) is an automation: create_event_automation with slack_channel. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"default_action":{"description":"What a fix does when the caller doesn't say: build (preview first, PR on Make PR), build_pr (open the PR straight away), slack (post it to the organization's Slack channel for its own agent to make), or linear (file it as an issue in the organization's Linear team for the agent that builds from Linear).","type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"default_branch":{"description":"The branch changes go to when the caller names none and the repo has it (e.g. staging); null goes back to each repo's own default branch.","anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}]},"slack_agent":{"description":"Who a change sent to Slack tags: a Slack handle or display name (e.g. Devin), looked up in the organization's Slack workspace, or a Slack member id (U…, from list_slack_fix_targets people). null tags nobody.","anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]},"slack_channel":{"description":"Where changes sent to Slack go: a public channel id from list_slack_fix_targets. Letter joins it and links it to the organization. null goes back to the organization's first linked channel.","anyOf":[{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"},{"type":"null"}]},"autopilot_channel":{"description":"Where autopilot announces what it starts: a channel id, as above. null = the fix channel.","anyOf":[{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"},{"type":"null"}]},"slack_listening_channels":{"description":"Listening channels: Letter answers every message in them (no @-mention), replying in a thread. The full list — ids from list_slack_fix_targets; each new one is joined and linked; [] turns them all off.","maxItems":20,"type":"array","items":{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"}},"slack_listen_all":{"description":"Every other channel Letter is in: it answers top-level messages that are meant for it (a quick triage skips the rest), no @-mention needed. Off by default; listening channels still answer everything.","type":"boolean"},"linear_reports":{"description":"Report new findings to Linear on their own: as_found files each new finding as an issue in the connected Linear team (the same send as action linear), for the coding agent that builds from Linear. Needs Linear connected with a team (get_linear_connection).","type":"string","enum":["off","as_found"]},"autopilot":{"description":"Autopilot: Letter starts the top suggested fixes on its own, once an hour, with the default action — at most one in flight and autopilot_weekly_limit a week; a fix it already tried isn't retried. Off by default.","type":"boolean"},"autopilot_weekly_limit":{"description":"Most fixes autopilot starts in a week (1-20, default 3).","type":"integer","minimum":1,"maximum":20},"autopilot_min_severity":{"description":"Least severe issue autopilot fixes: critical only, or warnings too (default).","type":"string","enum":["critical","warning"]},"auto_merge_after_hours":{"description":"Auto-merge: Letter merges the pull requests it opened (fixes, builds, posts published as PRs) once they've been open this many hours — 1, 12, 24 or 72 — squashing into their base branch. A PR that fell behind is brought up to date first and merged once its checks pass; one that conflicts, or fails its checks after updating, is made again on the current code and that new PR is merged instead. The repo's own branch rules still apply. A PR opened before auto-merge was turned on waits its window from then. null turns it off (the default).","anyOf":[{"type":"number","enum":[1,12,24,72]},{"type":"null"}]},"editor_auto_merge":{"description":"Letter Edit's auto-merge (Settings → Letter Edit): true, a change asked for with ⌘L on the organization's own site opens its pull request as soon as it's built and merges once it's ready (up to date, its checks passing; checks that fail are fixed on the PR and a conflict is made again on the current code, up to 3 times each, until it merges; the repo's branch rules still apply), and until then can be cancelled from the card (cancel_growth_build). true is the default. false: it opens its pull request as soon as it's built and waits there for someone to merge it. The card's send button says which: Make Pull Request & Merge, or Make Pull Request.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"default_action":{"type":"string","enum":["build","build_pr","build_merge","slack","linear"]},"default_branch":{"type":["string","null"]},"slack_agent":{"anyOf":[{"type":"object","properties":{"user_id":{"type":"string"},"name":{"type":["string","null"]}},"required":["user_id","name"]},{"type":"null"}],"description":"Who a change sent to Slack tags; null tags nobody."},"slack_channel_id":{"description":"The bound Slack channel changes are sent to; null means action slack can't work until a channel is bound.","type":["string","null"]},"slack_channel_name":{"description":"That channel's name when it was picked with slack_channel; null for a channel staff linked.","type":["string","null"]},"slack_connected":{"type":"boolean","description":"Whether the organization has connected its own Slack workspace."},"slack_team":{"description":"The connected Slack workspace's name.","type":["string","null"]},"slack_reconnect_needed":{"type":"boolean","description":"The connection predates channel picking; reconnect Slack before list_slack_fix_targets works."},"slack_listening_channels":{"type":"array","items":{"type":"string"},"description":"Channels where Letter answers every message, no @-mention needed."},"slack_listen_all":{"type":"boolean","description":"Letter answers messages meant for it in every other channel it's in, no @-mention needed."},"autopilot_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where autopilot announces what it starts; null = the fix channel."},"autopilot":{"type":"object","properties":{"enabled":{"type":"boolean"},"weekly_limit":{"type":"number"},"min_severity":{"type":"string","enum":["critical","warning"]}},"required":["enabled","weekly_limit","min_severity"],"description":"Whether Letter starts the top fixes on its own, how many a week, and how severe they must be."},"auto_merge":{"type":"object","properties":{"after_hours":{"anyOf":[{"type":"number","enum":[1,12,24,72]},{"type":"null"}],"description":"1, 12, 24 or 72; null = off."},"since":{"description":"When auto-merge was turned on; a PR opened before then waits from here.","type":["string","null"]}},"required":["after_hours","since"],"description":"Whether Letter merges the pull requests it opened on its own, and after how long."},"editor_auto_merge":{"type":"boolean","description":"Letter Edit (⌘L on the site): true (the default), its changes open their pull request and merge once ready; false, they open their pull request and stop there."},"updated_by":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"message":{"type":"string"}},"required":["default_action","default_branch","slack_agent","slack_channel_id","slack_channel_name","slack_connected","slack_team","slack_reconnect_needed","slack_listening_channels","slack_listen_all","autopilot_channel","autopilot","auto_merge","editor_auto_merge","updated_by","updated_at","message"]}},{"name":"list_slack_fix_targets","description":"Setting up Send to Slack: the public channels in the organization's connected Slack workspace, and the people a send can tag (bots — coding agents like Devin — first). Pass a channel id to set_repo_change_settings slack_channel and a person id to slack_agent, then send_slack_test_message. Needs the organization's own Slack connection (Settings → Fixes → Connect Slack). Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"channels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"is_member":{"type":"boolean"},"is_private":{"type":"boolean"}},"required":["id","name","is_member","is_private"]},"description":"Public channels in the organization's Slack; pass an id as set_repo_change_settings slack_channel."},"people":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"is_bot":{"type":"boolean"}},"required":["id","name","is_bot"]},"description":"Who a send can tag, bots (coding agents) first; pass an id as set_repo_change_settings slack_agent."},"message":{"type":"string"}},"required":["channels","people","message"]}},{"name":"send_slack_test_message","description":"Post a test into the organization's Send to Slack channel, to prove a change sent there will land. Names the agent set as slack_agent without tagging them, so it sets no one to work. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"channel_id":{"type":"string"},"permalink":{"type":["string","null"]},"message":{"type":"string"}},"required":["channel_id","permalink","message"]}},{"name":"reset_repo_site","description":"Undo set_repo_site for one repo and website: the pair goes back to automatic detection (detect_repo_sites) on its next check. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"site_host":{"type":"string","minLength":3,"maxLength":253,"description":"The website's host, e.g. acme.com (a URL is fine; www. is dropped)."}},"required":["repo_owner","repo_name","site_host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reset":{"type":"boolean"},"message":{"type":"string"}},"required":["reset","message"]}},{"name":"list_repo_preview_secrets","description":"List a repo's preview secrets: the env vars a repo change's live preview site gets. Returns each name and when it was set, never a value (values are write-only). Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"}},"required":["repo_owner","repo_name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"secrets":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"updatedAt":{"type":"string"}},"required":["key","updatedAt"]}}},"required":["secrets"]}},{"name":"set_repo_preview_secret","description":"Set (or overwrite) one preview secret: an env var the live preview site of this repo's changes gets when it starts, after Claude Code has finished (Claude sees only the name). Write-only: no tool returns the value. The preview URL is public to anyone with the link and the site's code can read the value, so use a preview or test credential, never a production one. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"key":{"type":"string","maxLength":128,"description":"The environment variable's name, e.g. LETTERSTORY_API_KEY."},"value":{"type":"string","description":"The value. Use a preview or test credential: the preview URL is public to anyone with the link and the site's code can read it."}},"required":["repo_owner","repo_name","key","value"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"secret":{"type":"object","properties":{"key":{"type":"string"},"updatedAt":{"type":"string"}},"required":["key","updatedAt"]},"message":{"type":"string"}},"required":["secret","message"]}},{"name":"delete_repo_preview_secret","description":"Delete one of a repo's preview secrets. Previews started afterwards no longer get it. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"provider":{"description":"Where the repo lives: github (the default) or gitlab.","type":"string","enum":["github","gitlab"]},"repo_owner":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}(?:\\/[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}){0,19}$"},"repo_name":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._-]+$"},"key":{"type":"string","minLength":1,"maxLength":128,"description":"The environment variable's name."}},"required":["repo_owner","repo_name","key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"message":{"type":"string"}},"required":["deleted","message"]}},{"name":"get_repo_change","description":"Check a repo-change run: its phase, spend, progress feed and — once finished — the pull request, Claude Code's summary and the files changed. Not read-only: polling a running run also advances it, and may mark a dead run failed. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"get_repo_change_activity","description":"Watch a repo-change run as it happens — what Instances → the Site and the letterpress terminal show while a build runs: one line per step, oldest first. Letter's milestones (sandbox up, checked out, preview ready, PR opened) woven with the sandbox's own feed: each stage, what the agent said and thought, each tool it called (a file it read or edited, with lines read or added/removed; a command it ran; a search) with structured step/file/detail fields, messages a member sent it mid-run (send_repo_change_message), and calls that failed. Read live from the sandbox while it builds and kept on the run once it stops. `live` true means poll again for more (every few seconds is fine); pass `since` (the last line's `at`) to get only what's new. Read-only: unlike get_repo_change it never advances the run. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."},"since":{"description":"Only lines at or after this time (the `at` of the last line you have); lines at that exact time come again, so drop the ones you hold.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"activity":{"type":"object","properties":{"runId":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"live":{"type":"boolean","description":"Still at work (not waiting in review, not ended): poll again for more lines"},"lines":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string","enum":["progress","stage","say","think","tool","user","error"],"description":"progress: Letter's own milestone; stage: the sandbox's; say: what the agent said; think: its reasoning; tool: a tool it called (a file it read or edited, a command it ran); user: a message a member sent it mid-run (send_repo_change_message), as it reached the agent; error: a call that failed"},"text":{"type":"string","description":"The line as one sentence"},"step":{"description":"A tool line's step: read, edit, write, command, search, fetch, agent (a subagent) or other","type":"string","enum":["read","edit","write","command","search","fetch","agent","other"]},"title":{"description":"What the agent said a command was for, when it said","type":"string"},"file":{"description":"The file a read, edit or write touched, relative to the repo","type":"string"},"detail":{"description":"A command's text, a search's pattern, a fetch's url","type":"string"},"lines":{"description":"Lines a read returned","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"added":{"description":"Lines an edit or write added","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"removed":{"description":"Lines an edit removed","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["at","kind","text"]},"description":"Oldest first"}},"required":["runId","status","phase","live","lines"]}},"required":["activity"]}},{"name":"list_repo_changes","description":"List the organization's repo-change runs, newest first: one short row each (title, repo, state, phase, the pull request and whether it is merged (prState, read live from GitHub), the preview, what it cost). Read one in full with get_repo_change; detail: \"full\" returns every field of every run, thousands of characters each. Check it before starting a change: a run already asking for the same thing is followed up on (revise_repo_change), not started again. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"status":{"description":"Only runs in this state.","type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"limit":{"description":"Newest first; defaults to 20.","type":"integer","minimum":1,"maximum":100},"audit_id":{"description":"Only runs started from this site audit's issues (fix_audit_issue).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"source_kind":{"description":"Only runs started from this part of the application: site_audit_issue (fix_audit_issue), site_quick_check (fix_site_findings), letterprove_install (letterprove_open_install_pr), visitors_install (visitors_open_install_pr) or collection_publish (a post published as a pull request to a GitHub publish target, publish_to_connector).","type":"string","enum":["site_audit_issue","site_quick_check","letterprove_install","visitors_install","collection_publish"]},"site_host":{"description":"Only changes to this site, however they were started (Instances, an audit, @Letter in Slack, autopilot, the API): runs that name the host, and runs on the repo that builds it.","type":"string","minLength":1,"maxLength":253},"detail":{"description":"brief (default): one short row per run (title, state, PR, preview), to find a change; get_repo_change reads one in full. full: every field of every run, which runs to thousands of characters each.","type":"string","enum":["brief","full"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},{"type":"object","properties":{"id":{"type":"string"},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"errorCode":{"type":["string","null"]},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"title":{"type":"string","description":"What was asked: the first line of its instructions."},"previewUrl":{"description":"The changed site, live, when the run serves a preview.","type":["string","null"]}},"required":["id","repo","status","phase","prUrl","prNumber","prState","costUsd","buildId","source","errorCode","attention","requestedVia","revisesRunId","revisedByRunId","continuesRunId","startedAt","completedAt","title","previewUrl"]}]}}},"required":["runs"]}},{"name":"get_coding_telemetry","description":"Staff only. How the coding agent does on Builds and Fixes across every organization, or one (`org_id`) (docs/architecture/coding-telemetry.md): over the last `days` (default 7) against the period before, per harness (letterpress-1, claude-code, codex) — runs, how many failed and whether the platform or the agent was at fault, pull requests merged / closed / open, first-try merge (a settled ask that merged on its first run, no revision or follow-up: the headline), cost per run and per merged pull request (every run it took, summed; runs that recorded no spend are unpriced, not free), sandbox / agent / total time (median and slowest twentieth), the verifier's outcomes and whether fixes were checked as gone; `first_pass` — how often the agent was right on the first try (the verifier's first review found no blocking issue; the repo's own checks passed before any fix), and when it was not, whose miss it was (agent: had what it needed and missed it; context: not told, or told unclearly; setup: the run could not be done as set up), the biggest recurring misses by cause and category with the reviewer's own notes and what to do about each (`gaps`), and `ours_runs` (runs whose blocking issues were all on our side: nothing the agent was told); failures by error code with their commonest detail lines; the share lost to timeouts, memory and crashes; a weekly series per harness; and `moved`, the headline changes beyond chance (empty means nothing moved — with fewer than 20 runs a side nothing is called). Rates carry n and a 95% interval. Test and demo organizations are left out unless customer_only is false. list_coding_runs lists the runs behind a number; `letterstory admin letterpress telemetry`. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"org_id":{"description":"Only this organization's runs. Default: every organization.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"customer_only":{"description":"Across organizations: leave out test and demo organizations (default true), as customers' numbers.","type":"boolean"},"harness":{"description":"Only runs on this coding harness: letterpress-1, claude-code or codex. Default: every harness.","type":"string","minLength":1,"maxLength":40},"days":{"description":"The period read, in days, against the same length before it (default 7).","type":"integer","minimum":1,"maximum":90},"weeks":{"description":"How many weeks the weekly series covers (default 8).","type":"integer","minimum":1,"maximum":26}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"scope":{"type":"string","enum":["org","fleet"]},"days":{"type":"number"},"since":{"type":"string"},"until":{"type":"string"},"customer_only":{"type":"boolean"},"harness":{"type":["string","null"]},"all":{"type":"object","properties":{"harness":{"type":"string","description":"letterpress-1, claude-code, codex, or all"},"models":{"type":"array","items":{"type":"string"}},"runs":{"type":"number"},"running":{"type":"number"},"in_review":{"type":"number","description":"Agent done, waiting on a person to review the preview"},"status":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"failed":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of runs the sandbox ran to an end, how many failed"},"platform_failed":{"type":"number","description":"Of those failures, how many were the platform's (never billed)"},"merged":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of pull requests since merged or closed, how many merged"},"prs":{"type":"object","properties":{"opened":{"type":"number"},"merged":{"type":"number"},"closed":{"type":"number"},"open":{"type":"number"}},"required":["opened","merged","closed","open"]},"first_try":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of lineages that merged, how many merged on their first run"},"first_try_merge":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"THE headline: of settled lineages (finished, pull request merged, closed or never opened), how many merged on their first run"},"cost":{"type":"object","properties":{"total_usd":{"type":"number"},"per_run":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"],"description":"Over priced runs only"},"per_merged_pr_usd":{"description":"Every run in a merged lineage, summed, per merged pull request (priced lineages)","type":["number","null"]},"budget_hits":{"type":"number"},"unpriced":{"type":"number","description":"Ended runs that recorded no spend (a harness not metered by the proxy)"}},"required":["total_usd","per_run","per_merged_pr_usd","budget_hits","unpriced"]},"time":{"type":"object","properties":{"sandbox_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"agent_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"total_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]}},"required":["sandbox_ms","agent_ms","total_ms"]},"verifier":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by verifier outcome"},"verified":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by whether the fix was checked as gone (verify_state)"}},"required":["harness","models","runs","running","in_review","status","failed","platform_failed","merged","prs","first_try","first_try_merge","cost","time","verifier","verified"]},"previous":{"type":"object","properties":{"harness":{"type":"string","description":"letterpress-1, claude-code, codex, or all"},"models":{"type":"array","items":{"type":"string"}},"runs":{"type":"number"},"running":{"type":"number"},"in_review":{"type":"number","description":"Agent done, waiting on a person to review the preview"},"status":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"failed":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of runs the sandbox ran to an end, how many failed"},"platform_failed":{"type":"number","description":"Of those failures, how many were the platform's (never billed)"},"merged":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of pull requests since merged or closed, how many merged"},"prs":{"type":"object","properties":{"opened":{"type":"number"},"merged":{"type":"number"},"closed":{"type":"number"},"open":{"type":"number"}},"required":["opened","merged","closed","open"]},"first_try":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of lineages that merged, how many merged on their first run"},"first_try_merge":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"THE headline: of settled lineages (finished, pull request merged, closed or never opened), how many merged on their first run"},"cost":{"type":"object","properties":{"total_usd":{"type":"number"},"per_run":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"],"description":"Over priced runs only"},"per_merged_pr_usd":{"description":"Every run in a merged lineage, summed, per merged pull request (priced lineages)","type":["number","null"]},"budget_hits":{"type":"number"},"unpriced":{"type":"number","description":"Ended runs that recorded no spend (a harness not metered by the proxy)"}},"required":["total_usd","per_run","per_merged_pr_usd","budget_hits","unpriced"]},"time":{"type":"object","properties":{"sandbox_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"agent_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"total_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]}},"required":["sandbox_ms","agent_ms","total_ms"]},"verifier":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by verifier outcome"},"verified":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by whether the fix was checked as gone (verify_state)"}},"required":["harness","models","runs","running","in_review","status","failed","platform_failed","merged","prs","first_try","first_try_merge","cost","time","verifier","verified"],"description":"The same reading for the period before"},"by_harness":{"type":"array","items":{"type":"object","properties":{"harness":{"type":"string","description":"letterpress-1, claude-code, codex, or all"},"models":{"type":"array","items":{"type":"string"}},"runs":{"type":"number"},"running":{"type":"number"},"in_review":{"type":"number","description":"Agent done, waiting on a person to review the preview"},"status":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"failed":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of runs the sandbox ran to an end, how many failed"},"platform_failed":{"type":"number","description":"Of those failures, how many were the platform's (never billed)"},"merged":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of pull requests since merged or closed, how many merged"},"prs":{"type":"object","properties":{"opened":{"type":"number"},"merged":{"type":"number"},"closed":{"type":"number"},"open":{"type":"number"}},"required":["opened","merged","closed","open"]},"first_try":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of lineages that merged, how many merged on their first run"},"first_try_merge":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"THE headline: of settled lineages (finished, pull request merged, closed or never opened), how many merged on their first run"},"cost":{"type":"object","properties":{"total_usd":{"type":"number"},"per_run":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"],"description":"Over priced runs only"},"per_merged_pr_usd":{"description":"Every run in a merged lineage, summed, per merged pull request (priced lineages)","type":["number","null"]},"budget_hits":{"type":"number"},"unpriced":{"type":"number","description":"Ended runs that recorded no spend (a harness not metered by the proxy)"}},"required":["total_usd","per_run","per_merged_pr_usd","budget_hits","unpriced"]},"time":{"type":"object","properties":{"sandbox_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"agent_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]},"total_ms":{"type":"object","properties":{"n":{"type":"number"},"p50":{"type":["number","null"]},"p95":{"type":["number","null"]}},"required":["n","p50","p95"]}},"required":["sandbox_ms","agent_ms","total_ms"]},"verifier":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by verifier outcome"},"verified":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Runs by whether the fix was checked as gone (verify_state)"}},"required":["harness","models","runs","running","in_review","status","failed","platform_failed","merged","prs","first_try","first_try_merge","cost","time","verifier","verified"]}},"surfaces":{"type":"array","items":{"type":"object","properties":{"source_kind":{"type":"string"},"runs":{"type":"number"}},"required":["source_kind","runs"]}},"first_pass":{"type":"object","properties":{"reviewed":{"type":"number","description":"Runs whose first review completed"},"clean":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of those, how many had no blocking issue: right on the first try"},"checks_clean":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of runs the repo's own checks ran on, how many passed before any fix"},"by_cause":{"type":"object","propertyNames":{"type":"string","enum":["agent","context","setup","unknown"]},"additionalProperties":{"type":"object","properties":{"issues":{"type":"number"},"runs":{"type":"number"}},"required":["issues","runs"]},"required":["agent","context","setup","unknown"],"description":"Blocking issues by whose miss: agent, context, setup (unknown: reviewed before causes were recorded)"},"ours_runs":{"type":"number","description":"Reviewed runs whose blocking issues were all on our side (context or setup): the agent missed nothing it was told"},"gaps":{"type":"array","items":{"type":"object","properties":{"cause":{"type":"string","enum":["agent","context","setup","unknown"]},"category":{"type":"string","description":"brief, brand, broken, scope, safety or other"},"issues":{"type":"number"},"runs":{"type":"number"},"notes":{"type":"array","items":{"type":"string"},"description":"The reviewer's own sentences about what was missing, commonest first"},"what_to_do":{"type":"string"},"open_with":{"type":"object","properties":{"first_review_cause":{"type":"string","enum":["agent","context","setup","unknown"]}},"required":["first_review_cause"],"description":"list_coding_runs filter that opens these runs"}},"required":["cause","category","issues","runs","notes","what_to_do","open_with"]}}},"required":["reviewed","clean","checks_clean","by_cause","ours_runs","gaps"]},"first_pass_previous":{"type":"object","properties":{"reviewed":{"type":"number","description":"Runs whose first review completed"},"clean":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of those, how many had no blocking issue: right on the first try"},"checks_clean":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]}},"required":["n","hits","rate","low","high"],"description":"Of runs the repo's own checks ran on, how many passed before any fix"},"by_cause":{"type":"object","propertyNames":{"type":"string","enum":["agent","context","setup","unknown"]},"additionalProperties":{"type":"object","properties":{"issues":{"type":"number"},"runs":{"type":"number"}},"required":["issues","runs"]},"required":["agent","context","setup","unknown"],"description":"Blocking issues by whose miss: agent, context, setup (unknown: reviewed before causes were recorded)"},"ours_runs":{"type":"number","description":"Reviewed runs whose blocking issues were all on our side (context or setup): the agent missed nothing it was told"},"gaps":{"type":"array","items":{"type":"object","properties":{"cause":{"type":"string","enum":["agent","context","setup","unknown"]},"category":{"type":"string","description":"brief, brand, broken, scope, safety or other"},"issues":{"type":"number"},"runs":{"type":"number"},"notes":{"type":"array","items":{"type":"string"},"description":"The reviewer's own sentences about what was missing, commonest first"},"what_to_do":{"type":"string"},"open_with":{"type":"object","properties":{"first_review_cause":{"type":"string","enum":["agent","context","setup","unknown"]}},"required":["first_review_cause"],"description":"list_coding_runs filter that opens these runs"}},"required":["cause","category","issues","runs","notes","what_to_do","open_with"]}}},"required":["reviewed","clean","checks_clean","by_cause","ours_runs","gaps"],"description":"The same reading for the period before"},"failures":{"type":"array","items":{"type":"object","properties":{"error_code":{"type":"string"},"fault":{"type":"string","enum":["platform","agent"]},"count":{"type":"number"},"previous":{"type":"number"},"details":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string"},"count":{"type":"number"}},"required":["detail","count"]}}},"required":["error_code","fault","count","previous","details"]}},"resilience":{"type":"object","properties":{"n":{"type":"number"},"hits":{"type":"number"},"rate":{"type":["number","null"]},"low":{"type":["number","null"]},"high":{"type":["number","null"]},"over_bar":{"type":"boolean"}},"required":["n","hits","rate","low","high","over_bar"],"description":"Runs lost to timeouts, memory, crashes; over 3% asks for checkpoint/resume"},"weeks":{"type":"array","items":{"type":"object","properties":{"week":{"type":"string"},"harness":{"type":"string"},"runs":{"type":"number"},"failed_rate":{"type":["number","null"]},"merged_rate":{"type":["number","null"]},"first_try_merge_rate":{"type":["number","null"]},"cost_p50_usd":{"type":["number","null"]},"agent_p50_ms":{"type":["number","null"]}},"required":["week","harness","runs","failed_rate","merged_rate","first_try_merge_rate","cost_p50_usd","agent_p50_ms"]}},"moved":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["failed","merged","first_try_merge","first_pass_clean"]},"direction":{"type":"string","enum":["up","down"]}},"required":["measure","direction"]},"description":"Headline changes beyond chance (20+ runs each side, intervals apart); empty means nothing moved"},"capped":{"type":"boolean"}},"required":["scope","days","since","until","customer_only","harness","all","previous","by_harness","surfaces","first_pass","first_pass_previous","failures","resilience","weeks","moved","capped"]}},{"name":"list_coding_runs","description":"Staff only. The coding-agent runs behind get_coding_telemetry's numbers, newest first, one row each: the organization, harness, model, status and phase, error code and detail, where it was started (source_kind), total time, cost, the verifier's outcome, whether the fix was checked as gone, the pull request and whether it merged, and the run it follows up. Each row carries first_review_causes (whose miss the first review found) and checks_outcome. Filter by org_id, harness, status, error_code, verifier_outcome, first_review_cause (agent / context / setup / unknown: open the runs behind a gap), pr (merged / closed / open / none), days (default 30); limit default 50. `letterstory admin letterpress runs`. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"org_id":{"description":"Only this organization's runs. Default: every organization.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"customer_only":{"description":"Across organizations: leave out test and demo organizations (default true), as customers' numbers.","type":"boolean"},"harness":{"description":"Only runs on this coding harness: letterpress-1, claude-code or codex. Default: every harness.","type":"string","minLength":1,"maxLength":40},"status":{"description":"Only runs that ended this way (running: not finished yet).","type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"error_code":{"description":"Only failed runs with this error code.","type":"string","minLength":1,"maxLength":40},"verifier_outcome":{"description":"Only runs the verifier ended this way.","type":"string","enum":["passed","fixed","open_issues","skipped","error"]},"first_review_cause":{"description":"Only runs whose first review (before any fix round) raised a blocking issue of this cause: agent (had what it needed, missed it), context (not told, or told unclearly), setup (the run could not be done as set up), unknown (reviewed before causes were recorded).","type":"string","enum":["agent","context","setup","unknown"]},"pr":{"description":"Only runs whose pull request merged, closed unmerged, is still open, or that opened none.","type":"string","enum":["merged","closed","open","none"]},"days":{"description":"Runs started in the last this many days (default 30).","type":"integer","minimum":1,"maximum":90},"limit":{"description":"At most this many runs, newest first (default 50).","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"org_id":{"type":"string"},"org_name":{"type":["string","null"]},"harness":{"type":"string"},"model":{"type":"string"},"status":{"type":"string"},"phase":{"type":"string"},"error_code":{"type":["string","null"]},"error_detail":{"type":["string","null"]},"source_kind":{"type":["string","null"]},"started_at":{"type":"string"},"total_ms":{"type":["number","null"]},"cost_usd":{"type":"number"},"verifier_outcome":{"type":["string","null"]},"first_review_causes":{"type":"array","items":{"type":"string"},"description":"Whose miss the first review's blocking issues were"},"checks_outcome":{"description":"The repo's own checks on the first pass: passed, fixed, open, skipped","type":["string","null"]},"verify_state":{"type":["string","null"]},"pr_url":{"type":["string","null"]},"pr":{"type":"string","enum":["merged","closed","open","none"]},"follows":{"type":["string","null"]}},"required":["id","org_id","org_name","harness","model","status","phase","error_code","error_detail","source_kind","started_at","total_ms","cost_usd","verifier_outcome","first_review_causes","checks_outcome","verify_state","pr_url","pr","follows"]}}},"required":["runs"]}},{"name":"list_blockers","description":"Staff only. What Letter withheld because it cannot do it here (docs/architecture/preflight.md): a suggestion is not shown, and a start is refused before a sandbox, when a sandbox ended 'CANNOT DO: <code>' or a check found its premise false. Per code (wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page): how many are withheld and in how many organizations, who owns the fix, and what to do. Per thing blocked (a recipe, a Fix observation key): a recipe blocked in 3 or more organizations for one reason is `recipe_bug` (its conditions are wrong). `cleared` counts what came back in the period (the facts moved) and what staff cleared ('Show anyway': a possible wrong withhold). `recent` lists the ones that hold now, with the organization, site and reason. `off` is the codes switched off in the engine settings (`letterstory admin letterpress set preflight.off <codes>`). clear_blocker undoes one; `letterstory admin letterpress blocked`. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"org_id":{"description":"Only this organization's runs. Default: every organization.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"customer_only":{"description":"Across organizations: leave out test and demo organizations (default true), as customers' numbers.","type":"boolean"},"days":{"description":"The period cleared blockers are counted over (default 7).","type":"integer","minimum":1,"maximum":90},"limit":{"description":"At most this many blockers in `recent` (default 50).","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"days":{"type":"number"},"since":{"type":"string"},"active":{"type":"number","description":"Suggestions withheld right now"},"orgs":{"type":"number","description":"In how many organizations"},"cleared":{"type":"object","properties":{"facts":{"type":"number"},"time":{"type":"number"},"staff":{"type":"number"}},"required":["facts","time","staff"],"description":"In the period: lifted because the facts moved (the suggestion came back), by the 14-day limit, or by staff 'Show anyway' (a possible wrong withhold)"},"by_code":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["wrong_repo","not_in_repo","already_done","no_input","not_applicable","needs_info","not_a_page"]},"owner":{"type":"string","enum":["account","system","recipe"]},"active":{"type":"number"},"orgs":{"type":"number"},"cleared":{"type":"number"},"what_to_do":{"type":"string"}},"required":["code","owner","active","orgs","cleared","what_to_do"]}},"by_subject":{"type":"array","items":{"type":"object","properties":{"subject":{"type":"string","description":"A recipe by `recipe:<id>`, a Fix observation by `obs:<key>`"},"label":{"type":"string"},"code":{"type":"string","enum":["wrong_repo","not_in_repo","already_done","no_input","not_applicable","needs_info","not_a_page"]},"active":{"type":"number"},"orgs":{"type":"number"},"recipe_bug":{"type":"boolean","description":"A recipe blocked in 3 or more organizations for one reason: its conditions are wrong"},"what_to_do":{"type":"string"}},"required":["subject","label","code","active","orgs","recipe_bug","what_to_do"]}},"recent":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"org_name":{"type":["string","null"]},"host":{"type":"string"},"subject":{"type":"string"},"code":{"type":"string","enum":["wrong_repo","not_in_repo","already_done","no_input","not_applicable","needs_info","not_a_page"]},"owner":{"type":"string","enum":["account","system","recipe"]},"say":{"type":"string"},"source":{"type":"string","enum":["door","sandbox","check"]},"since":{"type":"string"},"run_id":{"type":["string","null"]}},"required":["org_id","org_name","host","subject","code","owner","say","source","since","run_id"]}},"off":{"type":"array","items":{"type":"string"},"description":"Codes staff switched off (engine settings preflight.off): not enforced"}},"required":["days","since","active","orgs","cleared","by_code","by_subject","recent","off"]}},{"name":"clear_blocker","description":"Staff only. 'Show anyway': clear one blocker (an organization, a site, and what was blocked as list_blockers shows it: `recipe:<id>` or `obs:<id>`), so the suggestion is offered and the start is allowed again. It is counted in list_blockers' `cleared.staff`, the evidence of a wrong withhold. `cleared` is false when none was holding. `letterstory admin letterpress unblock`. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The organization the blocker is on."},"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site it is on, as list_blockers shows it."},"subject":{"type":"string","minLength":1,"maxLength":300,"description":"What was blocked, as list_blockers shows it: `recipe:<id>` or `obs:<id>`."}},"required":["org_id","host","subject"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cleared":{"type":"boolean","description":"False when there was no such blocker holding"}},"required":["cleared"]}},{"name":"list_letterpress_reports","description":"Staff only. The coding agent's analyst (docs/architecture/coding-telemetry.md), newest first: the daily brief (what moved since yesterday; posted to Slack only when something did) and the weekly report (the week read in full, Mondays), each with its summary, report, observations — each observation with its tone (regression, watch, improvement, holding), its evidence and ONE action ready to take (a Linear ticket, an eval case, a Lemma monitor, or watch) — and the hypotheses it is tracking week to week. `kind` ask lists staff questions and their answers. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"kind":{"description":"reports (default): daily briefs and weekly reports; daily, weekly or ask for one kind","type":"string","enum":["daily","weekly","ask","reports"]},"limit":{"description":"Default 20","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reports":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["daily","weekly","run","ask"]},"status":{"type":"string","enum":["running","done","failed"]},"question":{"type":["string","null"]},"model":{"type":["string","null"]},"summary":{"type":["string","null"]},"report":{"type":["string","null"]},"observations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Short stable id; reuse an earlier observation's id when it is the same thing"},"tone":{"type":"string","enum":["regression","watch","improvement","holding"],"description":"regression: worse beyond chance; watch: moving, not yet beyond chance; improvement: better beyond chance; holding: an earlier one still true"},"statement":{"type":"string","description":"One sentence, plain words"},"evidence":{"type":"string","description":"The numbers behind it, quoted from the tools, with n and the interval"},"action":{"type":"object","properties":{"kind":{"type":"string","enum":["linear","eval_case","lemma_monitor","watch"],"description":"linear: a ticket for an engineer to fix; eval_case: a harness-eval task that would catch it; lemma_monitor: a behaviour to watch in traces; watch: nothing to do yet"},"title":{"type":"string","description":"The ticket title, the eval task's name, the monitor's name, or what to watch for"},"body":{"type":"string","description":"Ready to paste: the ticket's description with the run ids, the eval task's instructions and its check, or the monitor's scenario and what correct looks like"}},"required":["kind","title","body"]}},"required":["id","tone","statement","evidence","action"]}},"hypotheses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable short id; reuse last report's id when re-testing a hypothesis"},"statement":{"type":"string"},"grade":{"type":"string","enum":["strong","suggestive","speculative"]},"status":{"type":"string","enum":["new","holding","weakening","refuted","confirmed"]},"evidence":{"type":"string","description":"The numbers behind it, with n and ranges, and a counter-example"},"test":{"type":"string","description":"The experiment or next read that would settle it"}},"required":["id","statement","grade","status","evidence","test"]}},"steps":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"input":{},"ms":{"type":"number"},"error":{"type":["string","null"]}},"required":["tool","input","ms","error"]}},"error":{"type":["string","null"]},"posted_at":{"type":["string","null"]},"created_at":{"type":"string"},"finished_at":{"type":["string","null"]}},"required":["id","kind","status","question","model","summary","report","observations","hypotheses","steps","error","posted_at","created_at","finished_at"]}}},"required":["reports"]}},{"name":"get_letterpress_report","description":"Staff only. One of the coding agent's analyst reports (list_letterpress_reports) in full, with every tool call behind it. Refused unless the key was created by Letter staff. Requires capability repo:read.","capability":"repo:read","inputSchema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"report":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["daily","weekly","run","ask"]},"status":{"type":"string","enum":["running","done","failed"]},"question":{"type":["string","null"]},"model":{"type":["string","null"]},"summary":{"type":["string","null"]},"report":{"type":["string","null"]},"observations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Short stable id; reuse an earlier observation's id when it is the same thing"},"tone":{"type":"string","enum":["regression","watch","improvement","holding"],"description":"regression: worse beyond chance; watch: moving, not yet beyond chance; improvement: better beyond chance; holding: an earlier one still true"},"statement":{"type":"string","description":"One sentence, plain words"},"evidence":{"type":"string","description":"The numbers behind it, quoted from the tools, with n and the interval"},"action":{"type":"object","properties":{"kind":{"type":"string","enum":["linear","eval_case","lemma_monitor","watch"],"description":"linear: a ticket for an engineer to fix; eval_case: a harness-eval task that would catch it; lemma_monitor: a behaviour to watch in traces; watch: nothing to do yet"},"title":{"type":"string","description":"The ticket title, the eval task's name, the monitor's name, or what to watch for"},"body":{"type":"string","description":"Ready to paste: the ticket's description with the run ids, the eval task's instructions and its check, or the monitor's scenario and what correct looks like"}},"required":["kind","title","body"]}},"required":["id","tone","statement","evidence","action"]}},"hypotheses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable short id; reuse last report's id when re-testing a hypothesis"},"statement":{"type":"string"},"grade":{"type":"string","enum":["strong","suggestive","speculative"]},"status":{"type":"string","enum":["new","holding","weakening","refuted","confirmed"]},"evidence":{"type":"string","description":"The numbers behind it, with n and ranges, and a counter-example"},"test":{"type":"string","description":"The experiment or next read that would settle it"}},"required":["id","statement","grade","status","evidence","test"]}},"steps":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"input":{},"ms":{"type":"number"},"error":{"type":["string","null"]}},"required":["tool","input","ms","error"]}},"error":{"type":["string","null"]},"posted_at":{"type":["string","null"]},"created_at":{"type":"string"},"finished_at":{"type":["string","null"]}},"required":["id","kind","status","question","model","summary","report","observations","hypotheses","steps","error","posted_at","created_at","finished_at"]}},"required":["report"]}},{"name":"cancel_repo_change","description":"Cancel a running repo change: stops Claude Code, deletes the sandbox and opens no pull request. Idempotent on a run that already finished. Cancel only a change you started: one another key or person started is refused unless `force: true`, which is for when the person you work for asked for that change to stop. To avoid duplicating a change, follow up on it instead (revise_repo_change). Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."},"force":{"description":"Cancel it even though another key or person started it. Only when the person you're working for asked for that change to stop.","type":"boolean"}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"publish_repo_change","description":"Make PR: open the pull request for a repo change started with preview: true, once it waits in phase `review` (its preview at run.preview.url has been looked at). Commits the same validated change the preview serves; it stays open for review unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Repeating it is harmless; on any other run it fails with NOT_IN_REVIEW. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"merge_repo_change","description":"MERGE WHEN READY — what Build's Merge (beside Archive, on a build whose PR is open) does: ask for a repo change's open pull request to be squash-merged once it's ready, through the same steps as auto-merge, whether or not the org has it on. A branch behind its base is brought up to date; it waits for the repo's checks; checks that fail are fixed on the same PR by a follow-up handed what failed (it reproduces each failure from the repo's own workflow first, and fixes only what this change broke); a conflict is made again on the current code as a new PR that takes its place; it keeps working until it merges, up to 3 fixes per PR and 3 remakes per change (a fix or remake that failed is tried again), then leaves it for a person (gave_up); a branch rule (a required review) is never worked around. The first step is taken in the call, so a ready PR merges at once; otherwise run.autoMerge says what it's waiting for (state, detail) and the follow-repo-changes cron carries on every 10 minutes. Who asked is recorded on the run and the merge is commented on the PR, then followed through like any merge (the Slack thread hears; a fix is checked on the site). Asking again restarts one that gave up; a PR already merged is returned as it is. PR_CLOSED: closed unmerged; NO_PULL_REQUEST: none yet. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"retry_repo_change","description":"TRY AGAIN — what Retry on a failed build's page does: start a failed repo change again as a fresh run with the same instructions, repo, branch, files and options (preview or PR, harness, Letter's rules, site brand check), for a run that failed before it opened a pull request (a sandbox that couldn't start, a provisioning error). The new run's rebuildsRunId names the failed one, and a build that started the failed run follows the new run in its place (list_growth_builds, get_repo_change_activity). It goes through the same gates as start_repo_change (the spend cap or free tokens, one open change per ask, the cap on open runs) and spends model credit like it. NOT_FAILED: the run is still going or ended another way; ALREADY_RETRIED: it was tried again already (retry that run if it failed too). Poll get_repo_change. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"revise_repo_change","description":"Ask for more on a repo change, before or after its PR: what the review panel's Ask box and a Build conversation's follow-ups do. On a preview waiting in `review` (no PR yet), it starts a new run that begins from the previewed change in a fresh sandbox, makes the follow-up on top, and once its own preview is up takes over the same preview link (the old run ends, its panel reloads into the new one); poll it with get_repo_change and publish_repo_change it when it looks right. On a change whose pull request is open (the run that opened it, or an earlier follow-up), it starts a run that checks out the PR's branch and pushes the follow-up onto the same PR as one more commit (continuesRunId); no second PR opens. Say what to change in `instructions`, give exact copy in `copy_edits` (each a page, its text as it reads now and what it should say, made word for word at the text's source: what the preview panel sends when a reviewer double-clicks text and types over it), or both. A merged or closed PR can't take more (PR_CLOSED): start a new change. A run still being built takes steering instead (send_repo_change_message). One follow-up at a time per preview or PR (REVISION_IN_PROGRESS); spends model credit like start_repo_change. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."},"instructions":{"description":"What else to change, in plain words, as a reviewer would say it after looking at the preview or the PR — e.g. 'Make the new pricing note bold and move it above the button.' Built on top of the change already made. Needed unless copy_edits says it all.","type":"string","minLength":3,"maxLength":4000},"copy_edits":{"description":"Exact copy changes: each a piece of text on a page and what it should say instead, made word for word where that text comes from in the code. What the preview panel sends when a reviewer double-clicks text and types over it; with instructions, both are made.","minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"path":{"type":"string","minLength":1,"maxLength":2000,"pattern":"^\\/","description":"The page it's on, as a path on the site: `/pricing`."},"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"emphasis":{"description":"Bold and italic, when the text has any: `before` and `after` with bold words as **…** and italic as *…* (a literal * as \\*). Make the emphasis match `after`; what `before` already marks is there now.","type":"object","properties":{"before":{"type":"string","maxLength":4200},"after":{"type":"string","maxLength":4200}},"required":["before","after"]},"font":{"description":"A font the text is set in instead, by family name: { before: 'DM Sans', after: 'Ibarra Real Nova' }.","type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":200},"after":{"type":"string","minLength":1,"maxLength":200}},"required":["after"]},"size":{"description":"A size the text is set at instead, in CSS points (1pt is 4/3px, so 12pt is 16px): { before: 12, after: 18 }.","type":"object","properties":{"before":{"type":"number","exclusiveMinimum":0,"maximum":400},"after":{"type":"number","exclusiveMinimum":0,"maximum":400}},"required":["after"]}},"required":["path","before","after"]}}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"expect":{"description":"get_repo_change on a running change: how long its preview (or PR) usually takes, from the last runs, and how long this one has run. The only wait to tell a person; null when there is nothing to wait for or too few runs to say.","type":["string","null"]}},"required":["run","message"]}},{"name":"describe_automation_options","description":"What an automation can be made of in this organization: each trigger (where events come from: a competitor publishes a page, Visitors de-anonymizes a person on your site, Letter finds a new issue to fix on your site, Letter suggests something to build on your site, your own system sends an event) with the facts its events carry, an example instruction (which events it acts on) and an example prompt (what Letterpress then does), and whether its events can come here yet; the templates to start from (Slack's: post new issues, fix new issues, recommended actions, relevant competitor pages, ICP visitors); and the custom event names already sent. Read this before create_event_automation. Requires capability automation:read.","capability":"automation:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"triggers":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"label":{"type":"string"},"short":{"type":"string"},"describe":{"type":"string"},"facts":{"type":"array","items":{"type":"string"},"description":"What its events carry, for an instruction to be written against."},"needs":{"type":"string"},"example":{"type":"string","description":"An instruction (which events it acts on) someone might write for it."},"then":{"type":"string","description":"A prompt (what Letterpress then does) someone might write for it."},"person":{"type":"boolean","description":"Its events can name a person (an email to enroll)."},"every_hours":{"description":"Read at most this often, in hours; unset: every hourly pass.","type":"number"},"ready":{"type":"boolean","description":"Its events can come in this organization now."},"status":{"type":"string"}},"required":["kind","label","short","describe","facts","needs","example","then","person","ready","status"]}},"templates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Pass as create_event_automation `template`."},"name":{"type":"string"},"describe":{"type":"string"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"competitors":{"type":"array","items":{"type":"string"}},"hosts":{"type":"array","items":{"type":"string"}},"hot_leads_only":{"type":"boolean"},"event":{"type":"string"},"per_day":{"type":"number"}}},"instruction":{"type":"string"},"prompt":{"type":"string","description":"Empty when it only posts."},"slack":{"type":"boolean","description":"It posts each event it acts on to a Slack channel (slack_channel)."},"ready":{"type":"boolean","description":"Its trigger's events can come in this organization now."}},"required":["id","name","describe","trigger","trigger_params","instruction","prompt","slack","ready"]},"description":"Automations to start from: Slack's (new issues, recommended actions, competitor pages, ICP visitors)."},"custom_events":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"last_at":{"type":"string"}},"required":["name","last_at"]},"description":"Event names your systems have sent, newest first."}},"required":["triggers","templates","custom_events"]}},{"name":"list_event_automations","description":"The organization's automations, oldest first: each one's trigger and params, its instruction (Filter Logic: which events it acts on), its prompt (Then: what Letterpress does with each), the Slack channel it posts each event to, the template it came from, on or off, a one-line summary, why it isn't acting if it isn't (note), and what it did with the last 7 days' events. Not a letterbase's writing automations: those are list_automations. Requires capability automation:read.","capability":"automation:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"competitors":{"type":"array","items":{"type":"string"}},"hosts":{"type":"array","items":{"type":"string"}},"hot_leads_only":{"type":"boolean"},"event":{"type":"string"},"per_day":{"type":"number"}}},"instruction":{"type":"string","description":"Filter Logic: which events it acts on, in the org's words."},"prompt":{"type":"string","description":"Then: what Letterpress does with each event the instruction lets through; empty when it only posts."},"slack_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons; null posts nothing."},"template":{"description":"The template it was made from (describe_automation_options), if any.","type":["string","null"]},"enabled":{"type":"boolean"},"counts_from":{"type":"string","description":"Events from before this are never read."},"checked_at":{"description":"When it last looked for events.","type":["string","null"]},"note":{"description":"Why it isn't acting right now; null while it runs.","type":["string","null"]},"summary":{"type":"string","description":"\"When … → …\": the trigger, then the prompt's first line."},"created_by":{"description":"The admin it acts as: whoever last saved it.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"recent":{"type":"object","properties":{"acting":{"type":"number"},"acted":{"type":"number"},"proposed":{"type":"number"},"skipped":{"type":"number"},"failed":{"type":"number"}},"required":["acting","acted","proposed","skipped","failed"],"description":"What it did with the events of the last 7 days (acting: Letterpress is on it)."}},"required":["id","name","trigger","trigger_params","instruction","prompt","slack_channel","template","enabled","counts_from","checked_at","note","summary","created_by","created_at","updated_at","recent"]}}},"required":["automations"]}},{"name":"get_event_automation","description":"One automation, as list_event_automations shows it. Its events and what it did are list_automation_runs. Requires capability automation:read.","capability":"automation:read","inputSchema":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The automation (list_event_automations)."}},"required":["automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"competitors":{"type":"array","items":{"type":"string"}},"hosts":{"type":"array","items":{"type":"string"}},"hot_leads_only":{"type":"boolean"},"event":{"type":"string"},"per_day":{"type":"number"}}},"instruction":{"type":"string","description":"Filter Logic: which events it acts on, in the org's words."},"prompt":{"type":"string","description":"Then: what Letterpress does with each event the instruction lets through; empty when it only posts."},"slack_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons; null posts nothing."},"template":{"description":"The template it was made from (describe_automation_options), if any.","type":["string","null"]},"enabled":{"type":"boolean"},"counts_from":{"type":"string","description":"Events from before this are never read."},"checked_at":{"description":"When it last looked for events.","type":["string","null"]},"note":{"description":"Why it isn't acting right now; null while it runs.","type":["string","null"]},"summary":{"type":"string","description":"\"When … → …\": the trigger, then the prompt's first line."},"created_by":{"description":"The admin it acts as: whoever last saved it.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"recent":{"type":"object","properties":{"acting":{"type":"number"},"acted":{"type":"number"},"proposed":{"type":"number"},"skipped":{"type":"number"},"failed":{"type":"number"}},"required":["acting","acted","proposed","skipped","failed"],"description":"What it did with the events of the last 7 days (acting: Letterpress is on it)."}},"required":["id","name","trigger","trigger_params","instruction","prompt","slack_channel","template","enabled","counts_from","checked_at","note","summary","created_by","created_at","updated_at","recent"]}},"required":["automation"]}},{"name":"create_event_automation","description":"Make an automation: when <trigger> happens, Letter reads the event against your instruction (and the company) and, when it fits, Letterpress does what your prompt says with it, on its own, saying why for every event. trigger: competitor_page (trigger_params.kinds, .competitors), visitor_identified (a person Visitors de-anonymized on one of your Visitors sites for the first time, the people visitors_get_people lists, with their name and email; needs identification on; .hosts, .hot_leads_only), issue_found (an issue Letter finds on one of your sites for the first time, what Fix lists, with the observation ids that fix it; .hosts), build_suggestion (something Build suggests your site should have, the first time it's suggested; .hosts) or custom (.event, the name your system sends with send_automation_event). Any trigger takes .per_day (act on at most that many a day). template: start from one of describe_automation_options' templates, which fills whatever you leave out. slack_channel: a channel id from list_slack_fix_targets (joined and linked on save) where each event it acts on is posted with its buttons (Fix it on an issue, Build it on a suggestion, a similar page or post on a competitor's page; an identified visitor with who they are); with no prompt, posting is all it does. prompt: what to do with each event, in your words (\"Write our own take in the Archil blog\", \"Enroll them in Warm leads\", \"Fix it and open the pull request\"): Letterpress carries it out as one Build turn, with a tool when one does it (run_collection, enroll_in_sequence, fix_site_observations) or a Build sandbox when the site's code must change (built, then the pull request, never merged on its own). It starts from now: events from before it was made are never read; try_event_automation shows what it would have done with recent ones. It acts as the admin who saves it: a key with no admin behind it is refused. Read describe_automation_options first. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"template":{"type":"string","enum":["post_new_issues","fix_new_issues","recommended_actions","competitor_pages","icp_visitors"],"description":"Start from a template (describe_automation_options `templates`): its name, trigger, params, filter and prompt fill whatever you leave out, and the automation remembers it came from it."},"name":{"description":"What it's called in the list. Required without a template.","type":"string","minLength":1,"maxLength":120},"trigger":{"description":"Where its events come from (describe_automation_options). Required without a template.","type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"description":"competitor_page: only these kinds of page (post, landing). Omit for both.","minItems":1,"type":"array","items":{"type":"string","enum":["post","landing"]}},"competitors":{"description":"competitor_page: only these competitors' hosts. Omit for every competitor watched.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hosts":{"description":"visitor_identified: only people identified on these sites. issue_found: only issues on these sites of yours. Omit for every site.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hot_leads_only":{"description":"visitor_identified: only people the provider marks as hot leads.","type":"boolean"},"event":{"description":"custom: the event's name, as your system sends it (send_automation_event `event`). Required.","type":"string","pattern":"^[a-z0-9][a-z0-9_.:-]{0,63}$"},"per_day":{"description":"Any trigger: act on at most this many events in 24 hours; the rest wait (Recommended actions: 3). Omit for no cap.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false,"description":"What narrows the trigger; each key says which trigger reads it."},"instruction":{"type":"string","maxLength":2000,"description":"Filter Logic: which events it acts on, in your words (when to act, when to skip). Empty acts on every event."},"prompt":{"type":"string","maxLength":2000,"description":"Then: what Letterpress does with each event the filter lets through, in your words (\"Write our own take in the Archil blog\", \"Enroll them in Warm leads\", \"Fix it and open the pull request\"). Letterpress carries it out on its own, as you: with the tools when one does it (a post, an enrollment, a fix), or a Build sandbox when the site's code must change (a preview and a pull request, never merged on its own). Empty when it only posts each event in Slack (slack_channel)."},"slack_channel":{"anyOf":[{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons (Fix it, Build it, Not this …): a channel id from list_slack_fix_targets, which Letter joins and links to the organization on save. With a prompt too, what Letterpress did follows in the message's thread. null posts nothing."},"enabled":{"description":"Defaults to on.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"competitors":{"type":"array","items":{"type":"string"}},"hosts":{"type":"array","items":{"type":"string"}},"hot_leads_only":{"type":"boolean"},"event":{"type":"string"},"per_day":{"type":"number"}}},"instruction":{"type":"string","description":"Filter Logic: which events it acts on, in the org's words."},"prompt":{"type":"string","description":"Then: what Letterpress does with each event the instruction lets through; empty when it only posts."},"slack_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons; null posts nothing."},"template":{"description":"The template it was made from (describe_automation_options), if any.","type":["string","null"]},"enabled":{"type":"boolean"},"counts_from":{"type":"string","description":"Events from before this are never read."},"checked_at":{"description":"When it last looked for events.","type":["string","null"]},"note":{"description":"Why it isn't acting right now; null while it runs.","type":["string","null"]},"summary":{"type":"string","description":"\"When … → …\": the trigger, then the prompt's first line."},"created_by":{"description":"The admin it acts as: whoever last saved it.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"recent":{"type":"object","properties":{"acting":{"type":"number"},"acted":{"type":"number"},"proposed":{"type":"number"},"skipped":{"type":"number"},"failed":{"type":"number"}},"required":["acting","acted","proposed","skipped","failed"],"description":"What it did with the events of the last 7 days (acting: Letterpress is on it)."}},"required":["id","name","trigger","trigger_params","instruction","prompt","slack_channel","template","enabled","counts_from","checked_at","note","summary","created_by","created_at","updated_at","recent"]}},"required":["automation"]}},{"name":"update_event_automation","description":"Change an automation: its name, instruction, prompt, its Slack channel (slack_channel; null posts nothing), the trigger's params (replaced whole), on or off. Its trigger stays: make a new one for a different trigger. Turned on again, it starts from now. Saving it makes it act as you (an admin), and clears why it was waiting. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The automation (list_event_automations)."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"What it's called in the list."},"trigger_params":{"description":"Replaces the trigger's params.","type":"object","properties":{"kinds":{"description":"competitor_page: only these kinds of page (post, landing). Omit for both.","minItems":1,"type":"array","items":{"type":"string","enum":["post","landing"]}},"competitors":{"description":"competitor_page: only these competitors' hosts. Omit for every competitor watched.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hosts":{"description":"visitor_identified: only people identified on these sites. issue_found: only issues on these sites of yours. Omit for every site.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hot_leads_only":{"description":"visitor_identified: only people the provider marks as hot leads.","type":"boolean"},"event":{"description":"custom: the event's name, as your system sends it (send_automation_event `event`). Required.","type":"string","pattern":"^[a-z0-9][a-z0-9_.:-]{0,63}$"},"per_day":{"description":"Any trigger: act on at most this many events in 24 hours; the rest wait (Recommended actions: 3). Omit for no cap.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false},"instruction":{"type":"string","maxLength":2000,"description":"Filter Logic: which events it acts on, in your words (when to act, when to skip). Empty acts on every event."},"prompt":{"type":"string","maxLength":2000,"description":"Then: what Letterpress does with each event the filter lets through, in your words (\"Write our own take in the Archil blog\", \"Enroll them in Warm leads\", \"Fix it and open the pull request\"). Letterpress carries it out on its own, as you: with the tools when one does it (a post, an enrollment, a fix), or a Build sandbox when the site's code must change (a preview and a pull request, never merged on its own). Empty when it only posts each event in Slack (slack_channel)."},"slack_channel":{"anyOf":[{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons (Fix it, Build it, Not this …): a channel id from list_slack_fix_targets, which Letter joins and links to the organization on save. With a prompt too, what Letterpress did follows in the message's thread. null posts nothing."},"enabled":{"description":"Off stops it; turned on again, it starts from now (events while it was off are never read).","type":"boolean"}},"required":["automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automation":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"trigger_params":{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"competitors":{"type":"array","items":{"type":"string"}},"hosts":{"type":"array","items":{"type":"string"}},"hot_leads_only":{"type":"boolean"},"event":{"type":"string"},"per_day":{"type":"number"}}},"instruction":{"type":"string","description":"Filter Logic: which events it acts on, in the org's words."},"prompt":{"type":"string","description":"Then: what Letterpress does with each event the instruction lets through; empty when it only posts."},"slack_channel":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}},"required":["id","name"]},{"type":"null"}],"description":"Where it posts each event it acts on, with the event's buttons; null posts nothing."},"template":{"description":"The template it was made from (describe_automation_options), if any.","type":["string","null"]},"enabled":{"type":"boolean"},"counts_from":{"type":"string","description":"Events from before this are never read."},"checked_at":{"description":"When it last looked for events.","type":["string","null"]},"note":{"description":"Why it isn't acting right now; null while it runs.","type":["string","null"]},"summary":{"type":"string","description":"\"When … → …\": the trigger, then the prompt's first line."},"created_by":{"description":"The admin it acts as: whoever last saved it.","type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"recent":{"type":"object","properties":{"acting":{"type":"number"},"acted":{"type":"number"},"proposed":{"type":"number"},"skipped":{"type":"number"},"failed":{"type":"number"}},"required":["acting","acted","proposed","skipped","failed"],"description":"What it did with the events of the last 7 days (acting: Letterpress is on it)."}},"required":["id","name","trigger","trigger_params","instruction","prompt","slack_channel","template","enabled","counts_from","checked_at","note","summary","created_by","created_at","updated_at","recent"]}},"required":["automation"]}},{"name":"delete_event_automation","description":"Remove an automation and its runs. What Letterpress made for it stays: posts, enrollments, pull requests. To stop it for a while instead, update_event_automation enabled: false. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The automation (list_event_automations)."}},"required":["automation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["deleted","id"]}},{"name":"try_event_automation","description":"What an automation would do with the latest events (up to 5, from the last 14 days; for issue_found, the issues open now), saved (automation_id) or a draft (trigger, its params, instruction and prompt): each event read as it would be, act or skip, why, and what Letterpress would do with it in a line. Acts on nothing and writes nothing; spends a model read per event. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"automation_id":{"description":"A saved automation to try. Or pass a draft: trigger, its params, instruction and prompt.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"],"description":"Where its events come from (describe_automation_options)."},"trigger_params":{"type":"object","properties":{"kinds":{"description":"competitor_page: only these kinds of page (post, landing). Omit for both.","minItems":1,"type":"array","items":{"type":"string","enum":["post","landing"]}},"competitors":{"description":"competitor_page: only these competitors' hosts. Omit for every competitor watched.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hosts":{"description":"visitor_identified: only people identified on these sites. issue_found: only issues on these sites of yours. Omit for every site.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}},"hot_leads_only":{"description":"visitor_identified: only people the provider marks as hot leads.","type":"boolean"},"event":{"description":"custom: the event's name, as your system sends it (send_automation_event `event`). Required.","type":"string","pattern":"^[a-z0-9][a-z0-9_.:-]{0,63}$"},"per_day":{"description":"Any trigger: act on at most this many events in 24 hours; the rest wait (Recommended actions: 3). Omit for no cap.","type":"integer","minimum":1,"maximum":50}},"additionalProperties":false,"description":"What narrows the trigger; each key says which trigger reads it."},"instruction":{"type":"string","maxLength":2000,"description":"Filter Logic: which events it acts on, in your words (when to act, when to skip). Empty acts on every event."},"prompt":{"type":"string","maxLength":2000,"description":"Then: what Letterpress does with each event the filter lets through, in your words (\"Write our own take in the Archil blog\", \"Enroll them in Warm leads\", \"Fix it and open the pull request\"). Letterpress carries it out on its own, as you: with the tools when one does it (a post, an enrollment, a fix), or a Build sandbox when the site's code must change (a preview and a pull request, never merged on its own). Empty when it only posts each event in Slack (slack_channel)."},"slack_channel":{"description":"A draft's Slack channel: read as what it then does; nothing is posted.","anyOf":[{"type":"string","pattern":"^[CG][A-Z0-9]{2,30}$"},{"type":"null"}]},"limit":{"description":"How many of the latest events to read (at most 5).","type":"integer","minimum":1,"maximum":5}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"trigger":{"type":"string","enum":["competitor_page","visitor_identified","issue_found","build_suggestion","custom"]},"events":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":["string","null"]},"at":{"type":"string"},"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string"}}]}},"names_person":{"type":"boolean","description":"It names a person."},"decision":{"type":"string","enum":["act","skip","unread"]},"why":{"type":"string"},"would":{"description":"What Letterpress would do with it, in a line.","type":["string","null"]}},"required":["title","url","at","facts","names_person","decision","why","would"]}},"read_error":{"description":"Why the trigger's events couldn't be read, if they couldn't.","type":["string","null"]}},"required":["trigger","events","read_error"]}},{"name":"list_automation_runs","description":"Every event the automations read, newest first, one per automation and event: the event and its facts, the person it names (an identified visitor, a person your event named), its state (acting: Letterpress is doing what the prompt says; acted, skipped or failed, with why), what it asked Letterpress, and what came of it (Letterpress's reply, the build or conversation it made). A run whose status is proposed was left waiting from when automations asked first (decide_automation_run). Page with `before`. Requires capability automation:read.","capability":"automation:read","inputSchema":{"type":"object","properties":{"automation_id":{"description":"One automation's runs; omit for every automation.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"description":"Only runs in this state (acting: Letterpress is on it; proposed: left from asking first).","type":"string","enum":["pending","proposed","acting","acted","skipped","failed","rejected"]},"limit":{"description":"At most this many, newest first (default 30).","type":"integer","minimum":1,"maximum":100},"before":{"description":"Only runs created before this time: the next page.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"automation_id":{"type":"string"},"automation_name":{"type":["string","null"]},"event_key":{"type":"string"},"event":{"type":"object","properties":{"key":{"type":"string"},"at":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string"}}]}},"person":{"anyOf":[{"type":"object","properties":{"email":{"type":["string","null"]},"name":{"type":["string","null"]},"company":{"type":["string","null"]}},"required":["email","name","company"]},{"type":"null"}],"description":"The person it names: Letterpress reads them and acts on them as the prompt says."}},"required":["key","at","title","url","facts","person"]},"status":{"type":"string","enum":["pending","proposed","acting","acted","skipped","failed","rejected"]},"status_reason":{"description":"Why: the instruction's reading, a person's reason, or the error.","type":["string","null"]},"call":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"would":{"type":"string"}},"required":["tool","args"]},{"type":"null"}],"description":"What it asked: send_build_message and the message Letterpress was given (a run from before 2026-10-30: the tool its fixed action called)."},"would":{"description":"What Letter said it would do with the event, in a line.","type":["string","null"]},"result":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"ref":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["label","ref","url"]},{"type":"null"}],"description":"What came of it: Letterpress's reply, the build (ref) and its page (url), or the conversation (ref) when nothing was built."},"decided_by":{"description":"Who decided; null with decided_at set is Letter, on the standing instruction.","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","automation_id","automation_name","event_key","event","status","status_reason","call","would","result","decided_by","decided_at","created_at"]}},"next_before":{"description":"Pass as `before` for the next page; null at the end.","type":["string","null"]}},"required":["runs","next_before"]}},{"name":"decide_automation_run","description":"Answer a run left waiting from when automations asked first (before 2026-10-30; automations act on their own now): approve makes the call it proposed (as you, an admin); reject records why not (reason). A run is decided once. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"A run left waiting for a person (list_automation_runs, status proposed)."},"decision":{"type":"string","enum":["approve","reject"],"description":"approve makes the call it proposed; reject records why not."},"reason":{"description":"Why not. Required to reject.","type":"string","minLength":1,"maxLength":500}},"required":["run_id","decision"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"automation_id":{"type":"string"},"automation_name":{"type":["string","null"]},"event_key":{"type":"string"},"event":{"type":"object","properties":{"key":{"type":"string"},"at":{"type":"string"},"title":{"type":"string"},"url":{"type":["string","null"]},"facts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"type":"string"}}]}},"person":{"anyOf":[{"type":"object","properties":{"email":{"type":["string","null"]},"name":{"type":["string","null"]},"company":{"type":["string","null"]}},"required":["email","name","company"]},{"type":"null"}],"description":"The person it names: Letterpress reads them and acts on them as the prompt says."}},"required":["key","at","title","url","facts","person"]},"status":{"type":"string","enum":["pending","proposed","acting","acted","skipped","failed","rejected"]},"status_reason":{"description":"Why: the instruction's reading, a person's reason, or the error.","type":["string","null"]},"call":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"would":{"type":"string"}},"required":["tool","args"]},{"type":"null"}],"description":"What it asked: send_build_message and the message Letterpress was given (a run from before 2026-10-30: the tool its fixed action called)."},"would":{"description":"What Letter said it would do with the event, in a line.","type":["string","null"]},"result":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"ref":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["label","ref","url"]},{"type":"null"}],"description":"What came of it: Letterpress's reply, the build (ref) and its page (url), or the conversation (ref) when nothing was built."},"decided_by":{"description":"Who decided; null with decided_at set is Letter, on the standing instruction.","type":["string","null"]},"decided_at":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","automation_id","automation_name","event_key","event","status","status_reason","call","would","result","decided_by","decided_at","created_at"]}},"required":["run"]}},{"name":"send_automation_event","description":"Send an event from your own systems (a trial started, a deal closed, a form filled): every automation with a custom trigger of this name reads it at once, and the result says what each decided; what one acts on goes to Letterpress straight away (list_automation_runs follows it). `facts` are what an instruction and a prompt are read against; `person` (email, name, company) is the person it's about, whom Letterpress acts on as the prompt says (an enrollment). The same `key` sent twice is read once. Kept, so a new automation can be tried on recent events. Spends a model read per automation that reads it. Requires capability automation:write.","capability":"automation:write","inputSchema":{"type":"object","properties":{"event":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.:-]{0,63}$","description":"The event's name; automations with a custom trigger of this name read it."},"key":{"description":"Your id for it: the same key sent twice is read once. Omit for a new one each time.","type":"string","minLength":1,"maxLength":200},"title":{"type":"string","minLength":1,"maxLength":300,"description":"What happened, in a line."},"url":{"description":"A link to it, if it has one.","type":"string","maxLength":2000,"format":"uri"},"at":{"description":"When it happened; defaults to now.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"facts":{"description":"What is known about it, as flat key → value pairs (at most 40): what the filter and the prompt are read against.","type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"anyOf":[{"type":"string","maxLength":2000},{"type":"number"},{"type":"boolean"},{"type":"null"}]}},"person":{"description":"The person it's about, if any: Letterpress reads them and acts on them as the prompt says (an enrollment).","type":"object","properties":{"email":{"anyOf":[{"type":"string","maxLength":320,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"company":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]}}}},"required":["event","title"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"event":{"type":"object","properties":{"name":{"type":"string"},"key":{"type":"string"},"at":{"type":"string"}},"required":["name","key","at"]},"duplicate":{"type":"boolean","description":"This key was already sent: nothing was read again."},"runs":{"type":"array","items":{"type":"object","properties":{"automation_id":{"type":"string"},"automation_name":{"type":"string"},"status":{"type":"string","description":"acting (Letterpress is doing what the prompt says) | skipped | waiting (its owner is no longer an admin) | queued (read again on the next pass)"},"why":{"type":"string"},"result":{"description":"What Letterpress will do with it, in a line.","type":["string","null"]}},"required":["automation_id","automation_name","status","why","result"]}}},"required":["event","duplicate","runs"]}},{"name":"create_simulation","description":"Create a simulation of YOUR product's API (an OpenAPI 3.x spec you ship), so you can see how AI agents use it: a free, signup-free copy agents can explore at /sim/<slug> (REST with the same paths, MCP, llms.txt), with every call recorded. Not a demo of Letter and not a buyer simulation. Each resource gets an inferred serving rule from what it does: synthesize (we hold per-session state built from your schemas), forward (calls go to a dedicated account you give with upstream_base_url + upstream_credential, isolated per session), sink (emails, webhooks and charges are recorded and answered as success) or refuse. It starts paused (agents get a 503) unless you pass live: true: review the rules in the result, change any with update_simulation, then set status live there. From an MCP server instead: mcp_url (its tools are read from the server). To make the invented data look like the real thing, ask the customer for an API key for their real API or MCP server (read-only if they have one) and pass it as sample_credential: the simulation makes a few read-only calls (lists and gets, never a write) and keeps only the structure of what came back, never a real value; it's never used to answer an agent. Afterwards the key is held encrypted for an hour (sample.key: held): ask the customer whether to keep it for re-sampling later, and pass their answer to update_simulation as keep_sample_key; with no answer it's deleted. Return the three URLs (urls.base, urls.mcp, urls.llms) to the customer. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"What the simulation is called, usually the product name, e.g. \"Attio API\"."},"slug":{"description":"URL slug: the simulation is served at /sim/<slug>. Derived from the name when omitted; must be unique.","type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$"},"spec_url":{"description":"Public URL of the product's OpenAPI 3.x JSON document. The catalog (operations, resources, inferred serving rules) is built from it.","type":"string","format":"uri"},"spec":{"description":"The OpenAPI document itself, when it isn't public.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"mcp_tools":{"description":"The product's MCP `tools/list` result (its `tools` array), when its contract is a tool list rather than an OpenAPI document. Each tool becomes an operation named as published.","minItems":1,"maxItems":2000,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":128},"description":{"type":"string"},"inputSchema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"outputSchema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name"],"additionalProperties":{}}},"tool_route":{"description":"With mcp_tools: the product's own REST route for calling a tool by name, e.g. /api/integrations/tools/{name}. Agents can then call the simulation the same way.","type":"string","maxLength":200,"pattern":"^\\/.*\\{name\\}"},"live":{"description":"true = agents can use it as soon as it's created. Default false: it starts paused so you can review the inferred serving rules (get_simulation), then set it live with update_simulation.","type":"boolean"},"auth_header":{"description":"With mcp_tools: the header the product's API key travels in, e.g. x-api-key. Omitted = Authorization: Bearer.","type":"string","pattern":"^[A-Za-z0-9-]{1,64}$"},"offering_id":{"description":"The org offering this simulation is of, when the org sells more than one product.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"upstream_base_url":{"description":"Base URL of the dedicated account the forward rule calls, e.g. https://api.acme.com. Without one, stored data is synthesized.","type":"string","format":"uri"},"upstream_credential":{"description":"Credential for that account, sent as the spec's auth header. Stored encrypted and never returned.","type":"string","minLength":1,"maxLength":4000},"mcp_url":{"description":"The product's real MCP server URL. Its tools are read from the server itself (with sample_credential when it needs one), in place of mcp_tools.","type":"string","format":"uri"},"sample_credential":{"description":"The customer's API key for their real API or MCP server, ideally read-only. The simulation makes a few read-only calls with it (lists and gets, never a write) and keeps only the structure of what came back, never a real value, so its invented data has the real shape. Held encrypted afterwards while the customer decides whether to keep it (keep_sample_key), else deleted. Never used to answer an agent. Ask the customer for one when they ask you to simulate their API.","type":"string","minLength":1,"maxLength":4000},"sample_base_url":{"description":"With sample_credential and an OpenAPI spec: where the real API answers, e.g. https://api.acme.com or https://acme.com/api/v3. Default: the spec's first server.","type":"string","format":"uri"},"sample_auth_header":{"description":"The header the key travels in when sampling, e.g. x-api-key. Default: the spec's auth header, else Authorization: Bearer.","type":"string","maxLength":100},"keep_sample_key":{"description":"Already decided: true keeps the key encrypted for re-sampling later, false deletes it right after sampling. Omitted: it's held for an hour while you ask the customer, then deleted unless kept.","type":"boolean"}},"required":["name"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"simulation":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","description":"live | paused"},"title":{"type":"string","description":"The API's title, from its spec."},"version":{"type":"string"},"catalog_version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spec_url":{"type":["string","null"]},"forwards_to":{"description":"The dedicated account the forward rule calls, if any.","type":["string","null"]},"key_ttl_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"session_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"daily_session_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sessions it mints per rolling 24 hours."},"daily_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Calls it answers per rolling 24 hours, across every session."},"active_seed_id":{"description":"The seed new sessions start from; null = the spec's example records.","type":["string","null"]},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]},"sample":{"type":"object","properties":{"sampled_at":{"description":"When the real product was last sampled with the customer's key.","type":["string","null"]},"source":{"anyOf":[{"type":"string","enum":["rest","mcp"]},{"type":"null"}]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Read-only calls made to the real product."},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Operations whose real structure the simulation now follows."},"errors":{"type":"array","items":{"type":"string"},"description":"What the sampler couldn't read, at most five."},"key":{"type":"string","enum":["none","held","kept"],"description":"The customer's sample key: held = waiting on their answer until key_expires_at (ask them, then update_simulation keep_sample_key), kept = encrypted for resample_simulation, none = deleted or never given."},"key_expires_at":{"type":["string","null"]}},"required":["sampled_at","source","calls","operations","errors","key","key_expires_at"],"description":"Sampling the real product with the customer's key: structure only, never a real value."},"created_at":{"type":"string"}},"required":["id","slug","name","status","title","version","catalog_version","operations","spec_url","forwards_to","key_ttl_hours","session_call_cap","daily_session_cap","daily_call_cap","active_seed_id","urls","sample","created_at"]},"resources":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"Resource key: every operation on it follows one rule, so state stays consistent."},"label":{"type":"string"},"side_effect":{"type":"string","enum":["stored","compute","messaging","money","egress","admin"],"description":"What the resource does in the real world; proposes the rule."},"rule":{"type":"string","enum":["synthesize","forward","sink","refuse"],"description":"How calls are served now."},"inferred_rule":{"type":"string","enum":["synthesize","forward","sink","refuse"]},"set_by":{"type":"string","enum":["inferred","customer","staff"],"description":"Who decided the rule; inferred = no override."},"note":{"type":["string","null"]},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["resource","label","side_effect","rule","inferred_rule","set_by","note","operations"]}}},"required":["simulation","resources"]}},{"name":"list_simulations","description":"List the organization's simulations, newest first, with their URLs and how many agent sessions each had in the last 7 days. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"simulations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","description":"live | paused"},"title":{"type":"string","description":"The API's title, from its spec."},"version":{"type":"string"},"catalog_version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spec_url":{"type":["string","null"]},"forwards_to":{"description":"The dedicated account the forward rule calls, if any.","type":["string","null"]},"key_ttl_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"session_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"daily_session_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sessions it mints per rolling 24 hours."},"daily_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Calls it answers per rolling 24 hours, across every session."},"active_seed_id":{"description":"The seed new sessions start from; null = the spec's example records.","type":["string","null"]},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]},"sample":{"type":"object","properties":{"sampled_at":{"description":"When the real product was last sampled with the customer's key.","type":["string","null"]},"source":{"anyOf":[{"type":"string","enum":["rest","mcp"]},{"type":"null"}]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Read-only calls made to the real product."},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Operations whose real structure the simulation now follows."},"errors":{"type":"array","items":{"type":"string"},"description":"What the sampler couldn't read, at most five."},"key":{"type":"string","enum":["none","held","kept"],"description":"The customer's sample key: held = waiting on their answer until key_expires_at (ask them, then update_simulation keep_sample_key), kept = encrypted for resample_simulation, none = deleted or never given."},"key_expires_at":{"type":["string","null"]}},"required":["sampled_at","source","calls","operations","errors","key","key_expires_at"],"description":"Sampling the real product with the customer's key: structure only, never a real value."},"created_at":{"type":"string"},"sessions_7d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","slug","name","status","title","version","catalog_version","operations","spec_url","forwards_to","key_ttl_hours","session_call_cap","daily_session_cap","daily_call_cap","active_seed_id","urls","sample","created_at","sessions_7d"]}}},"required":["simulations"]}},{"name":"get_simulation","description":"Read one simulation: its URLs, every resource with its serving rule and who set it (inferred, customer or staff), and every operation in its catalog. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"simulation":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","description":"live | paused"},"title":{"type":"string","description":"The API's title, from its spec."},"version":{"type":"string"},"catalog_version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spec_url":{"type":["string","null"]},"forwards_to":{"description":"The dedicated account the forward rule calls, if any.","type":["string","null"]},"key_ttl_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"session_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"daily_session_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sessions it mints per rolling 24 hours."},"daily_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Calls it answers per rolling 24 hours, across every session."},"active_seed_id":{"description":"The seed new sessions start from; null = the spec's example records.","type":["string","null"]},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]},"sample":{"type":"object","properties":{"sampled_at":{"description":"When the real product was last sampled with the customer's key.","type":["string","null"]},"source":{"anyOf":[{"type":"string","enum":["rest","mcp"]},{"type":"null"}]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Read-only calls made to the real product."},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Operations whose real structure the simulation now follows."},"errors":{"type":"array","items":{"type":"string"},"description":"What the sampler couldn't read, at most five."},"key":{"type":"string","enum":["none","held","kept"],"description":"The customer's sample key: held = waiting on their answer until key_expires_at (ask them, then update_simulation keep_sample_key), kept = encrypted for resample_simulation, none = deleted or never given."},"key_expires_at":{"type":["string","null"]}},"required":["sampled_at","source","calls","operations","errors","key","key_expires_at"],"description":"Sampling the real product with the customer's key: structure only, never a real value."},"created_at":{"type":"string"}},"required":["id","slug","name","status","title","version","catalog_version","operations","spec_url","forwards_to","key_ttl_hours","session_call_cap","daily_session_cap","daily_call_cap","active_seed_id","urls","sample","created_at"]},"resources":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"Resource key: every operation on it follows one rule, so state stays consistent."},"label":{"type":"string"},"side_effect":{"type":"string","enum":["stored","compute","messaging","money","egress","admin"],"description":"What the resource does in the real world; proposes the rule."},"rule":{"type":"string","enum":["synthesize","forward","sink","refuse"],"description":"How calls are served now."},"inferred_rule":{"type":"string","enum":["synthesize","forward","sink","refuse"]},"set_by":{"type":"string","enum":["inferred","customer","staff"],"description":"Who decided the rule; inferred = no override."},"note":{"type":["string","null"]},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["resource","label","side_effect","rule","inferred_rule","set_by","note","operations"]}},"operations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"resource":{"type":"string"},"summary":{"type":"string"}},"required":["id","method","path","resource","summary"]}}},"required":["simulation","resources","operations"]}},{"name":"update_simulation","description":"Pause or resume a simulation, and override the serving rule of any resource (synthesize, forward, sink, refuse; null returns it to the inferred rule). Every operation on a resource follows its rule, so a write and the read that follows stay consistent. The override records who set it. keep_sample_key records the customer's answer about the key their API was sampled with: true keeps it encrypted for resample_simulation, false deletes it now. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."},"daily_call_cap":{"description":"Calls the simulation answers per rolling 24 hours, across every session; past it, calls get a 429. Default 20,000.","type":"integer","minimum":1,"maximum":10000000},"daily_session_cap":{"description":"Sessions (keys) the simulation mints per rolling 24 hours, however they're minted. Default 500.","type":"integer","minimum":1,"maximum":100000},"active_seed_id":{"description":"Which seed new sessions start from (from generate_simulation_seed); null = the spec's example records. Sessions already open keep the seed they started with.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"status":{"description":"paused = every call and key mint answers 503 until set live again.","type":"string","enum":["live","paused"]},"keep_sample_key":{"description":"The customer's answer about the key their API was sampled with: true keeps it encrypted for re-sampling later (resample_simulation), false deletes it now.","type":"boolean"},"resource_rules":{"description":"Serving-rule overrides, one per resource. Each records who set it.","maxItems":200,"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","minLength":1,"maxLength":300,"description":"The resource key from get_simulation, e.g. /v2/objects/{object}/records."},"rule":{"anyOf":[{"type":"string","enum":["synthesize","forward","sink","refuse"]},{"type":"null"}],"description":"synthesize, forward, sink or refuse; null returns the resource to its inferred rule."},"note":{"description":"Why, for whoever reads the rule next.","type":"string","maxLength":500}},"required":["resource","rule"]}}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"simulation":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","description":"live | paused"},"title":{"type":"string","description":"The API's title, from its spec."},"version":{"type":"string"},"catalog_version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spec_url":{"type":["string","null"]},"forwards_to":{"description":"The dedicated account the forward rule calls, if any.","type":["string","null"]},"key_ttl_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"session_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"daily_session_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sessions it mints per rolling 24 hours."},"daily_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Calls it answers per rolling 24 hours, across every session."},"active_seed_id":{"description":"The seed new sessions start from; null = the spec's example records.","type":["string","null"]},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]},"sample":{"type":"object","properties":{"sampled_at":{"description":"When the real product was last sampled with the customer's key.","type":["string","null"]},"source":{"anyOf":[{"type":"string","enum":["rest","mcp"]},{"type":"null"}]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Read-only calls made to the real product."},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Operations whose real structure the simulation now follows."},"errors":{"type":"array","items":{"type":"string"},"description":"What the sampler couldn't read, at most five."},"key":{"type":"string","enum":["none","held","kept"],"description":"The customer's sample key: held = waiting on their answer until key_expires_at (ask them, then update_simulation keep_sample_key), kept = encrypted for resample_simulation, none = deleted or never given."},"key_expires_at":{"type":["string","null"]}},"required":["sampled_at","source","calls","operations","errors","key","key_expires_at"],"description":"Sampling the real product with the customer's key: structure only, never a real value."},"created_at":{"type":"string"}},"required":["id","slug","name","status","title","version","catalog_version","operations","spec_url","forwards_to","key_ttl_hours","session_call_cap","daily_session_cap","daily_call_cap","active_seed_id","urls","sample","created_at"]},"resources":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"Resource key: every operation on it follows one rule, so state stays consistent."},"label":{"type":"string"},"side_effect":{"type":"string","enum":["stored","compute","messaging","money","egress","admin"],"description":"What the resource does in the real world; proposes the rule."},"rule":{"type":"string","enum":["synthesize","forward","sink","refuse"],"description":"How calls are served now."},"inferred_rule":{"type":"string","enum":["synthesize","forward","sink","refuse"]},"set_by":{"type":"string","enum":["inferred","customer","staff"],"description":"Who decided the rule; inferred = no override."},"note":{"type":["string","null"]},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["resource","label","side_effect","rule","inferred_rule","set_by","note","operations"]}}},"required":["simulation","resources"]}},{"name":"resample_simulation","description":"Sample a simulation's real product again, after its API changed: with the key the customer chose to keep, or a new sample_credential. A few read-only calls (never a write); only the structure of what comes back is kept, never a real value, and the simulation's routes don't change. A new key is held for an hour unless keep_sample_key says otherwise: ask the customer whether to keep it. Regenerate the seed afterwards (generate_simulation_seed) for invented data in the new structure. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation to sample again."},"sample_credential":{"description":"A key for the real API or MCP server. Omitted: the key the customer chose to keep.","type":"string","minLength":1,"maxLength":4000},"keep_sample_key":{"description":"With a new key: true keeps it encrypted, false deletes it after. Omitted: held for an hour while you ask.","type":"boolean"}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"simulation":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","description":"live | paused"},"title":{"type":"string","description":"The API's title, from its spec."},"version":{"type":"string"},"catalog_version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"spec_url":{"type":["string","null"]},"forwards_to":{"description":"The dedicated account the forward rule calls, if any.","type":["string","null"]},"key_ttl_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"session_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"daily_session_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sessions it mints per rolling 24 hours."},"daily_call_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Calls it answers per rolling 24 hours, across every session."},"active_seed_id":{"description":"The seed new sessions start from; null = the spec's example records.","type":["string","null"]},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]},"sample":{"type":"object","properties":{"sampled_at":{"description":"When the real product was last sampled with the customer's key.","type":["string","null"]},"source":{"anyOf":[{"type":"string","enum":["rest","mcp"]},{"type":"null"}]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Read-only calls made to the real product."},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Operations whose real structure the simulation now follows."},"errors":{"type":"array","items":{"type":"string"},"description":"What the sampler couldn't read, at most five."},"key":{"type":"string","enum":["none","held","kept"],"description":"The customer's sample key: held = waiting on their answer until key_expires_at (ask them, then update_simulation keep_sample_key), kept = encrypted for resample_simulation, none = deleted or never given."},"key_expires_at":{"type":["string","null"]}},"required":["sampled_at","source","calls","operations","errors","key","key_expires_at"],"description":"Sampling the real product with the customer's key: structure only, never a real value."},"created_at":{"type":"string"}},"required":["id","slug","name","status","title","version","catalog_version","operations","spec_url","forwards_to","key_ttl_hours","session_call_cap","daily_session_cap","daily_call_cap","active_seed_id","urls","sample","created_at"]},"resources":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"Resource key: every operation on it follows one rule, so state stays consistent."},"label":{"type":"string"},"side_effect":{"type":"string","enum":["stored","compute","messaging","money","egress","admin"],"description":"What the resource does in the real world; proposes the rule."},"rule":{"type":"string","enum":["synthesize","forward","sink","refuse"],"description":"How calls are served now."},"inferred_rule":{"type":"string","enum":["synthesize","forward","sink","refuse"]},"set_by":{"type":"string","enum":["inferred","customer","staff"],"description":"Who decided the rule; inferred = no override."},"note":{"type":["string","null"]},"operations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["resource","label","side_effect","rule","inferred_rule","set_by","note","operations"]}}},"required":["simulation","resources"]}},{"name":"generate_simulation_seed","description":"Write the workspace agents find when they open a simulation, once, from a plain description of the account (\"a 3-person content team with two blogs and a few runs in progress\"). A model writes it as a whole so its values agree with each other; every item is checked against your schemas, and the report shows what was kept, completed, dropped or left dangling. It is saved as the simulation's next seed version; pass activate, or set active_seed_id with update_simulation, for new sessions to start from it. Collections it doesn't cover keep the spec's example records, or start empty with empty_workspace. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."},"prompt":{"type":"string","minLength":10,"maxLength":4000,"description":"The account an agent should find, in plain words: \"a 3-person content team with two blogs, a dozen published articles and two runs in progress\"."},"resources":{"description":"What to seed, by resource key or tool name (see get_simulation). Omitted = the resources whose names match the prompt, plus what their items point at.","maxItems":30,"type":"array","items":{"type":"string","minLength":1,"maxLength":300}},"items_per_collection":{"description":"Default 5.","type":"integer","minimum":1,"maximum":20},"empty_workspace":{"description":"true = collections the seed doesn't cover start empty instead of with the spec's example records.","type":"boolean"},"activate":{"description":"true = new sessions start from this seed now. Default false: review it first.","type":"boolean"}},"required":["simulation_id","prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"seed":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"model":{"type":["string","null"]},"active":{"type":"boolean","description":"Whether new sessions start from it now."},"targets":{"type":"array","items":{"type":"string"},"description":"The collections and answers it covers."},"items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Items kept per collection."},"effects":{"type":"array","items":{"type":"object","properties":{"on":{"type":"string"},"answer":{"type":"string"},"add":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["on","answer","add"]},"description":"Counters actions move: after a successful `on`, the session's `answer` changes by `add`."},"report":{"type":"object","properties":{"targets":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"kept":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"dropped":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"filled":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Items the model left incomplete that the schema completed."},"field_errors":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string"},"path":{"type":"string"},"code":{"type":"string"}},"required":["target","path","code"]}},"dangling":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string"},"field":{"type":"string"},"value":{"type":"string"}},"required":["target","field","value"]},"description":"References into a collection the seed has no items for."},"repaired_ids":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Ids in a format the API's inputs reject, replaced everywhere they appear."},"repaired_refs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"References to items the seed doesn't have, pointed at items it does."},"id_format":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string"},"field":{"type":"string"},"value":{"type":"string"}},"required":["target","field","value"]},"description":"Ids still in a format the API rejects."},"planned":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Items the plan gave each collection."},"failed":{"type":"array","items":{"type":"string"},"description":"Targets the model never returned as usable JSON, after a retry; regenerate to fill them."}},"required":["targets","kept","dropped","filled","field_errors","dangling","repaired_ids","repaired_refs","id_format","planned","failed"]}},"required":["id","version","model","active","targets","items","effects","report"]}},"required":["seed"]}},{"name":"mint_simulation_key","description":"Mint a simulation key for your own agents or CI, labelled with the agent, model and goal so their sessions are told apart in the report. Agents can also mint their own with POST <base>/_sim/sessions, or by connecting over MCP; keys expire after the simulation's TTL. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."},"agent":{"description":"Which agent will use the key, e.g. claude-code, codex, our-ci.","type":"string","maxLength":80},"model":{"type":"string","maxLength":80},"goal":{"description":"What the agent is trying to do, in its own words.","type":"string","maxLength":500}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"key":{"type":"string","description":"sim_… — shown once. Send it as the product's own auth header."},"session_id":{"type":"string"},"expires_at":{"type":"string"},"urls":{"type":"object","properties":{"base":{"type":"string","description":"The simulated API's base URL: call it exactly like the real one."},"mcp":{"type":"string","description":"MCP endpoint: every operation is a tool; connecting mints a session."},"llms":{"type":"string","description":"llms.txt: what the simulation is and how an agent gets in."},"openapi":{"type":"string","description":"The product's spec with servers pointing at the simulation."},"mint":{"type":"string","description":"POST here to mint a key with nothing asked."}},"required":["base","mcp","llms","openapi","mint"]}},"required":["key","session_id","expires_at","urls"]}},{"name":"list_simulation_sessions","description":"List a simulation's agent sessions, newest first: who the agent said it was, its MCP client, its goal, and how many calls it made. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."},"limit":{"description":"Newest first; default 50.","type":"integer","minimum":1,"maximum":200}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string"},"expires_at":{"type":"string"},"last_call_at":{"type":["string","null"]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"minted_via":{"type":"string","description":"rest | mcp | tool"},"agent":{"description":"As declared at mint.","type":["string","null"]},"model":{"type":["string","null"]},"goal":{"type":["string","null"]},"client":{"description":"MCP clientInfo, e.g. claude-code 2.1.291.","type":["string","null"]},"requester_agent":{"description":"Classified from the user agent.","type":["string","null"]},"key_prefix":{"type":"string"}},"required":["id","created_at","expires_at","last_call_at","calls","minted_via","agent","model","goal","client","requester_agent","key_prefix"]}}},"required":["sessions"]}},{"name":"get_simulation_session","description":"Read one agent session call by call: each operation (or the nonexistent path it guessed), how it was served, the status, the per-field validation errors, the argument shape, and the effects its sinks captured. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The session's id, from list_simulation_sessions."}},"required":["session_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"session":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string"},"expires_at":{"type":"string"},"last_call_at":{"type":["string","null"]},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"minted_via":{"type":"string","description":"rest | mcp | tool"},"agent":{"description":"As declared at mint.","type":["string","null"]},"model":{"type":["string","null"]},"goal":{"type":["string","null"]},"client":{"description":"MCP clientInfo, e.g. claude-code 2.1.291.","type":["string","null"]},"requester_agent":{"description":"Classified from the user agent.","type":["string","null"]},"key_prefix":{"type":"string"},"simulation_id":{"type":"string"},"outbox":{"type":"array","items":{},"description":"Effects the sinks captured."}},"required":["id","created_at","expires_at","last_call_at","calls","minted_via","agent","model","goal","client","requester_agent","key_prefix","simulation_id","outbox"]},"calls":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"at":{"type":"string"},"surface":{"type":"string"},"kind":{"type":"string","description":"call | feedback"},"method":{"type":["string","null"]},"path":{"description":"The operation's path template, or the raw path of an unknown call.","type":["string","null"]},"operation_id":{"description":"null = the agent called something that doesn't exist.","type":["string","null"]},"served":{"type":["string","null"]},"status":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"latency_ms":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"field_errors":{},"nearest_operation":{"type":["string","null"]},"arg_shape":{"description":"The argument's shape; values are never stored."},"note":{"type":["string","null"]}},"required":["seq","at","surface","kind","method","path","operation_id","served","status","latency_ms","field_errors","nearest_operation","arg_shape","note"]}}},"required":["session","calls"]}},{"name":"get_simulation_report","description":"How agents use your API, from every session in the window: the funnel (minted → called → first success → first write), calls and time to first success, operations agents expected that don't exist, argument errors by field, error loops, unused operations, results by agent, and what agents said in feedback. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation's id."},"days":{"description":"Window, default 30 days.","type":"integer","minimum":1,"maximum":90}},"required":["simulation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"report":{"type":"object","properties":{"window_days":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"funnel":{"type":"object","properties":{"minted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"called":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"succeeded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"wrote":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["minted","called","succeeded","wrote"]},"first_success":{"type":"object","properties":{"median_calls":{"type":["number","null"]},"median_seconds":{"type":["number","null"]}},"required":["median_calls","median_seconds"]},"hallucinated":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"nearest_operation":{"type":["string","null"]}},"required":["method","path","calls","sessions","nearest_operation"]},"description":"Operations agents expected that don't exist: missing features, or docs that imply them."},"field_errors":{"type":"array","items":{"type":"object","properties":{"operation_id":{"type":"string"},"field":{"type":"string"},"code":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["operation_id","field","code","count","sessions"]}},"error_loops":{"type":"array","items":{"type":"object","properties":{"operation_id":{"type":"string"},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"longest":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["operation_id","sessions","longest"]},"description":"The same call failing 3+ times in a row."},"operations":{"type":"array","items":{"type":"object","properties":{"operation_id":{"type":"string"},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"errors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["operation_id","calls","errors","sessions"]}},"unused":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sample":{"type":"array","items":{"type":"string"}}},"required":["count","total","sample"]},"by_agent":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"success_rate":{"type":"number"}},"required":["agent","sessions","calls","success_rate"]}},"served":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"feedback":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string"},"at":{"type":"string"},"note":{"type":"string"}},"required":["session_id","at","note"]}}},"required":["window_days","sessions","calls","funnel","first_success","hallucinated","field_errors","error_loops","operations","unused","by_agent","served","feedback"]}},"required":["report"]}},{"name":"list_simulation_runs","description":"Driven runs: real agents (Claude Code, Codex) given one task and nothing else, and how each went. Every run's verdict against the truth (pass / partial / fail), how it stopped (completed / blocked / stopped / budget / timeout / error), the named cause of a miss (discovery: never found the right tool or data; application: found it, used it wrong; hallucinated; tool_error; …), invented values, turns and cost, newest first, with totals. Filter to one simulation, to runs against the live product (simulation_id: null), or to a task pack (suite). Read one with get_simulation_run. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"simulation_id":{"description":"Runs against this simulation. null = runs pointed at the live product (no simulation). Omitted = every run in the org.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"suite":{"description":"Only runs from this task pack, e.g. letter-mcp.","type":"string","maxLength":80},"limit":{"description":"Newest first; default 100.","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"simulation_id":{"description":"The simulation it ran against; null = the live product.","type":["string","null"]},"suite":{"description":"The task pack it came from, e.g. letter-mcp.","type":["string","null"]},"task_key":{"type":"string"},"task_prompt":{"type":"string","description":"The one task the agent was given, and nothing else."},"agent":{"type":"string"},"model":{"type":["string","null"]},"started_via":{"type":"string","description":"app | tool | cli | script"},"status":{"type":"string","description":"queued | running | finished"},"outcome":{"description":"How it stopped: completed | blocked | stopped | budget | timeout | error.","type":["string","null"]},"verdict":{"description":"Graded against the truth: pass | partial | fail.","type":["string","null"]},"verdict_reason":{"type":["string","null"]},"cause":{"description":"The named cause of a miss: discovery (never found the tool or data) | application (used it wrong) | hallucinated | tool_error | unwanted_side_effect | incomplete | none.","type":["string","null"]},"turns":{"type":["number","null"]},"secs":{"type":["number","null"]},"cost_usd":{"type":["number","null"]},"created_at":{"type":"string"},"tool_calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many tool calls it made."},"invented":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Values in its answer judged invented (the hallucination check)."}},"required":["id","simulation_id","suite","task_key","task_prompt","agent","model","started_via","status","outcome","verdict","verdict_reason","cause","turns","secs","cost_usd","created_at","tool_calls","invented"]}},"totals":{"type":"object","properties":{"runs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pass":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"partial":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fail":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"ungraded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"invented":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cost_usd":{"type":"number"},"causes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Misses by named cause."}},"required":["runs","pass","partial","fail","ungraded","invented","cost_usd","causes"]}},"required":["runs","totals"]}},{"name":"get_simulation_run","description":"One driven run in full: the task, what it was graded against, the agent's answer, every tool it called in order with each call's error, the values judged invented, how it stopped and why it missed. The detail behind a row of list_simulation_runs. Requires capability simulations:read.","capability":"simulations:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run's id, from list_simulation_runs."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"simulation_id":{"description":"The simulation it ran against; null = the live product.","type":["string","null"]},"suite":{"description":"The task pack it came from, e.g. letter-mcp.","type":["string","null"]},"task_key":{"type":"string"},"task_prompt":{"type":"string","description":"The one task the agent was given, and nothing else."},"agent":{"type":"string"},"model":{"type":["string","null"]},"started_via":{"type":"string","description":"app | tool | cli | script"},"status":{"type":"string","description":"queued | running | finished"},"outcome":{"description":"How it stopped: completed | blocked | stopped | budget | timeout | error.","type":["string","null"]},"verdict":{"description":"Graded against the truth: pass | partial | fail.","type":["string","null"]},"verdict_reason":{"type":["string","null"]},"cause":{"description":"The named cause of a miss: discovery (never found the tool or data) | application (used it wrong) | hallucinated | tool_error | unwanted_side_effect | incomplete | none.","type":["string","null"]},"turns":{"type":["number","null"]},"secs":{"type":["number","null"]},"cost_usd":{"type":["number","null"]},"created_at":{"type":"string"},"phase":{"description":"Where a run started from Letter is: provisioning | running | collecting | grading | done. null for an imported run.","type":["string","null"]},"live":{"type":"boolean","description":"true while the run is going: read it again for more of its activity."},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"The agent's feed, oldest first: what it said (say), each call it made (tool), errors, stages. Live while it runs."},"budget_usd":{"description":"What the run may spend on the model, at most.","type":["number","null"]},"max_turns":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"description":"Why a run that failed failed.","type":["string","null"]},"session_id":{"description":"The simulation session it used, when it ran against one.","type":["string","null"]},"truth":{"description":"What its answer was graded against.","type":["string","null"]},"target":{"description":"What it was pointed at: an MCP or API endpoint, or a repo.","type":["string","null"]},"answer":{"description":"Its last message to the person.","type":["string","null"]},"tool_calls":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"error":{"type":["string","null"]}},"required":["tool","error"]}},"invented":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"why":{"type":"string"}},"required":["value"]}},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","simulation_id","suite","task_key","task_prompt","agent","model","started_via","status","outcome","verdict","verdict_reason","cause","turns","secs","cost_usd","created_at","phase","live","activity","budget_usd","max_turns","error","session_id","truth","target","answer","tool_calls","invented","started_at","finished_at"]}},"required":["run"]}},{"name":"record_simulation_runs","description":"Record graded driven runs (up to 200 at once): what the driven-runs grader produced for each agent and task, stored so the simulation page, list_simulation_runs and the CLI show it. simulation_id is the simulation they ran against; omit it for runs against the live product. letterstory sim runs-import builds this from the grader's graded.json and the pack's tasks.json. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"description":"The simulation the runs were against; omitted or null = the live product.","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"suite":{"description":"The task pack they came from, e.g. letter-mcp.","type":"string","minLength":1,"maxLength":80},"runs":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"task_key":{"type":"string","minLength":1,"maxLength":120},"task_prompt":{"type":"string","minLength":1,"maxLength":4000},"truth":{"type":"string","maxLength":4000},"agent":{"type":"string","minLength":1,"maxLength":80,"description":"e.g. claude-code, codex."},"model":{"type":"string","maxLength":80},"target":{"description":"What the agent was pointed at: an MCP or API endpoint, or a repo.","type":"string","maxLength":500},"outcome":{"description":"How the run stopped.","type":"string","enum":["completed","blocked","stopped","budget","timeout","error"]},"verdict":{"description":"How it was graded against the truth.","type":"string","enum":["pass","partial","fail"]},"verdict_reason":{"type":"string","maxLength":1000},"cause":{"description":"The named cause of a miss; none when it passed.","type":"string","enum":["none","discovery","application","hallucinated","tool_error","unwanted_side_effect","incomplete"]},"turns":{"type":"integer","minimum":0,"maximum":9007199254740991},"secs":{"type":"number","minimum":0},"cost_usd":{"type":"number","minimum":0},"tool_calls":{"description":"The tools it called, in order, with each call's error. No inputs or outputs.","maxItems":500,"type":"array","items":{"type":"object","properties":{"tool":{"type":"string","maxLength":200},"error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["tool"]}},"invented":{"description":"Values in the answer no tool output contains, judged invented.","maxItems":100,"type":"array","items":{"type":"object","properties":{"value":{"type":"string","maxLength":500},"why":{"type":"string","maxLength":500}},"required":["value"]}},"answer":{"type":"string","maxLength":20000},"started_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"finished_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["task_key","task_prompt","agent"]},"description":"One per agent per task, as the grader produced them."}},"required":["runs"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"recorded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"ids":{"type":"array","items":{"type":"string"}}},"required":["recorded","ids"]}},{"name":"start_simulation_run","description":"Run a real agent (Claude Code) against one of your simulations: give it one task in a customer's words and, optionally, the right answer to grade it against. It runs in a sandbox with the simulation's MCP server as its only tools, and is always told it is working in a simulation. Watch it with get_simulation_run (live: true while it runs; activity is its feed), stop it with stop_simulation_run. When it ends it is graded: verdict against the truth, the named cause of a miss, and values it made up. Spends up to budget_usd of model use (default $2, at most $5) and usually takes 2 to 10 minutes, plus a few minutes to start a sandbox. The simulation must be live; at most 2 runs at once per organization. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"simulation_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The simulation the agent works against. It must be live."},"task_prompt":{"type":"string","minLength":3,"maxLength":4000,"description":"What the agent is asked to do, in a customer's words, e.g. \"Find our three open deals over $10k and tell me who owns each.\" The agent is always told it is working in a simulation."},"truth":{"description":"The right answer, and what counts as a pass or a partial, to grade against. Omitted = no verdict; the run is still checked for errors and made-up values.","type":"string","maxLength":4000},"task_key":{"description":"A short name for the task, to compare runs of it, e.g. open-deals. Default: adhoc.","type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,79}$"},"suite":{"description":"A label to group runs by, e.g. weekly-check.","type":"string","minLength":1,"maxLength":80},"model":{"description":"The Claude model the agent runs on. Default: Letter's simulation-run model (Sonnet).","type":"string","maxLength":80,"pattern":"^claude-[a-z0-9.-]+$"},"budget_usd":{"description":"What the run may spend on the model, at most. Default $2; at most $5.","type":"number","minimum":0.1,"maximum":5},"max_turns":{"description":"The most model turns the agent may take. Default 40.","type":"integer","minimum":1,"maximum":100}},"required":["simulation_id","task_prompt"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"simulation_id":{"description":"The simulation it ran against; null = the live product.","type":["string","null"]},"suite":{"description":"The task pack it came from, e.g. letter-mcp.","type":["string","null"]},"task_key":{"type":"string"},"task_prompt":{"type":"string","description":"The one task the agent was given, and nothing else."},"agent":{"type":"string"},"model":{"type":["string","null"]},"started_via":{"type":"string","description":"app | tool | cli | script"},"status":{"type":"string","description":"queued | running | finished"},"outcome":{"description":"How it stopped: completed | blocked | stopped | budget | timeout | error.","type":["string","null"]},"verdict":{"description":"Graded against the truth: pass | partial | fail.","type":["string","null"]},"verdict_reason":{"type":["string","null"]},"cause":{"description":"The named cause of a miss: discovery (never found the tool or data) | application (used it wrong) | hallucinated | tool_error | unwanted_side_effect | incomplete | none.","type":["string","null"]},"turns":{"type":["number","null"]},"secs":{"type":["number","null"]},"cost_usd":{"type":["number","null"]},"created_at":{"type":"string"},"phase":{"description":"Where a run started from Letter is: provisioning | running | collecting | grading | done. null for an imported run.","type":["string","null"]},"live":{"type":"boolean","description":"true while the run is going: read it again for more of its activity."},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"The agent's feed, oldest first: what it said (say), each call it made (tool), errors, stages. Live while it runs."},"budget_usd":{"description":"What the run may spend on the model, at most.","type":["number","null"]},"max_turns":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"description":"Why a run that failed failed.","type":["string","null"]},"session_id":{"description":"The simulation session it used, when it ran against one.","type":["string","null"]},"truth":{"description":"What its answer was graded against.","type":["string","null"]},"target":{"description":"What it was pointed at: an MCP or API endpoint, or a repo.","type":["string","null"]},"answer":{"description":"Its last message to the person.","type":["string","null"]},"tool_calls":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"error":{"type":["string","null"]}},"required":["tool","error"]}},"invented":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"why":{"type":"string"}},"required":["value"]}},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","simulation_id","suite","task_key","task_prompt","agent","model","started_via","status","outcome","verdict","verdict_reason","cause","turns","secs","cost_usd","created_at","phase","live","activity","budget_usd","max_turns","error","session_id","truth","target","answer","tool_calls","invented","started_at","finished_at"]},"message":{"type":"string"}},"required":["run","message"]}},{"name":"stop_simulation_run","description":"Stop a running simulation run. What the agent did so far is kept (its calls, any answer, its feed), and the run is recorded as stopped, ungraded. A run that already ended is returned as it is. Requires capability simulations:write.","capability":"simulations:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run to stop, from start_simulation_run or list_simulation_runs."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"simulation_id":{"description":"The simulation it ran against; null = the live product.","type":["string","null"]},"suite":{"description":"The task pack it came from, e.g. letter-mcp.","type":["string","null"]},"task_key":{"type":"string"},"task_prompt":{"type":"string","description":"The one task the agent was given, and nothing else."},"agent":{"type":"string"},"model":{"type":["string","null"]},"started_via":{"type":"string","description":"app | tool | cli | script"},"status":{"type":"string","description":"queued | running | finished"},"outcome":{"description":"How it stopped: completed | blocked | stopped | budget | timeout | error.","type":["string","null"]},"verdict":{"description":"Graded against the truth: pass | partial | fail.","type":["string","null"]},"verdict_reason":{"type":["string","null"]},"cause":{"description":"The named cause of a miss: discovery (never found the tool or data) | application (used it wrong) | hallucinated | tool_error | unwanted_side_effect | incomplete | none.","type":["string","null"]},"turns":{"type":["number","null"]},"secs":{"type":["number","null"]},"cost_usd":{"type":["number","null"]},"created_at":{"type":"string"},"phase":{"description":"Where a run started from Letter is: provisioning | running | collecting | grading | done. null for an imported run.","type":["string","null"]},"live":{"type":"boolean","description":"true while the run is going: read it again for more of its activity."},"activity":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"}},"required":["at","kind","text"]},"description":"The agent's feed, oldest first: what it said (say), each call it made (tool), errors, stages. Live while it runs."},"budget_usd":{"description":"What the run may spend on the model, at most.","type":["number","null"]},"max_turns":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"description":"Why a run that failed failed.","type":["string","null"]},"session_id":{"description":"The simulation session it used, when it ran against one.","type":["string","null"]},"truth":{"description":"What its answer was graded against.","type":["string","null"]},"target":{"description":"What it was pointed at: an MCP or API endpoint, or a repo.","type":["string","null"]},"answer":{"description":"Its last message to the person.","type":["string","null"]},"tool_calls":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"error":{"type":["string","null"]}},"required":["tool","error"]}},"invented":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"why":{"type":"string"}},"required":["value"]}},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","simulation_id","suite","task_key","task_prompt","agent","model","started_via","status","outcome","verdict","verdict_reason","cause","turns","secs","cost_usd","created_at","phase","live","activity","budget_usd","max_turns","error","session_id","truth","target","answer","tool_calls","invented","started_at","finished_at"]},"stopped":{"type":"boolean","description":"false when the run had already ended."}},"required":["run","stopped"]}},{"name":"list_variationals","description":"List the org's variationals (A/B tests of one page's words and images), newest first: each one's page, status (draft, running, paused, ended), variants with their weights and changes, goals, and results for the current revision (views, visitors, seconds on the page, scroll, conversions per goal, each challenger read against the original, and a one-line summary). Args: site_id?, status?. Requires capability variationals:read.","capability":"variationals:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"Only this Visitors site's.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"with_results":{"description":"false: no results (each is null), for a quick list of names and statuses. Default true.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variationals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]}},"github_connected":{"type":"boolean","description":"Letter can write to the org's GitHub: a new test needs it, and the Visitors script on its site."}},"required":["variationals","github_connected"]}},{"name":"get_variational","description":"One variational with its results. Per variant (people only; automated browsers apart in agent_views): views, distinct visitors, mean and median seconds visible, mean scroll, the share of views that showed all its changes, and per goal the visitors who converted. Each challenger against the original (versus_default): the conversion difference with its interval, lift and chance of being better (or time on page when there is no goal), and the call: too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes, then ahead, behind or even by the interval (Bonferroni-widened with several challengers). Results count the current revision only. edit_url opens the page with the in-page editor. Args: variational_id, with_results? (false: no results, much quicker). Requires capability variationals:read.","capability":"variationals:read","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."},"with_results":{"description":"false: the test without its results (results null), much quicker; for an editor opening it. Default true.","type":"boolean"}},"required":["variational_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variational":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]}},"required":["variational"]}},{"name":"read_variational_page","description":"Read a variational's page as its editors do, to change its copy with no one looking: every block of copy (a heading, a paragraph, a link, a button's text) with the exact tag, `before` and `occurrence` a text change in save_variational_variant needs, every image with its address, and each saved challenger checked against the page now (found, and the changes whose `before` the page no longer has, which nobody is shown). Fetched now from the site, so it works whether or not the Visitors script is installed. Read with the editors' own code, without the site's scripts or stylesheets. Args: variational_id. Requires capability variationals:read.","capability":"variationals:read","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."}},"required":["variational_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"url":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"before":{"type":"string","description":"The block's text exactly as a change's `before` needs it."},"occurrence":{"description":"Which of the blocks with this tag and text it is, from 0.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"context":{"type":"string","description":"Where it is on the page, in words."}},"required":["tag","before","context"]},"description":"Every block of copy a change can target, in page order: a heading, a paragraph, a link, a button's text."},"images":{"type":"array","items":{"type":"object","properties":{"before":{"type":"string","description":"The image's address, as an image change's `before`."},"alt":{"type":"string"},"occurrence":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["before","alt"]}},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"changes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"found":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many of its changes find their block on the page now."},"missing":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Changes whose `before` the page no longer has: nobody sees them."}},"required":["key","changes","found","missing"]},"description":"Each saved challenger checked against the page now."},"truncated":{"type":"boolean","description":"The page had more blocks than are listed."}},"required":["url","blocks","images","variants","truncated"]}},{"name":"create_variational","description":"Make a variational for one page of a Visitors site: a draft with only A, the page as it is. Then add variants with save_variational_variant (or open edit_url, the page with the in-page editor, and change its words and images in place), and start it with update_variational status running. Goals are what counts as a conversion (none given: a click on any button or link counts; goals [] for none): a click on an element (selector), reaching a path later (/thanks, or /signup* for a prefix), or an event the site calls with lettervisit(\"convert\", \"<event>\"); the first is primary. Time on page and scroll are measured either way. Args: page_url (https://acme.com/pricing, or a path), site_id? (else the site the page's domain names, or the org's only one), name?, goals?. Requires capability variationals:write.","capability":"variationals:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site the page is on. Optional when the page's domain names one, or the org has one.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"page_url":{"type":"string","minLength":1,"maxLength":2000,"description":"The page to test, e.g. https://acme.com/pricing (or a path on the site, /pricing)."},"name":{"description":"Defaults to the page's path.","type":"string","minLength":1,"maxLength":120},"goals":{"maxItems":5,"type":"array","items":{"oneOf":[{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"click"},"selector":{"type":"string","minLength":1,"maxLength":600,"description":"A click on this element, or anything inside it, converts."}},"required":["label","kind","selector"],"additionalProperties":false},{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"url"},"path":{"type":"string","maxLength":500,"pattern":"^\\/[^\\s?#]*\\*?$","description":"Reaching this path on the site later converts; a trailing * matches a prefix."}},"required":["label","kind","path"],"additionalProperties":false},{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"event"},"event":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,60}$","description":"The site calls lettervisit(\"convert\", \"<event>\") when it happens."}},"required":["label","kind","event"],"additionalProperties":false}]},"description":"What counts as a conversion, first one primary. Time on page and scroll are measured either way."}},"required":["page_url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variational":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]}},"required":["variational"]}},{"name":"update_variational","description":"Change a variational: its name, goals (replaced whole; the first is primary), weights (every variant's share of visitors in whole percent, a included, adding up to 100; a may be 0), or status: running starts or resumes it (it needs a variant with a change, and a proven site; one running variational per page, page_busy otherwise), paused stops it for now (everyone sees the page as it is within a minute), ended stops it for good, with winner naming the variant you chose. Who changed the status and when is kept on the variational. Weights and goals don't restart the results. Args: variational_id, name?, goals?, weights?, status?, winner?. Requires capability variationals:write.","capability":"variationals:write","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."},"name":{"type":"string","minLength":1,"maxLength":120},"goals":{"maxItems":5,"type":"array","items":{"oneOf":[{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"click"},"selector":{"type":"string","minLength":1,"maxLength":600,"description":"A click on this element, or anything inside it, converts."}},"required":["label","kind","selector"],"additionalProperties":false},{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"url"},"path":{"type":"string","maxLength":500,"pattern":"^\\/[^\\s?#]*\\*?$","description":"Reaching this path on the site later converts; a trailing * matches a prefix."}},"required":["label","kind","path"],"additionalProperties":false},{"type":"object","properties":{"key":{"description":"A short id; made from the label when absent.","type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What a conversion is, in words (Started a trial)."},"kind":{"type":"string","const":"event"},"event":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,60}$","description":"The site calls lettervisit(\"convert\", \"<event>\") when it happens."}},"required":["label","kind","event"],"additionalProperties":false}]},"description":"What counts as a conversion, first one primary. Time on page and scroll are measured either way."},"weights":{"description":"Every variant's share of visitors in whole percent, a included, adding up to 100.","type":"object","propertyNames":{"type":"string","pattern":"^[a-f]$","description":"A variant: a is the page as it is (the default); b, c, d… are the challengers."},"additionalProperties":{"type":"integer","minimum":0,"maximum":100}},"status":{"description":"running starts or resumes it, paused stops it for now, ended stops it for good.","type":"string","enum":["running","paused","ended"]},"winner":{"description":"With status ended: the variant you chose.","type":"string","pattern":"^[a-f]$"}},"required":["variational_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variational":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]}},"required":["variational"]}},{"name":"save_variational_variant","description":"Add or replace a variant's changes. Words and images only. A text change is a copy edit of one block (the shape revise_repo_change's copy_edits take, and the preview panel's and the in-page editor's copy editor make): {kind: text, tag (h1, p, button, a, li…), before (the block's whole text as a reader sees it now, one line per line), after (what it says instead; empty removes it), occurrence? (which of the blocks with that text, from 0), context? (where it is, in words)}. The Visitors script finds the block by tag and text (whitespace never matters) and changes only the words that differ, so links and emphasis inside it stay. An image change is {kind: image, before (its src now), after (an https address or a path on the site), alt?, occurrence?}. A change applies only while the page still has `before`, so a stale test never overwrites new copy. Without key it adds the next variant (b, then c…, up to f) and splits the visitors evenly again; a (the page as it is) can't carry changes. Changing a variant's changes starts the results again (a new revision). Args: variational_id, key?, name?, deltas. Requires capability variationals:write.","capability":"variationals:write","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."},"key":{"description":"The variant to replace; absent adds the next one (b, then c…). a can't carry changes.","type":"string","pattern":"^[a-f]$"},"name":{"description":"Defaults to Variant B, C…","type":"string","minLength":1,"maxLength":80},"deltas":{"maxItems":50,"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"],"additionalProperties":false}]},"description":"Words and images only: each change says what the page has now (before) and what this variant has (after). A text change is a copy edit of one block (a heading, a paragraph, a button's text), whole, as a reader sees it."}},"required":["variational_id","deltas"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variational":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]},"variant":{"type":"string","description":"The variant saved (b, c…)."},"revised":{"type":"boolean","description":"Its changes changed: the results start again from this revision."}},"required":["variational","variant","revised"]}},{"name":"remove_variational_variant","description":"Remove a challenger (b, c…) and split the visitors evenly again among those left. Starts the results again. A running test keeps at least one challenger with a change. Args: variational_id, key. Requires capability variationals:write.","capability":"variationals:write","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."},"key":{"type":"string","pattern":"^[a-f]$","description":"A variant: a is the page as it is (the default); b, c, d… are the challengers."}},"required":["variational_id","key"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"variational":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"domain":{"type":["string","null"]},"name":{"type":"string"},"page_url":{"type":"string"},"page_path":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","ended"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The goal's id: conversions are counted per goal."},"label":{"type":"string"},"kind":{"type":"string","enum":["click","url","event"],"description":"click on an element, reach a path, or an event the site calls."},"selector":{"type":"string"},"path":{"type":"string"},"event":{"type":"string"}},"required":["key","label","kind"]}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"revised_at":{"type":"string"},"winner":{"type":["string","null"]},"started_at":{"type":["string","null"]},"ended_at":{"type":["string","null"]},"status_set_by":{"description":"Who last started, paused or ended it.","type":["string","null"]},"status_set_at":{"type":["string","null"]},"created_by":{"type":["string","null"]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"a is the page as it is; b, c… are the challengers."},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Its share of visitors, in whole percent; every variant's add up to 100."},"deltas":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"before":{"type":"string","minLength":1,"maxLength":4000,"description":"The text as the page shows it now, exactly as a visitor reads it; one line per line on the page."},"after":{"type":"string","maxLength":4000,"description":"What it should say instead, exactly: used word for word. Empty removes the text."},"context":{"description":"Where it is on the page, in words, when the text alone could be in more than one place: 'button text, linking to /signup, in the header', 'paragraph, under the heading \"Pricing\"'.","type":"string","maxLength":400},"kind":{"type":"string","const":"text"},"tag":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,39}$","description":"The block's tag (h1, p, button, a, li…): the block is found by its tag and its text."},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200}},"required":["before","after","kind","tag"]},{"type":"object","properties":{"kind":{"type":"string","const":"image"},"before":{"type":"string","minLength":1,"maxLength":2048,"description":"The image's address as the page has it now (its src)."},"after":{"type":"string","maxLength":2048,"description":"The variant's image: an https address, or a path on the site."},"alt":{"description":"The variant image's alt text; the original's when absent.","type":"string","maxLength":500},"occurrence":{"description":"Which of the blocks (or images) with this text on the page it is, counting from 0, when there are several.","type":"integer","minimum":0,"maximum":200},"context":{"description":"Where it is on the page, in words.","type":"string","maxLength":400}},"required":["kind","before","after"]}]},"description":"Its text and image changes; a has none."},"is_default":{"type":"boolean"},"saved_by":{"type":["string","null"]},"updated_at":{"type":"string"}},"required":["key","name","weight","deltas","is_default","saved_by","updated_at"]}},"edit_url":{"type":"string","description":"The page with the in-page editor open: change its words and images there."},"tag":{"description":"The site's Visitors tag, for <head>: a small inline loader that loads v.js with the key in its address, so running variationals apply before the page shows. It never blocks the page: if Letter is slow or down the page shows within a second without the test. Older tags still work.","type":["string","null"]},"site_proven":{"type":"boolean","description":"The site's Visitors script, with its key, is on its homepage: the test can run, and edit_url opens the editor on the site. Variants can be saved either way."},"results":{"anyOf":[{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Results count this revision only: the last time a variant's changes changed."},"since":{"type":"string"},"decided_by":{"type":"string","enum":["conversion","time_on_page"]},"goal":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"weight":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"is_default":{"type":"boolean"},"views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Page views it was shown on, people only."},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Distinct visitors shown it, people only."},"seconds":{"type":"object","properties":{"mean":{"type":["number","null"]},"median":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","median","n"]},"scroll":{"type":"object","properties":{"mean":{"type":["number","null"]},"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mean","n"]},"applied":{"description":"The share of its views that showed every one of its changes (null on the original).","type":["number","null"]},"goals":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"converted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate":{"type":["number","null"]}},"required":["key","label","converted","rate"]}},"agent_views":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Views by automated browsers (AI agents): never in the numbers above."},"versus_default":{"anyOf":[{"type":"object","properties":{"conversion":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."},"goal":{"type":"string"},"lift":{"description":"Relative to the original's rate.","type":["number","null"]}},"required":["diff","low","high","p_value","chance_better","goal","lift"]},{"type":"null"}]},"seconds":{"anyOf":[{"type":"object","properties":{"diff":{"type":"number","description":"The challenger minus the original: a rate's share (0.012 = 1.2 points), or seconds."},"low":{"type":"number"},"high":{"type":"number"},"p_value":{"type":"number"},"chance_better":{"type":"number","description":"The chance the challenger is above the original (0-1)."}},"required":["diff","low","high","p_value","chance_better"]},{"type":"null"}],"description":"Mean seconds visible against the original's."},"call":{"type":"string","enum":["too_early","ahead","behind","even"],"description":"too_early until 100 visitors an arm, 30 conversions an arm for a goal, 7 days run and 90% of a challenger's views showing all its changes; then by the interval."}},"required":["conversion","seconds","call"]},{"type":"null"}]}},"required":["key","name","weight","is_default","views","visitors","seconds","scroll","applied","goals","agent_views","versus_default"]}},"leader":{"description":"The challenger ahead of the original with the best chance, if any.","type":["string","null"]},"summary":{"type":"string","description":"Where the test stands, in one sentence."}},"required":["revision","since","decided_by","goal","variants","leader","summary"]},{"type":"null"}],"description":"Null for a draft: it has never been shown."}},"required":["id","site_id","domain","name","page_url","page_path","status","goals","revision","revised_at","winner","started_at","ended_at","status_set_by","status_set_at","created_by","created_at","updated_at","variants","edit_url","tag","site_proven","results"]}},"required":["variational"]}},{"name":"ship_variational_winner","description":"SHIP A TEST'S WINNER — write the winning variant's changes into the site's code as a pull request, so the page itself says what won and the Visitors script no longer has to put it there. Its text changes go to the coding agent as copy edits (word for word, changed where the text comes from), its image swaps beside them; it starts as a site change on the test's site (start_site_change: the repo that serves the site builds it, or, with no repo, it's handed to the org's own agent). Follow it with get_repo_change / list_repo_changes. Once it merges, end the test (update_variational status ended). Args: variational_id, variant? (the test's winner when absent). Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."},"variant":{"description":"The variant to make the page; the test's winner when absent. a is the page already.","type":"string","pattern":"^[a-f]$"}},"required":["variational_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},{"type":"null"}],"description":"The repo change; null when the change was handed off (action handoff, or slack / linear with no repo)."},"handoff":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["prompt","slack","linear"]},"instructions":{"type":"string","description":"The change as instructions for a coding agent: what, where, how."},"label":{"type":"string"},"permalink":{"description":"The Slack message, when sent there.","type":["string","null"]},"issue":{"anyOf":[{"type":"object","properties":{"identifier":{"type":"string"},"url":{"type":"string"}},"required":["identifier","url"]},{"type":"null"}],"description":"The Linear issue, when filed."}},"required":["kind","instructions","label","permalink","issue"]},{"type":"null"}],"description":"Set when the change was handed off instead of built: the instructions, and where they went."},"message":{"type":"string"},"alsoOpen":{"description":"Other changes in flight on the same repo, to check for one that already does this","type":"array","items":{"type":"object","properties":{"runId":{"type":"string"},"title":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]},"startedAt":{"type":"string"}},"required":["runId","title","label","prUrl","startedAt"]}},"variant":{"type":"string","description":"The variant shipped: its changes are what the change writes into the page."}},"required":["run","handoff","message","variant"]}},{"name":"delete_variational","description":"Delete a variational, its variants and everything it measured. A running one stops at once: the page is shown as it is. To keep its results, end it instead (update_variational status ended). Args: variational_id. Requires capability variationals:write.","capability":"variationals:write","inputSchema":{"type":"object","properties":{"variational_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The variational."}},"required":["variational_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"string"}},"required":["deleted"]}},{"name":"send_repo_change_message","description":"STEER A BUILD — tell a running repo change's agent something while it works, what the letterpress terminal's prompt box does during a build: 'actually make it yellow', 'skip the blog page', 'use the photo I attached'. The agent stops where it is (nothing done so far is lost) and carries on in the same session with your message; its next words in get_repo_change_activity answer it, and the message itself shows there as a `user` line once the agent has it (within seconds). Sent while the sandbox is still starting, it goes in with the brief. Works until the agent is done (NOT_STEERABLE after that: revise_repo_change asks for more on a preview in review; otherwise start a new build). Spends from the run's own budget. Requires capability repo:write.","capability":"repo:write","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The repo-change run id (from start_repo_change or list_repo_changes)."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"What to tell the agent, in plain words, as you would say it watching over its shoulder — e.g. 'Actually make the logo yellow, not green.' It stops what it's doing and carries on in the same session with this."}},"required":["run_id","message"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sent":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"},"createdAt":{"type":"string"},"delivered":{"type":"boolean","description":"True once it's in the sandbox (the agent takes it within seconds); false while the sandbox is still starting (it goes in when the run starts)"}},"required":["id","message","createdAt","delivered"]},"message":{"type":"string"}},"required":["sent","message"]}},{"name":"set_notion_connection","description":"Validate a Notion internal-integration secret and store it encrypted for the ORG (org-wide: every Notion seer uses it). Create the integration at notion.so/my-integrations, then share the pages/databases the seer should watch with it. A Notion integration only sees pages explicitly shared with it, so a valid token with nothing shared connects but detects nothing. This is the headless alternative to the app's browser OAuth flow. Requires capability seer:write.","capability":"seer:write","inputSchema":{"type":"object","properties":{"access_token":{"type":"string","minLength":1,"maxLength":500,"description":"A Notion internal-integration secret (from notion.so/my-integrations). Validated against Notion and stored encrypted. The integration only sees pages explicitly shared with it: share the pages/databases the seer should watch before or after connecting."}},"required":["access_token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"workspace_name":{"description":"The connected Notion workspace name.","type":["string","null"]}},"required":["success","workspace_name"]}},{"name":"list_templates","description":"List the org's content templates. A template's key is what uses it: a collection's default (update_auto_pen_config template_key, which its seers inherit), a seer's own override (config.template_key), an arena's defaults.template, or run_kernel's template. There are no built-in templates: an org has exactly the templates it has authored, and a post with none is written untemplated. Requires capability template:read.","capability":"template:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Stable key seers bind to via config.template_key."},"name":{"type":"string"},"angle":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","description":"Markdown; for an open block, the author's note on what may go here."},"optional":{"description":"Drop this block when the research doesn't earn it.","type":"boolean"}},"required":["id","kind","text"]}},"updated_at":{"type":"string"}},"required":["id","key","name","angle","blocks","updated_at"]}}},"required":["templates"]}},{"name":"get_template","description":"Fetch one content template: key, name, and its full block list. Each block's markdown is the shape it asks for — see create_template for what the markdown means. Requires capability template:read.","capability":"template:read","inputSchema":{"type":"object","properties":{"template_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Template UUID (from list_templates)"}},"required":["template_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"template":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Stable key seers bind to via config.template_key."},"name":{"type":"string"},"angle":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","description":"Markdown; for an open block, the author's note on what may go here."},"optional":{"description":"Drop this block when the research doesn't earn it.","type":"boolean"}},"required":["id","kind","text"]}},"updated_at":{"type":"string"}},"required":["id","key","name","angle","blocks","updated_at"]}},"required":["template"]}},{"name":"resolve_template","description":"Turn a template into the constraints a run takes. THIS IS THE ONE PLACE A TEMPLATE BECOMES CONSTRAINTS, and both primitives that consume one read the same answer. Call it with just the template and a title to get the SHAPE — `must_include`, `gather_hints`, `form_guidance`, which is all that can be known before material exists. Call it again with `material` (and the `constraints` the first call returned) to get the PLAN — `skeleton`, `target_word_count`, `brief`, `brief_json`, which is what run_kernel takes, with word budgets allocated across the sections and optional sections the material did not earn already dropped. ALWAYS PASS `constraints` BACK on the second call: resolving twice is a model call twice and two different sets of section labels, and the writer writes each section under its label while assembly matches output back by that label — so a second resolution files sections under headings the outline never planned. If you only want one write and no research, skip this and pass `template` straight to run_kernel; it resolves through this same function and the finished run comes back assembled. Costs no run credit on any balance. Requires capability template:read.","capability":"template:read","inputSchema":{"type":"object","properties":{"key":{"description":"A template key from list_templates. Mutually exclusive with `blocks`.","type":"string","minLength":1,"maxLength":80},"blocks":{"description":"A template authored in this request, for a caller who stores none. The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back.","minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]}},"title":{"description":"The working headline. What a generated section label is made concrete about — a block reading 'how teams end up here' becomes 'How the task queue starts to show its limits' rather than 'Background'.","type":"string","maxLength":200},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":80},"additionalProperties":{"type":"string","minLength":1,"maxLength":400},"description":"Values for the template's {Variables}, e.g. {\"Queue\": \"Sidekiq\"}. Matched case-insensitively against the variable names in the template. A declared value beats anything derived, and is never sent to a model to be guessed."},"constraints":{"description":"An answer resolve_template already gave for this template. Pass it back and nothing is re-decided — which matters because the kernel writes each section under its label and assembly matches output back by that label, so a second resolution files sections under headings the outline never planned. Always pass this through when the same template drives both a research run and a write.","type":"array","items":{"type":"object","properties":{"blockId":{"type":"string"},"heading":{"type":["string","null"]},"covers":{"type":"string"},"form":{"type":"string","enum":["passage","enumeration","sequence","matrix","literal","open"]},"required":{"type":"boolean"},"label":{"type":"string","description":"What the research plans for and the writer writes under."},"printHeading":{"type":"boolean","description":"Whether the finished article prints a heading here."},"subjects":{"type":"array","items":{"type":"string"}},"dimensions":{"type":"array","items":{"type":"string"}},"layout":{"type":"string","enum":["subjects-as-columns","subjects-as-rows"]},"literal":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"variables":{"type":"array","items":{"type":"string"}}},"required":["blockId","heading","covers","form","required","label","printHeading"]}},"material":{"description":"The material the sections will be written from — an outline, notes, research, a draft. OMIT IT and you get the SHAPE only: which sections, in what form, and what the research must cover, which is everything that can honestly be known before the material exists. SUPPLY IT and you additionally get the plan against it: the skeleton, the word budget, the brief and brief_json that run_kernel takes, with optional sections the material did not earn already dropped.","type":"string","maxLength":200000},"brand":{"description":"The brand a {Brand}-style variable names. Omitted = this organization's own, when it has one; an org with no positioning simply resolves no brand variable rather than failing.","type":"object","properties":{"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"one_liner":{"anyOf":[{"type":"string","maxLength":400},{"type":"null"}]}}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"template_key":{"type":"string","description":"The key resolved, or 'inline' for a template sent as blocks."},"constraints":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"description":"THE ANSWER. Pass this back as `template.constraints` on run_kernel and nothing is re-decided — the labels stay what this call chose, which is what keeps the outline's sections and the article's sections the same sections."},"must_include":{"type":"array","items":{"type":"string"},"description":"sections the outline must cover."},"gather_hints":{"type":"array","items":{"type":"string"},"description":"Phrases the gather step should look for."},"form_guidance":{"description":"How the outline should be shaped.","type":["string","null"]},"skeleton":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"description":"run_kernel's `skeleton`: the shape as data. Empty until `material` is supplied."},"target_word_count":{"description":"run_kernel's `target_word_count`: the prose budget.","type":["number","null"]},"brief":{"description":"run_kernel's `input`: the composed brief, material included.","type":["string","null"]},"brief_json":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"run_kernel's `brief_json`: the same inputs as data, which the service's entity and figure gates check output against."},"spans":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"description":"What assemble_template matches the kernel's output back to. Empty until `material` is supplied."},"variables":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"What each {Variable} became."},"unresolved":{"type":"array","items":{"type":"string"},"description":"Variables nothing could fill. Harmless where they sit only in a block's prose direction — the writer reads those and works around them."},"degraded":{"type":"boolean","description":"True when the labelling call did not land, so every block kept the author's own words."}},"required":["template_key","constraints","must_include","gather_hints","form_guidance","skeleton","target_word_count","brief","brief_json","spans","variables","unresolved","degraded"]}},{"name":"assemble_template","description":"Turn a templated kernel run's raw output into the article. A templated run's output is NOT the article: the kernel is asked for a `## ` heading per section including the ones the template never asked to print, because that is what it does reliably and what makes matching the output back exact. This drops those, restores any quoted literals byte for byte, and renders the result. Pass the `spans` from the resolve_template call that produced the run, and the `article` from the finished get_kernel_endpoint_run. You do NOT need this if you passed `template` to run_kernel — that path assembles on the poll and hands you the article directly (`format: \"html\"`). Deterministic, no model call, costs nothing. Requires capability template:read.","capability":"template:read","inputSchema":{"type":"object","properties":{"spans":{"minItems":1,"type":"array","items":{"type":"object","properties":{"blockId":{"type":"string"},"heading":{"type":["string","null"]},"covers":{"type":"string"},"form":{"type":"string","enum":["passage","enumeration","sequence","matrix","literal","open"]},"required":{"type":"boolean"},"label":{"type":"string","description":"What the research plans for and the writer writes under."},"printHeading":{"type":"boolean","description":"Whether the finished article prints a heading here."},"subjects":{"type":"array","items":{"type":"string"}},"dimensions":{"type":"array","items":{"type":"string"}},"layout":{"type":"string","enum":["subjects-as-columns","subjects-as-rows"]},"literal":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"variables":{"type":"array","items":{"type":"string"}},"key":{"type":"string","description":"Stable per-span key."},"wordTarget":{"type":["number","null"]},"itemMax":{"type":["number","null"]},"items":{"type":"array","items":{"type":"string"}}},"required":["blockId","heading","covers","form","required","label","printHeading","key"]},"description":"The `spans` resolve_template returned for this run. They are what output is matched back to."},"article":{"type":"string","minLength":1,"maxLength":400000,"description":"The kernel's raw output for that run — the `article` on a finished get_kernel_endpoint_run."}},"required":["spans","article"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"article":{"type":"string","description":"The finished article: the scaffolding headings the template never asked to print removed, literals restored, rendered as HTML."}},"required":["article"]}},{"name":"create_template","description":"Create a content template. The org-unique key is generated server-side and is what seers bind to. A template constrains the SHAPE of a post and never says what it is about: the research agent decides the content, the writing kernel the prose. The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back. Requires capability template:write.","capability":"template:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"angle":{"default":"","type":"string","maxLength":400},"blocks":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]}}},"required":["name","blocks"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"template":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Stable key seers bind to via config.template_key."},"name":{"type":"string"},"angle":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","description":"Markdown; for an open block, the author's note on what may go here."},"optional":{"description":"Drop this block when the research doesn't earn it.","type":"boolean"}},"required":["id","kind","text"]}},"updated_at":{"type":"string"}},"required":["id","key","name","angle","blocks","updated_at"]}},"required":["template"]}},{"name":"update_template","description":"Replace a template's name and full block list (the key never changes, so bound seers follow the edit). Blocks are a full replacement, not a patch — send every block you want to keep. Requires capability template:write.","capability":"template:write","inputSchema":{"type":"object","properties":{"template_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Template UUID (from list_templates)"},"name":{"type":"string","minLength":1,"maxLength":80,"description":"Template name"},"angle":{"description":"Optional. Legacy editorial angle; omit it — a post's angle comes from its research direction.","type":"string","maxLength":400},"blocks":{"minItems":1,"maxItems":24,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9][A-Za-z0-9_-]*$"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","maxLength":4000},"optional":{"type":"boolean"}},"required":["id","kind","text"]},"description":"The template's blocks, in article order: [{ id, kind: 'formatted'|'open', text (markdown), optional? }]. A template constrains the SHAPE of a post; the research agent decides the content and the writing kernel the prose. In a 'formatted' block the markdown IS the shape — a `## ` line is that section's heading, and what follows decides its form: bare prose → paragraphs (and no list is added); `- item` → a bulleted list; `1. item` → a numbered list, in order; a table skeleton (`| | A | B |`) → a comparison the prose must state (a rendered table is coming; today the comparison reaches the reader as prose); `> quoted text` → reproduced in the article word for word. Leave list items empty (`- `) to say 'a list here' without dictating its contents: HOW MANY items a list has is decided from the material, never by the template. An 'open' block asks for coverage in your own words and hands the SHAPE back to the engine — it decides whether that section becomes prose or a list, and how many sections it turns into. That is a waiver, not a convenience: if the request names a shape at all (\"prose only\", \"a list\", \"numbered steps\", \"this exact sentence\"), write a formatted block that says it. Reach for 'open' only where you genuinely do not mind what comes back."}},"required":["template_id","name","blocks"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"template":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Stable key seers bind to via config.template_key."},"name":{"type":"string"},"angle":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["formatted","open"]},"text":{"type":"string","description":"Markdown; for an open block, the author's note on what may go here."},"optional":{"description":"Drop this block when the research doesn't earn it.","type":"boolean"}},"required":["id","kind","text"]}},"updated_at":{"type":"string"}},"required":["id","key","name","angle","blocks","updated_at"]}},"required":["template"]}},{"name":"delete_template","description":"Delete a template. Nothing checks who uses it and there is no built-in to fall back to: a collection default or seer still bound to its key writes an UNTEMPLATED post on its next run — a normal article with no shape constraints, not a failure. Posts already composed from it are unaffected, since each snapshots its own shape at produce time. Requires capability template:write.","capability":"template:write","inputSchema":{"type":"object","properties":{"template_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Template UUID (from list_templates)"}},"required":["template_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}},{"name":"run_enhance_pass","description":"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. Requires capability article:write.","capability":"article:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The post to run the pass on."},"pass":{"type":"string","enum":["visuals","internal_links","external_links","metadata"],"description":"visuals | internal_links | external_links | metadata"}},"required":["article_id","pass"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run_id":{"description":"The run's record (get_enhance_run); null when the pass did not run.","type":["string","null"]},"pass":{"type":"string","enum":["visuals","internal_links","external_links","metadata"]},"status":{"type":"string","enum":["queued","running","done","skipped","error"]},"outcome":{"anyOf":[{"type":"string","enum":["added","none","skipped","failed"]},{"type":"null"}],"description":"added: it changed the post; none: nothing to add; skipped: it could not run; failed: it errored."},"detail":{"type":"string"},"warning":{"type":["string","null"]},"changed":{"type":"boolean","description":"True when the post changed."},"skip_reason":{"description":"Why a pass could not run: no_sitemap | sitemap_unreadable (fix the collection's sitemap).","type":["string","null"]},"applied":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"description":"What it added: links as {anchor, url, replaces, context}, visuals as {id, kind, section}, metadata as {kind, output}."},"version":{"description":"The enhancer version that ran (`<name>@<version>`).","type":["string","null"]},"models":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"The model in each of the pass's seats."}},"required":["run_id","pass","status","outcome","detail","warning","changed","skip_reason","applied","version","models"]}},{"name":"get_enhance_run","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run (list_enhance_runs, or an Auto-Pen step's `run.id`)."}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":["string","null"]},"collection_id":{"type":["string","null"]},"auto_pen_id":{"description":"The Auto-Pen this run was a step of.","type":["string","null"]},"pass":{"type":"string","description":"visuals | internal_links | external_links | metadata"},"caller":{"type":"string","description":"auto-pen | article | api | relink | replace-visuals"},"status":{"type":"string"},"outcome":{"description":"added: it changed the post; none: nothing to add; skipped: it could not run; failed: it errored.","type":["string","null"]},"skip_reason":{"description":"Why a pass could not run: no_sitemap | sitemap_unreadable (fix the collection's sitemap).","type":["string","null"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]},"error":{"type":["string","null"]},"enhancer_version_id":{"type":["string","null"]},"version_label":{"type":["string","null"]},"models":{"description":"Seat → model."},"settings":{"description":"The pass's settings from the enhance file."},"applied":{"description":"{ items: what it added, stats?: what the visuals pass proposed and lost }."},"version_before":{"description":"The post version (article_history id) the pass read.","type":["string","null"]},"version_after":{"description":"The post version it left; equal to version_before when unchanged.","type":["string","null"]},"usage":{"description":"Tokens per model: { model: { calls, input, output } }."},"latency_ms":{"type":["number","null"]},"build":{"description":"The deploy that ran it.","type":["string","null"]},"created_at":{"type":"string"},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","article_id","collection_id","auto_pen_id","pass","caller","status","outcome","skip_reason","detail","warning","error","enhancer_version_id","version_label","models","settings","applied","version_before","version_after","usage","latency_ms","build","created_at","started_at","finished_at"]}},"required":["run"]}},{"name":"list_enhance_runs","description":"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. Requires capability article:read.","capability":"article:read","inputSchema":{"type":"object","properties":{"article_id":{"description":"One post's runs.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"auto_pen_id":{"description":"One Auto-Pen's runs (get_auto_pen_status).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"collection_id":{"description":"One collection's runs.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"pass":{"type":"string","enum":["visuals","internal_links","external_links","metadata"],"description":"visuals | internal_links | external_links | metadata"},"outcome":{"description":"added: it changed the post; none: nothing to add; skipped: it could not run (see skip_reason); failed.","type":"string","enum":["added","none","skipped","failed"]},"since":{"description":"Only runs created after this instant (ISO 8601).","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"limit":{"description":"Default 50.","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"article_id":{"type":["string","null"]},"collection_id":{"type":["string","null"]},"auto_pen_id":{"description":"The Auto-Pen this run was a step of.","type":["string","null"]},"pass":{"type":"string","description":"visuals | internal_links | external_links | metadata"},"caller":{"type":"string","description":"auto-pen | article | api | relink | replace-visuals"},"status":{"type":"string"},"outcome":{"description":"added: it changed the post; none: nothing to add; skipped: it could not run; failed: it errored.","type":["string","null"]},"skip_reason":{"description":"Why a pass could not run: no_sitemap | sitemap_unreadable (fix the collection's sitemap).","type":["string","null"]},"detail":{"type":["string","null"]},"warning":{"type":["string","null"]},"error":{"type":["string","null"]},"enhancer_version_id":{"type":["string","null"]},"version_label":{"type":["string","null"]},"models":{"description":"Seat → model."},"settings":{"description":"The pass's settings from the enhance file."},"applied":{"description":"{ items: what it added, stats?: what the visuals pass proposed and lost }."},"version_before":{"description":"The post version (article_history id) the pass read.","type":["string","null"]},"version_after":{"description":"The post version it left; equal to version_before when unchanged.","type":["string","null"]},"usage":{"description":"Tokens per model: { model: { calls, input, output } }."},"latency_ms":{"type":["number","null"]},"build":{"description":"The deploy that ran it.","type":["string","null"]},"created_at":{"type":"string"},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","article_id","collection_id","auto_pen_id","pass","caller","status","outcome","skip_reason","detail","warning","error","enhancer_version_id","version_label","models","settings","applied","version_before","version_after","usage","latency_ms","build","created_at","started_at","finished_at"]}}},"required":["items"]}},{"name":"get_enhancer_file","description":"The enhance file a collection's passes run, as ONE complete, versioned JSON file — the same pattern as a kernel, arena and shredder file: the model in each pass's seats (visuals: brief, draw, pick, table; internalLinks: rank, place; externalLinks: source, verify; metadata, and the model a shredded post's metadata uses) and each pass's settings (the internal-link ceiling, sitemap candidates, source types, citations per source, visuals per post). Whether a pass runs stays the collection's policy (update_auto_pen_config). Says where the file comes from (your own copy, a pinned platform version, or ours) and whether we have released a newer version than your copy's base (`offer`). Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection whose passes the file runs (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","description":"The collection."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete enhance file the collection's passes run: `model` (the default seat), `visuals` (models: brief, draw, pick, table; maxPerPost), `internalLinks` (models: rank, place; maxPerPost; candidates), `externalLinks` (models: source, verify; sourceTypes; citesPerSource; targetCitations), `metadata` (model; shreddedModel)."},"source":{"type":"string","enum":["own","pinned","platform_live"],"description":"own: your copy; pinned: a platform version you pinned; platform_live: follows ours."},"version":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"],"description":"The stored version the collection runs."},"platform_live":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"offer":{"anyOf":[{"type":"object","properties":{"from":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},{"type":"null"}]},"to":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"What changed on our side between your base and our live version."},"kept":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Your own changes, re-applied over the new base when you accept."}},"required":["from","to","changes","kept"]},{"type":"null"}],"description":"A newer platform version than your copy's base; null when there is none."}},"required":["endpoint_id","file","source","version","platform_live","offer"]}},{"name":"save_enhancer_file","description":"Save a new version of a collection's enhance file and run every pass with it from now on. Send `file` (a whole file; only `name` is required, the rest is completed) or `patch` (only the fields to change, laid over the file the collection runs now, e.g. {\"internalLinks\":{\"maxPerPost\":3}}). The first save makes your own copy of our file; later platform releases arrive as an offer (get_enhancer_file `offer`, accept_enhancer_offer). A save that would run exactly like the current file is refused. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection to save a new version of the file for."},"file":{"description":"A whole file. Only `name` is required; the rest is completed.","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"The enhancer's name."},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"model":{"description":"The model every seat follows unless it names its own. Default: claude-sonnet-4-6.","type":"string","minLength":1,"maxLength":120},"visuals":{"type":"object","properties":{"models":{"type":"object","properties":{"brief":{"type":"string","minLength":1,"maxLength":120},"draw":{"type":"string","minLength":1,"maxLength":120},"pick":{"type":"string","minLength":1,"maxLength":120},"table":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"brief proposes diagrams, draw authors each one, pick chooses one visual per section, table builds a table the template commissioned."},"maxPerPost":{"description":"Visuals placed in one post. Default 3.","type":"integer","minimum":0,"maximum":10}},"additionalProperties":false},"internalLinks":{"type":"object","properties":{"models":{"type":"object","properties":{"rank":{"type":"string","minLength":1,"maxLength":120},"place":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"rank narrows the sitemap to the pages worth linking, place chooses each anchor and its destination."},"maxPerPost":{"description":"The ceiling of internal links in one post, counting the ones it already has. Default 5.","type":"integer","minimum":0,"maximum":20},"candidates":{"description":"Sitemap pages the placer chooses among. Default 25.","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"externalLinks":{"type":"object","properties":{"models":{"type":"object","properties":{"source":{"type":"string","minLength":1,"maxLength":120},"verify":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"source finds the claims worth citing and their sources, verify reads each source to confirm it says so."},"sourceTypes":{"description":"The kinds of source to look for. Default blog posts, documentation, research papers.","minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["blog_posts","documentation","research_papers"]}},"citesPerSource":{"description":"Times one research source may be cited in a post. Default 3.","type":"integer","minimum":1,"maximum":10},"targetCitations":{"description":"Citations the source seat tries to find in one post, without lowering the bar for what's citable. Default 0 (no target — cite what clears the bar).","type":"integer","minimum":0,"maximum":10}},"additionalProperties":false},"metadata":{"type":"object","properties":{"model":{"description":"The model that writes JSON-LD, meta tags and llms.txt.","type":"string","minLength":1,"maxLength":120},"shreddedModel":{"description":"The model on a post whose prose was shredded, so the page head does not re-imprint the watermark. Default mistral-large-latest.","type":"string","minLength":1,"maxLength":120}},"additionalProperties":false}},"required":["name"],"additionalProperties":false},"patch":{"description":"Only the fields to change, over the file the collection runs now — e.g. {\"internalLinks\":{\"maxPerPost\":3}}.","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"The enhancer's name."},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"model":{"description":"The model every seat follows unless it names its own. Default: claude-sonnet-4-6.","type":"string","minLength":1,"maxLength":120},"visuals":{"type":"object","properties":{"models":{"type":"object","properties":{"brief":{"type":"string","minLength":1,"maxLength":120},"draw":{"type":"string","minLength":1,"maxLength":120},"pick":{"type":"string","minLength":1,"maxLength":120},"table":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"brief proposes diagrams, draw authors each one, pick chooses one visual per section, table builds a table the template commissioned."},"maxPerPost":{"description":"Visuals placed in one post. Default 3.","type":"integer","minimum":0,"maximum":10}},"additionalProperties":false},"internalLinks":{"type":"object","properties":{"models":{"type":"object","properties":{"rank":{"type":"string","minLength":1,"maxLength":120},"place":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"rank narrows the sitemap to the pages worth linking, place chooses each anchor and its destination."},"maxPerPost":{"description":"The ceiling of internal links in one post, counting the ones it already has. Default 5.","type":"integer","minimum":0,"maximum":20},"candidates":{"description":"Sitemap pages the placer chooses among. Default 25.","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"externalLinks":{"type":"object","properties":{"models":{"type":"object","properties":{"source":{"type":"string","minLength":1,"maxLength":120},"verify":{"type":"string","minLength":1,"maxLength":120}},"additionalProperties":false,"description":"source finds the claims worth citing and their sources, verify reads each source to confirm it says so."},"sourceTypes":{"description":"The kinds of source to look for. Default blog posts, documentation, research papers.","minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["blog_posts","documentation","research_papers"]}},"citesPerSource":{"description":"Times one research source may be cited in a post. Default 3.","type":"integer","minimum":1,"maximum":10},"targetCitations":{"description":"Citations the source seat tries to find in one post, without lowering the bar for what's citable. Default 0 (no target — cite what clears the bar).","type":"integer","minimum":0,"maximum":10}},"additionalProperties":false},"metadata":{"type":"object","properties":{"model":{"description":"The model that writes JSON-LD, meta tags and llms.txt.","type":"string","minLength":1,"maxLength":120},"shreddedModel":{"description":"The model on a post whose prose was shredded, so the page head does not re-imprint the watermark. Default mistral-large-latest.","type":"string","minLength":1,"maxLength":120}},"additionalProperties":false}},"additionalProperties":false},"note":{"description":"Why, for the version history.","type":"string","maxLength":500}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","description":"The collection."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete enhance file the collection's passes run: `model` (the default seat), `visuals` (models: brief, draw, pick, table; maxPerPost), `internalLinks` (models: rank, place; maxPerPost; candidates), `externalLinks` (models: source, verify; sourceTypes; citesPerSource; targetCitations), `metadata` (model; shreddedModel)."},"source":{"type":"string","enum":["own","pinned","platform_live"],"description":"own: your copy; pinned: a platform version you pinned; platform_live: follows ours."},"version":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"],"description":"The stored version the collection runs."},"platform_live":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"offer":{"anyOf":[{"type":"object","properties":{"from":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},{"type":"null"}]},"to":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"What changed on our side between your base and our live version."},"kept":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Your own changes, re-applied over the new base when you accept."}},"required":["from","to","changes","kept"]},{"type":"null"}],"description":"A newer platform version than your copy's base; null when there is none."}},"required":["endpoint_id","file","source","version","platform_live","offer"]}},{"name":"list_enhancer_versions","description":"A collection's enhance file history: your copy's versions (newest first, the live one marked) and our released platform versions, which you can pin (pin_enhancer_version). Every pass run records the version it ran (get_enhance_run `version_label`). Requires capability collection:read.","capability":"collection:read","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection whose passes the file runs (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string"},"own":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"description":"Your copy's versions, newest first."},"platform":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"description":"Our released versions (live and retired), newest first."}},"required":["endpoint_id","own","platform"]}},{"name":"pin_enhancer_version","description":"Pin a collection to one of our released enhancer versions instead of following our live one (version_id), or unpin it (null). A collection with its own copy runs its copy; the pin applies when it has none. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection to pin."},"version_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"A platform enhancer version to pin (list_enhancer_versions `platform`), or null to follow our live one."}},"required":["collection_id","version_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","description":"The collection."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete enhance file the collection's passes run: `model` (the default seat), `visuals` (models: brief, draw, pick, table; maxPerPost), `internalLinks` (models: rank, place; maxPerPost; candidates), `externalLinks` (models: source, verify; sourceTypes; citesPerSource; targetCitations), `metadata` (model; shreddedModel)."},"source":{"type":"string","enum":["own","pinned","platform_live"],"description":"own: your copy; pinned: a platform version you pinned; platform_live: follows ours."},"version":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"],"description":"The stored version the collection runs."},"platform_live":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"offer":{"anyOf":[{"type":"object","properties":{"from":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},{"type":"null"}]},"to":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"What changed on our side between your base and our live version."},"kept":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Your own changes, re-applied over the new base when you accept."}},"required":["from","to","changes","kept"]},{"type":"null"}],"description":"A newer platform version than your copy's base; null when there is none."}},"required":["endpoint_id","file","source","version","platform_live","offer"]}},{"name":"accept_enhancer_offer","description":"Accept our newer platform enhancer version into your collection's copy: a new live version of your file = our live file with your own changes (`offer.kept`) laid back over it. Refused when there is no offer. Requires capability collection:write.","capability":"collection:write","inputSchema":{"type":"object","properties":{"collection_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The collection whose passes the file runs (list_collections)."}},"required":["collection_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"endpoint_id":{"type":"string","description":"The collection."},"file":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The complete enhance file the collection's passes run: `model` (the default seat), `visuals` (models: brief, draw, pick, table; maxPerPost), `internalLinks` (models: rank, place; maxPerPost; candidates), `externalLinks` (models: source, verify; sourceTypes; citesPerSource; targetCitations), `metadata` (model; shreddedModel)."},"source":{"type":"string","enum":["own","pinned","platform_live"],"description":"own: your copy; pinned: a platform version you pinned; platform_live: follows ours."},"version":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"],"description":"The stored version the collection runs."},"platform_live":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"number"},"label":{"type":"string","description":"`<enhancer>@<version>`, as runs record it."},"status":{"type":"string","enum":["live","candidate","retired"]},"note":{"type":["string","null"]},"based_on":{"description":"A copy's base: the platform version it was made from.","type":["string","null"]},"created_at":{"type":"string"},"promoted_at":{"type":["string","null"]}},"required":["id","version","label","status","note","based_on","created_at","promoted_at"]},"offer":{"anyOf":[{"type":"object","properties":{"from":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},{"type":"null"}]},"to":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"What changed on our side between your base and our live version."},"kept":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"from":{},"to":{}},"required":["path","from","to"]},"description":"Your own changes, re-applied over the new base when you accept."}},"required":["from","to","changes","kept"]},{"type":"null"}],"description":"A newer platform version than your copy's base; null when there is none."}},"required":["endpoint_id","file","source","version","platform_live","offer"]}},{"name":"letterprove_get_vendor_link","description":"Does this org already have a Letterprove vendor linked? Ask this before any other letterprove_* tool. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"linked":{"type":"boolean","description":"Whether this org already has a Letterprove vendor."},"slug":{"type":["string","null"]},"domain":{"type":["string","null"]}},"required":["linked"]}},{"name":"letterprove_create_vendor","description":"Create and link this org's Letterprove vendor (the site behind its Letterprove script, for the signed proof). Args: name, domain. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"domain":{"type":"string","minLength":1},"contact_email":{"description":"Where Letterprove emails alerts: DNS record gone, sign-ins stopped, Stripe not syncing.","type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["name","domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"linked":{"type":"boolean","const":true},"slug":{"type":"string"},"domain":{"type":"string"}},"required":["linked","slug","domain"]}},{"name":"letterprove_get_script","description":"This org's Letterprove script (proofs_snippet, for the product's sign-in pages; counts sign-ins for the signed proof) with install recipes, whether the proofs module is on, and install progress (proofs_script_seen_at, first sign-in). The Visitors script is a separate product: visitors_get_script. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"key":{"type":"string","description":"The publishable script key. It ships in the page by design."},"snippet":{"type":"string","description":"Legacy alias of proofs_snippet."},"proofs_snippet":{"description":"The Letterprove script tag, for pages where users sign in. Counts sign-ins for the proof.","type":"string"},"recipes":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}]},"agent_kit":{"anyOf":[{"type":"object","properties":{"footer_html":{"type":"string"},"llms_txt":{"type":"string"},"json_ld":{"type":"string"}},"required":["footer_html","llms_txt","json_ld"]},{"type":"null"}]},"modules":{"type":"object","properties":{"proofs":{"type":"boolean"}},"required":["proofs"]},"status":{"type":"object","properties":{"script_seen_at":{"description":"When the script tag first loaded on the site.","type":["string","null"]},"last_script_seen_at":{"type":["string","null"]},"first_signin_at":{"type":["string","null"]},"domain_verified":{"type":"boolean"},"proofs_script_seen_at":{"description":"When the Letterprove script first loaded.","type":["string","null"]}},"required":["script_seen_at","last_script_seen_at","first_signin_at","domain_verified"]}},"required":["key","snippet","modules","status"]}},{"name":"letterprove_set_script_modules","description":"Turn the Letterprove script's proofs module on or off (counting sign-ins for the signed proof). Args: proofs. AI agents and de-anonymization are Visitors: visitors_set_modules and visitors_set_identification. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"proofs":{"description":"Count sign-ins for the public proof.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"key":{"type":"string","description":"The publishable script key. It ships in the page by design."},"snippet":{"type":"string","description":"Legacy alias of proofs_snippet."},"proofs_snippet":{"description":"The Letterprove script tag, for pages where users sign in. Counts sign-ins for the proof.","type":"string"},"recipes":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}]},"agent_kit":{"anyOf":[{"type":"object","properties":{"footer_html":{"type":"string"},"llms_txt":{"type":"string"},"json_ld":{"type":"string"}},"required":["footer_html","llms_txt","json_ld"]},{"type":"null"}]},"modules":{"type":"object","properties":{"proofs":{"type":"boolean"}},"required":["proofs"]},"status":{"type":"object","properties":{"script_seen_at":{"description":"When the script tag first loaded on the site.","type":["string","null"]},"last_script_seen_at":{"type":["string","null"]},"first_signin_at":{"type":["string","null"]},"domain_verified":{"type":"boolean"},"proofs_script_seen_at":{"description":"When the Letterprove script first loaded.","type":["string","null"]}},"required":["script_seen_at","last_script_seen_at","first_signin_at","domain_verified"]}},"required":["key","snippet","modules","status"]}},{"name":"letterprove_get_proof_summary","description":"Letterprove: this vendor's signed proof, read back (works while private): tier and tier_name, companies_observed (companies whose users signed in to the product) and sessions_30d (sign-in sessions). Not site visitors: that is Visitors (visitors_get_people). Output: drop reported_history (read it from letterprove_get_posthog_connection) or have Letterprove return it. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"slug":{"type":"string"},"tier":{"type":"number"},"tier_name":{"type":"string"},"companies_observed":{"type":"number"},"sessions_30d":{"type":"number"},"last_attested":{"type":["string","null"]},"reported_history":{"description":"Vendor-reported history from the vendor's own PostHog, when it is on the proof.","anyOf":[{"type":"object","properties":{"companies":{"type":"number"},"since":{"type":["string","null"]},"active_companies_30d":{"type":"number"}},"required":["companies","since","active_companies_30d"]},{"type":"null"}]}},"required":["slug","tier","tier_name","companies_observed","sessions_30d","last_attested"]}},{"name":"letterprove_get_read_usage","description":"This vendor's AI-agent proof reads this month so far and last month, what each comes to at list price, and the pricing bands (first 25 a month free, then $0.20 a read, dropping to $0.08 past 500; only reads from an agent's published addresses count). Billed monthly to this org, except months that start before a Letterprove beta exemption. amount_cents: \"List-price amount under `pricing`; not invoiced while the org's beta exemption covers the month.\" Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"current":{"type":"object","properties":{"billing_month":{"type":"string","description":"First of the month, YYYY-MM-DD (UTC)."},"reads":{"type":"number","description":"AI-agent reads of this vendor's signed proofs in the month."},"tier2_reads":{"type":"number"},"tier3_reads":{"type":"number"},"amount_cents":{"type":"number","description":"What the month comes to under `pricing`; billed monthly to this org."},"counted_at":{"description":"When month-to-date was last counted (daily).","type":["string","null"]}},"required":["billing_month","reads","tier2_reads","tier3_reads","amount_cents","counted_at"]},"previous":{"type":"object","properties":{"billing_month":{"type":"string","description":"First of the month, YYYY-MM-DD (UTC)."},"reads":{"type":"number","description":"AI-agent reads of this vendor's signed proofs in the month."},"tier2_reads":{"type":"number"},"tier3_reads":{"type":"number"},"amount_cents":{"type":"number","description":"What the month comes to under `pricing`; billed monthly to this org."}},"required":["billing_month","reads","tier2_reads","tier3_reads","amount_cents"]},"pricing":{"type":"object","properties":{"free_reads":{"type":"number"},"tier2_ceiling":{"type":"number"},"tier2_rate_cents":{"type":"number"},"tier3_rate_cents":{"type":"number"}},"required":["free_reads","tier2_ceiling","tier2_rate_cents","tier3_rate_cents"]}},"required":["current","previous","pricing"]}},{"name":"letterprove_get_status","description":"Letterprove (Proofs) only: whether the Letterprove script has loaded on the vendor's product (installed) and how many sign-in events it sent in the last 24h (receiving, count), plus publish/listing state, dropped hosts and how the domain is proven (domain_proof: none, site or dns). Sign-ins only, not page traffic or visitor identification: for those use Visitors (visitors_get_people, get_site_metrics). Output additions: count: \"Sign-in events in the last 24h. 0 says nothing about site traffic.\"; domain_proof: \"none | site | dns\"; domain_verified: \"True only for DNS proof.\" Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"receiving":{"type":"boolean","description":"Whether the tracking script has sent an event in the last 24h."},"installed":{"type":"boolean","description":"Whether the tracking script has ever successfully checked in."},"count":{"type":"number"},"published":{"type":"boolean","description":"Whether this vendor's proofs are public. Collection runs either way."},"published_at":{"description":"When they went public. Null while private.","type":["string","null"]},"listed":{"description":"Whether the proof appears in Letterprove's directory (discovery, sitemap, llms.txt). False = unlisted: still served at its URL, marked noindex.","type":"boolean"},"ever_received_at":{"description":"When the first sign-in event ever arrived. Null until one has.","type":["string","null"]},"last_received_at":{"description":"When the most recent sign-in event arrived.","type":["string","null"]},"rejected_origins":{"description":"Hosts that sent sign-ins in the last 24h which were dropped because they aren't the verified domain or an accepted host. Counts only.","type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"events_24h":{"type":"number"},"last_at":{"type":["string","null"]},"addable":{"type":"boolean","description":"True when letterprove_add_host would accept this host."},"reason":{"type":["string","null"]}},"required":["host","events_24h","last_at","addable"]}}},"required":["receiving","installed","count","published","published_at"]}},{"name":"letterprove_get_install_snippet","description":"Letterprove: the script tag and identify() recipes for the product's sign-in pages (not the marketing site), the agent kit, and the footer badge (show it only when badge.live). Prefer letterprove_get_script, which also returns install progress. Not the Visitors script: visitors_get_script. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"snippet":{"type":"string"},"origin":{"type":"string"},"badge":{"type":"object","properties":{"html":{"type":"string"},"href":{"type":"string"},"live":{"type":"boolean"}},"required":["html","href","live"]},"server_snippet":{"description":"Legacy: a server-side forwarder for AI-agent requests. Agent visits are Visitors now (visitors_get_script).","type":"object","properties":{"endpoint":{"type":"string"},"nextjs_middleware":{"type":"string"},"generic":{"type":"string"}},"required":["endpoint","nextjs_middleware","generic"]},"recipes":{"description":"Install recipes keyed by stack: stub, nextjs, plain_js, clerk, auth0, supabase.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"agent_kit":{"description":"Snippets for the vendor's own site so AI agents find the proof: a footer link, an llms.txt section, and a complete JSON-LD <script> tag.","type":"object","properties":{"footer_html":{"type":"string"},"llms_txt":{"type":"string"},"json_ld":{"type":"string"}},"required":["footer_html","llms_txt","json_ld"]}},"required":["snippet","origin"]}},{"name":"letterprove_set_custom_domain","description":"Serve this vendor's public proof page on its own domain, e.g. proof.acme.com. Must be a subdomain of the verified domain. Returns the CNAME record to add. Args: domain. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"description":"A subdomain of the vendor's verified domain, e.g. proof.acme.com or success.acme.com."}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"CNAME"},"name":{"type":"string"},"value":{"type":"string"}},"required":["type","name","value"]},{"type":"null"}]},"extra_records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"domain":{"type":"string"},"value":{"type":"string"}},"required":["type","domain","value"]}},"configured":{"type":"boolean"},"verified":{"type":"boolean"},"published":{"type":"boolean"},"live":{"type":"boolean"},"verified_at":{"type":["string","null"]},"message":{"type":"string"}},"required":["domain","url","dns","extra_records","configured","verified","published","live","verified_at","message"]}},{"name":"letterprove_check_custom_domain","description":"Check whether this vendor's proof domain DNS is set up, and record the result. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"CNAME"},"name":{"type":"string"},"value":{"type":"string"}},"required":["type","name","value"]},{"type":"null"}]},"extra_records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"domain":{"type":"string"},"value":{"type":"string"}},"required":["type","domain","value"]}},"configured":{"type":"boolean"},"verified":{"type":"boolean"},"published":{"type":"boolean"},"live":{"type":"boolean"},"verified_at":{"type":["string","null"]},"message":{"type":"string"}},"required":["domain","url","dns","extra_records","configured","verified","published","live","verified_at","message"]}},{"name":"letterprove_get_custom_domain","description":"This vendor's proof domain and its last recorded state, without re-checking DNS. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":["string","null"]},"url":{"type":["string","null"]},"dns":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"CNAME"},"name":{"type":"string"},"value":{"type":"string"}},"required":["type","name","value"]},{"type":"null"}]},"extra_records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"domain":{"type":"string"},"value":{"type":"string"}},"required":["type","domain","value"]}},"configured":{"type":"boolean"},"verified":{"type":"boolean"},"published":{"type":"boolean"},"live":{"type":"boolean"},"verified_at":{"type":["string","null"]},"message":{"type":"string"}},"required":["domain","url","dns","extra_records","configured","verified","published","live","verified_at","message"]}},{"name":"letterprove_remove_custom_domain","description":"Stop serving this vendor's proof page on its own domain. It stays on app.letterprove.com. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean"},"domain":{"type":["string","null"]}},"required":["removed","domain"]}},{"name":"letterprove_add_host","description":"Accept events from another hostname on this vendor's verified domain, e.g. app.acme.com. A host under the verified domain is accepted at once; a sibling needs the vendor's TXT record on that host. Re-run to re-check. Args: host. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"description":"Another hostname on the vendor's verified registrable domain that runs attest.js, e.g. app.acme.com."}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"proof":{"type":"string","enum":["under_verified_domain","own_dns"]},"accepted":{"type":"boolean"},"verified_at":{"type":["string","null"]},"added_at":{"type":"string"},"record":{"type":["string","null"]},"record_hosts":{"type":"array","items":{"type":"string"}},"message":{"type":"string"}},"required":["host","proof","accepted","verified_at","added_at","record","record_hosts","message"]}},{"name":"letterprove_remove_host","description":"Stop accepting events from one of this vendor's extra hosts. Events already recorded stay. Args: host. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"description":"Another hostname on the vendor's verified registrable domain that runs attest.js, e.g. app.acme.com."}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean"},"host":{"type":"string"}},"required":["removed","host"]}},{"name":"letterprove_list_hosts","description":"This vendor's verified domain plus every extra host events are accepted from, and whether each is accepted now. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"domain":{"type":"string"},"hosts":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"proof":{"type":"string","enum":["under_verified_domain","own_dns"]},"accepted":{"type":"boolean"},"verified_at":{"type":["string","null"]},"added_at":{"type":"string"},"record":{"type":["string","null"]},"record_hosts":{"type":"array","items":{"type":"string"}},"message":{"type":"string"}},"required":["host","proof","accepted","verified_at","added_at","record","record_hosts","message"]}}},"required":["domain","hosts"]}},{"name":"letterprove_rotate_key","description":"Replace this vendor's publishable script key (the key in letterprove_get_script). The old key stops working at once: every installed Letterprove script stops collecting sign-ins until its tag is updated with the new key. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"key":{"type":"string"}},"required":["key"]}},{"name":"letterprove_verify_domain","description":"Check DNS for this vendor's domain-verification TXT record and record the result. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"verified":{"type":"boolean"},"checked":{"type":"boolean"},"message":{"type":"string"},"record":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"hosts":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"verified_at":{"type":["string","null"]}},"required":["verified","checked","message"]}},{"name":"letterprove_verify_site","description":"Prove this vendor's site without DNS: Letterprove fetches the page the Letterprove script is installed on (url, default the homepage; on the vendor's domain or a host under it) and looks for the vendor's own key in the HTML. Only someone who can edit the site can put it there. Returns verified, checked_url, reason and verified_at; get_status then shows domain_proof \"site\". DNS (letterprove_verify_domain) is the stronger proof. Args: url?. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"url":{"description":"The page the Letterprove script is installed on (e.g. https://app.acme.com/login), on the vendor's domain or a host under it. Default: the homepage.","type":"string","maxLength":2048,"format":"uri"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"verified":{"type":"boolean"},"checked_url":{"type":["string","null"]},"reason":{"type":["string","null"]},"verified_at":{"type":["string","null"]}},"required":["verified"]}},{"name":"letterprove_update_vendor","description":"Update this vendor's account. Args: any of name, domain, category, contact_email. A new domain clears domain verification: re-verify it (letterprove_verify_domain or letterprove_verify_site) before the proof counts it again. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"category":{"type":"string"},"contact_email":{"description":"The alert address. \"\" or null clears it.","type":["string","null"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"vendor":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["vendor"]}},{"name":"letterprove_list_observed","description":"Letterprove: the company domains behind the proof's counts, private to this vendor: each domain seen signing in to the product (sessions, signups, logins) and how it counts (status: observed, not-attributable, no-observation). Sign-ins only, never site visitors (Visitors: visitors_get_people). Output: customer: \"Customer slug on the private list, or null.\"; status: \"observed (in companies_observed) | not-attributable (excluded) | no-observation (on the list, nothing seen).\" Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"observed":{"type":"number"},"attributable":{"type":"number"},"awaiting":{"type":"number"},"published":{"type":"number"},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"kind":{"type":"string"},"sessions":{"type":"number"},"signups":{"type":"number"},"logins":{"type":"number"},"customer":{"type":"boolean"},"status":{"type":"string"},"detail":{"type":["string","null"]}},"required":["domain","kind","sessions","signups","logins","customer","status"]}}},"required":["observed","attributable","awaiting","published","domains"]}},{"name":"letterprove_record_observed","description":"Turn one of this vendor's own observed domains into a customer (anonymous, tier-1 ceiling). Args: domain. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"customer":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The newly created customer record (Letterprove's own shape)."}},"required":["customer"]}},{"name":"letterprove_list_customers","description":"List every customer recorded for this vendor. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"customers":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A record on the vendor's private customer list, used only to match observed usage to Stripe payments; never published (see Letterprove's own API for the full shape)."}}},"required":["customers"]}},{"name":"letterprove_create_customer","description":"create: \"Add a company to this vendor's private customer list (never published: the proof counts companies, it does not name them). Args: slug, name, domain, since.\" update: \"Update one of this vendor's private customer records (never published). Args: slug (required), plus any of name, domain, since, features.\" Output customer: \"A record on the vendor's private customer list; never published (Letterprove's own shape).\" Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"slug":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"domain":{"type":"string","minLength":1},"since":{"type":"string","description":"ISO date the customer relationship started."}},"required":["slug","name","domain","since"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"customer":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A record on the vendor's private customer list, used only to match observed usage to Stripe payments; never published (see Letterprove's own API for the full shape)."}},"required":["customer"]}},{"name":"letterprove_update_customer","description":"Update one of this vendor's private customer records (never published). Args: slug (required), plus any of name, domain, since, features. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"slug":{"type":"string","minLength":1,"description":"The customer to update."},"name":{"type":"string"},"domain":{"type":"string"},"since":{"type":"string"},"features":{"type":"array","items":{"type":"string"}}},"required":["slug"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"customer":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"A record on the vendor's private customer list, used only to match observed usage to Stripe payments; never published (see Letterprove's own API for the full shape)."}},"required":["customer"]}},{"name":"letterprove_delete_customer","description":"Delete one of this vendor's customers. Args: slug. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"slug":{"type":"string","minLength":1}},"required":["slug"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true}},"required":["deleted"]}},{"name":"letterprove_list_snapshots","description":"History of the retired per-customer attestation chains, frozen at 2026-10-13; nothing new is added. The current proof is one vendor attestation: letterprove_get_proof_summary. Args: customer (slug, optional). Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{"customer":{"description":"A customer slug — every customer if omitted.","type":"string","minLength":1}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"snapshots":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"required":["snapshots"]}},{"name":"letterprove_publish_proofs","description":"Make this vendor's proofs public: /proofs/{slug}, /attest/{slug} and the chain routes start resolving. Collection and signing were already running. Refused until the domain is verified. Repeating it returns the original publish date. Check letterprove_get_status for the current state. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"published":{"type":"boolean","const":true},"published_at":{"type":"string","description":"When the proofs went public — the original date on a repeat publish."},"slug":{"type":"string","description":"For building the now-live /proofs/{slug} and /attest/{slug} URLs."}},"required":["published","published_at","slug"]}},{"name":"letterprove_unpublish_proofs","description":"Take this vendor's proofs private again: every public route 404s; collection and signing continue. Does not invalidate documents already fetched while public. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"published":{"type":"boolean","const":false},"note":{"type":"string","description":"Unpublishing stops serving; anything already fetched while public stays signed and verifiable."}},"required":["published","note"]}},{"name":"letterprove_set_proof_listing","description":"List or unlist this vendor's published proof in Letterprove's directory (discovery, sitemap, llms.txt). An unlisted proof stays at its URL, so links keep working, but nothing enumerates it and it asks search engines not to index it. Does not change the signed attestation. Check letterprove_get_status `listed`. Args: listed (boolean). Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"listed":{"type":"boolean","description":"true lists the proof in Letterprove's directory; false unlists it (still served at its URL, never enumerated, noindex)."}},"required":["listed"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"listed":{"type":"boolean","description":"The listing state now in effect."},"note":{"type":"string","description":"What listing or unlisting changes; the signed attestation never does."}},"required":["listed","note"]}},{"name":"letterprove_get_stripe_connection","description":"This vendor's Stripe connection: key suffix, live or test, when the account was last read, and what it says about the business (whether Stripe verified it, its name, country, website, and whether that website is on the vendor's domain). Letterprove reads no payments. Never returns key material. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"connected":{"type":"boolean","const":false}},"required":["connected"]},{"type":"object","properties":{"connected":{"type":"boolean","const":true},"last4":{"type":"string","description":"Last four characters of the stored key. The key itself is never returned."},"livemode":{"type":"boolean","description":"False for a test key, which reads the account but never counts as verified."},"connected_at":{"type":"string"},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"business":{"description":"What the vendor's own Stripe account says about the business. Letterprove reads nothing about payments.","anyOf":[{"type":"object","properties":{"verified":{"type":"boolean","description":"Stripe has verified the business (the account can accept payments)."},"name":{"type":["string","null"]},"country":{"type":["string","null"]},"website":{"type":["string","null"]},"website_matches_domain":{"type":"boolean"}},"required":["verified","name","country","website","website_matches_domain"]},{"type":"null"}]},"account":{"description":"The Stripe account behind the key, as last read. It only counts as the vendor's when one of its domains shares the vendor's domain, or staff approved the account.","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"checked_at":{"type":["string","null"]}},"required":["id","domains","checked_at"]},{"type":"null"}]}},"required":["connected","last4","livemode","connected_at","last_synced_at","last_sync_error"]}],"type":"object"}},{"name":"letterprove_connect_stripe","description":"Store a Stripe RESTRICTED key for this vendor so Stripe can confirm it is a real, verified business at its domain (tier 3 with a DNS-verified domain). Letterprove reads only the account, never payments. Args: restricted_key (rk_…, one permission: Accounts read). The Stripe account must be the vendor's own — one of the domains it publishes (business website, support email) has to share the vendor's domain — or the key is refused. An unrestricted sk_ or publishable pk_ key is refused. The key is never returned. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"restricted_key":{"type":"string","minLength":1,"description":"A Stripe RESTRICTED key (rk_live_… or rk_test_…) with one permission: Accounts read (connected_account_read). Letterprove reads only which business the account is, never payments. Never echoed back."}},"required":["restricted_key"],"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"connected":{"type":"boolean","const":false}},"required":["connected"]},{"type":"object","properties":{"connected":{"type":"boolean","const":true},"last4":{"type":"string","description":"Last four characters of the stored key. The key itself is never returned."},"livemode":{"type":"boolean","description":"False for a test key, which reads the account but never counts as verified."},"connected_at":{"type":"string"},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"business":{"description":"What the vendor's own Stripe account says about the business. Letterprove reads nothing about payments.","anyOf":[{"type":"object","properties":{"verified":{"type":"boolean","description":"Stripe has verified the business (the account can accept payments)."},"name":{"type":["string","null"]},"country":{"type":["string","null"]},"website":{"type":["string","null"]},"website_matches_domain":{"type":"boolean"}},"required":["verified","name","country","website","website_matches_domain"]},{"type":"null"}]},"account":{"description":"The Stripe account behind the key, as last read. It only counts as the vendor's when one of its domains shares the vendor's domain, or staff approved the account.","anyOf":[{"type":"object","properties":{"id":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"checked_at":{"type":["string","null"]}},"required":["id","domains","checked_at"]},{"type":"null"}]}},"required":["connected","last4","livemode","connected_at","last_synced_at","last_sync_error"]}],"type":"object"}},{"name":"letterprove_refresh_stripe_business","description":"Re-read this vendor's Stripe account now: whether Stripe has verified the business, and whether the account is the vendor's own. Letterprove reads nothing about payments. Runs hourly on its own. Fails with stripe_not_connected until letterprove_connect_stripe has run. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"bound":{"description":"The account is the vendor's own (its domains match, or staff approved it).","type":"boolean"},"verified":{"description":"Stripe has verified the business. False for a test key.","type":"boolean"},"test_mode":{"type":"boolean"},"warning":{"description":"Why the account doesn't count as the vendor's, when it was read but didn't match.","type":"string"}}}},{"name":"letterprove_disconnect_stripe","description":"Remove this vendor's Stripe key and the business details read with it. The proof stops showing a Stripe-verified business, so tier 3 drops to tier 1. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean","const":true}},"required":["disconnected"]}},{"name":"letterprove_get_posthog_connection","description":"This vendor's PostHog connection (read only): which project, when it last synced, how many companies it has identified and since when, and whether that history is on the proof (and why not). Never returns key material. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"connected":{"type":"boolean","const":false}},"required":["connected"]},{"type":"object","properties":{"connected":{"type":"boolean","const":true},"last4":{"type":"string","description":"Last four characters of the personal API key. The key itself is never returned."},"api_host":{"type":"string"},"project_id":{"type":"string"},"connected_at":{"type":"string"},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"host_events_30d":{"description":"Events on the vendor's verified domain or hosts in 30 days. Zero means the project doesn't track this product.","type":["number","null"]},"companies":{"type":"number","description":"Company domains PostHog has identified people from."},"active_companies_30d":{"type":"number"},"since":{"description":"When PostHog first saw a person from any of them.","type":["string","null"]},"domains_excluded":{"type":["number","null"]},"truncated":{"type":"boolean"},"published":{"type":"boolean","description":"Whether this history is on the proof as reported_history (vendor-reported; never changes the tier)."},"unpublished_reason":{"type":["string","null"]}},"required":["connected","last4","api_host","project_id","connected_at","last_synced_at","last_sync_error","host_events_30d","companies","active_companies_30d","since","domains_excluded","truncated","published","unpublished_reason"]}],"type":"object"}},{"name":"letterprove_connect_posthog","description":"Connect this vendor's PostHog project, READ ONLY, so the companies it has identified people from appear on the proof as vendor-reported history (it never changes the tier). Args: personal_api_key (phx_, query:read scope), project_id, api_host (optional: us.posthog.com default, eu.posthog.com, or self-hosted). The key is checked with one read before it is stored and is never returned. Nothing is ever sent to PostHog. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"personal_api_key":{"type":"string","minLength":1,"description":"A PostHog PERSONAL API key (phx_…) with the query:read scope and access to the project. A project key (phc_) is refused. Read only: nothing is ever sent to PostHog. Never echoed back."},"project_id":{"type":"string","pattern":"^\\d{1,12}$","description":"The PostHog project id (the number in the project URL)."},"api_host":{"description":"us.posthog.com (default), eu.posthog.com, or a self-hosted https host.","type":"string"}},"required":["personal_api_key","project_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","const":true},"last4":{"type":"string","description":"Last four characters of the personal API key. The key itself is never returned."},"api_host":{"type":"string"},"project_id":{"type":"string"},"connected_at":{"type":"string"},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"host_events_30d":{"description":"Events on the vendor's verified domain or hosts in 30 days. Zero means the project doesn't track this product.","type":["number","null"]},"companies":{"type":"number","description":"Company domains PostHog has identified people from."},"active_companies_30d":{"type":"number"},"since":{"description":"When PostHog first saw a person from any of them.","type":["string","null"]},"domains_excluded":{"type":["number","null"]},"truncated":{"type":"boolean"},"published":{"type":"boolean","description":"Whether this history is on the proof as reported_history (vendor-reported; never changes the tier)."},"unpublished_reason":{"type":["string","null"]}},"required":["connected","last4","api_host","project_id","connected_at","last_synced_at","last_sync_error","host_events_30d","companies","active_companies_30d","since","domains_excluded","truncated","published","unpublished_reason"]}},{"name":"letterprove_sync_posthog","description":"Re-read this vendor's PostHog project now and replace the stored history. Also runs daily. Fails with posthog_not_connected until letterprove_connect_posthog has run. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connected":{"type":"boolean","const":true},"last4":{"type":"string","description":"Last four characters of the personal API key. The key itself is never returned."},"api_host":{"type":"string"},"project_id":{"type":"string"},"connected_at":{"type":"string"},"last_synced_at":{"type":["string","null"]},"last_sync_error":{"type":["string","null"]},"host_events_30d":{"description":"Events on the vendor's verified domain or hosts in 30 days. Zero means the project doesn't track this product.","type":["number","null"]},"companies":{"type":"number","description":"Company domains PostHog has identified people from."},"active_companies_30d":{"type":"number"},"since":{"description":"When PostHog first saw a person from any of them.","type":["string","null"]},"domains_excluded":{"type":["number","null"]},"truncated":{"type":"boolean"},"published":{"type":"boolean","description":"Whether this history is on the proof as reported_history (vendor-reported; never changes the tier)."},"unpublished_reason":{"type":["string","null"]}},"required":["connected","last4","api_host","project_id","connected_at","last_synced_at","last_sync_error","host_events_30d","companies","active_companies_30d","since","domains_excluded","truncated","published","unpublished_reason"]}},{"name":"letterprove_disconnect_posthog","description":"Remove this vendor's PostHog key and all history read with it. The proof stops showing reported_history at the next snapshot. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"disconnected":{"type":"boolean","const":true}},"required":["disconnected"]}},{"name":"letterprove_get_proof_display","description":"Which optional sections this vendor shows on its public proof (sessions, Stripe business, first observed date, logos, PostHog history, hosts, growth, retention), and which parts are always shown. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sections":{"type":"object","properties":{"sessions":{"type":"boolean"},"reported_history":{"type":"boolean"},"logos":{"type":"boolean"},"stripe_business":{"type":"boolean"},"observed_since":{"type":"boolean"},"hosts":{"type":"boolean"},"growth":{"type":"boolean"},"retention":{"type":"boolean"},"logo_names":{"type":"boolean","description":"true = verified logos are named on the proof; false = counts only."}},"description":"true = shown on the public proof. Hidden sections are left out of the signed proof. Also carries logo_names."},"locked":{"description":"Parts of the proof that are always shown.","type":"array","items":{"type":"string"}}},"required":["sections"]}},{"name":"letterprove_set_proof_display","description":"Show or hide optional sections of this vendor's public proof. Hidden sections are left out of the signed proof from the next hourly snapshot. The domain, tier, company count and verify link are always shown. Args: sections {sessions?, reported_history?, logos?, stripe_business?, observed_since?, hosts?, growth?, retention?, logo_names? (name verified logos, or counts only)}. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"sections":{"type":"object","properties":{"sessions":{"type":"boolean"},"reported_history":{"type":"boolean"},"logos":{"type":"boolean"},"stripe_business":{"description":"The business Stripe verified behind the vendor's Stripe account: name, country, website.","type":"boolean"},"observed_since":{"type":"boolean"},"hosts":{"type":"boolean"},"growth":{"description":"Companies observed per month, last six months.","type":"boolean"},"retention":{"description":"Of the companies observed 90 to 60 days ago, how many were also observed in the last 30 days.","type":"boolean"},"logo_names":{"description":"Name verified logos on the proof (true) or show counts only (false).","type":"boolean"}},"additionalProperties":false,"description":"Which optional sections to show on the public proof, plus logo_names. Omitted keys keep their current setting. The domain, tier, company count and verify link are always shown."}},"required":["sections"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sections":{"type":"object","properties":{"sessions":{"type":"boolean"},"reported_history":{"type":"boolean"},"logos":{"type":"boolean"},"stripe_business":{"type":"boolean"},"observed_since":{"type":"boolean"},"hosts":{"type":"boolean"},"growth":{"type":"boolean"},"retention":{"type":"boolean"},"logo_names":{"type":"boolean","description":"true = verified logos are named on the proof; false = counts only."}},"description":"true = shown on the public proof. Hidden sections are left out of the signed proof. Also carries logo_names."},"locked":{"description":"Parts of the proof that are always shown.","type":"array","items":{"type":"string"}}},"required":["sections"]}},{"name":"letterprove_get_logo_wall","description":"This vendor's public logo wall as Letterprove checked it: the page, each listed company, whether it was found on the page, and whether Letterprove observed it signing in over the last 30 days. Verified logos appear by name on the proof; the rest only as a count. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"anyOf":[{"type":"object","properties":{"configured":{"type":"boolean","const":false}},"required":["configured"]},{"type":"object","properties":{"configured":{"type":"boolean","const":true},"page_url":{"type":"string"},"checked_at":{"description":"When Letterprove last read the page.","type":["string","null"]},"check_error":{"description":"Why the page couldn't be read, when it couldn't. Logos stay off the proof until it can.","type":["string","null"]},"total":{"type":"number","description":"Companies the vendor listed."},"on_page":{"type":"number","description":"Of those, how many Letterprove found on the page."},"verified":{"type":"number","description":"Of those on the page, how many were observed signing in over the last 30 days."},"published":{"type":"boolean","description":"Whether the verified logos appear on the public proof."},"companies":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"on_page":{"type":"boolean"},"status":{"type":"string","enum":["paying","active","not_observed"]}},"required":["name","domain","on_page","status"]}}},"required":["configured","page_url","checked_at","check_error","total","on_page","verified","published","companies"]}],"type":"object"}},{"name":"letterprove_set_logo_wall","description":"Set the page on this vendor's own site that shows its customer logos, and the companies on it. Letterprove reads the page and only names companies it finds there. Each is marked paying, signed in, or not observed from Letterprove's own data. Args: page_url, companies [{name, domain}]. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"page_url":{"type":"string","format":"uri","description":"The public page on your own site that shows your customer logos, e.g. https://acme.com/customers."},"companies":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"domain":{"type":"string","minLength":3,"maxLength":253}},"required":["name","domain"]},"description":"The companies on that page, by name and domain (up to 200). Only companies Letterprove finds on the page can be named on your proof."}},"required":["page_url","companies"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"configured":{"type":"boolean","const":true},"page_url":{"type":"string"},"checked_at":{"description":"When Letterprove last read the page.","type":["string","null"]},"check_error":{"description":"Why the page couldn't be read, when it couldn't. Logos stay off the proof until it can.","type":["string","null"]},"total":{"type":"number","description":"Companies the vendor listed."},"on_page":{"type":"number","description":"Of those, how many Letterprove found on the page."},"verified":{"type":"number","description":"Of those on the page, how many were observed signing in over the last 30 days."},"published":{"type":"boolean","description":"Whether the verified logos appear on the public proof."},"companies":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"on_page":{"type":"boolean"},"status":{"type":"string","enum":["paying","active","not_observed"]}},"required":["name","domain","on_page","status"]}}},"required":["configured","page_url","checked_at","check_error","total","on_page","verified","published","companies"]}},{"name":"letterprove_clear_logo_wall","description":"Remove this vendor's logo wall. The proof stops naming any company at the next snapshot. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cleared":{"type":"boolean","const":true}},"required":["cleared"]}},{"name":"letterprove_submit_support_request","description":"Send a support message to the Letterprove team, attributed to this vendor and account. Args: message. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"message":{"type":"string","minLength":1,"maxLength":4000}},"required":["message"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"]}},{"name":"letterprove_list_beta_orgs","description":"Staff only. Every org in the Letterprove beta, and through when its agent reads are free. Every org may use Proofs; the list only decides billing. Refused unless the key was created by Letter staff. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"orgs":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"exempt_until":{"type":"string","description":"Free through this moment; agent-read billing skips months that start before it."},"reason":{"type":"string"},"added_by":{"type":["string","null"]},"added_at":{"type":"string"}},"required":["org_id","exempt_until","reason","added_by","added_at"]}}},"required":["orgs"]}},{"name":"letterprove_list_access_requests","description":"Staff only. The newest Letterprove beta access requests (newest first, up to 50): who asked, the org it came from when asked inside the app (org_id, what letterprove_set_beta_org takes) and whether that org is in the beta yet. Requests made on letterprove.com have no org; send those an invite link (letterprove_create_beta_invite). Refused unless the key was created by Letter staff. Requires capability vendor:read.","capability":"vendor:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"requests":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"company":{"type":"string"},"email":{"type":"string"},"org_id":{"description":"The org it came from (pass to letterprove_set_beta_org); null from letterprove.com.","type":["string","null"]},"in_beta":{"type":["boolean","null"]},"source_page":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","name","company","email","org_id","in_beta","source_page","created_at"]}}},"required":["requests"]}},{"name":"letterprove_set_beta_org","description":"Staff only. Add an org to the Letterprove beta, or change its row: its agent reads are not billed for any month that starts before exempt_until (every org may use Proofs either way). An org newly added has its admins emailed that they're in (`emailed`). To invite someone whose org you don't know, letterprove_create_beta_invite. Args: org_id, exempt_until (YYYY-MM-DD = through the end of that day, UTC, or a timestamp), reason. Refused unless the key was created by Letter staff. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The organization to invite."},"exempt_until":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"}],"description":"Free through this date (YYYY-MM-DD means through the end of that day, UTC) or timestamp."},"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Why this org is in the beta."}},"required":["org_id","exempt_until","reason"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"org_id":{"type":"string"},"exempt_until":{"type":"string","description":"Free through this moment; agent-read billing skips months that start before it."},"reason":{"type":"string"},"added_by":{"type":["string","null"]},"added_at":{"type":"string"},"emailed":{"type":"boolean","description":"Whether the org's admins were emailed that they're in (only when newly added)."}},"required":["org_id","exempt_until","reason","added_by","added_at","emailed"]}},{"name":"letterprove_create_beta_invite","description":"Staff only. A link that lets one org into the Letterprove beta without knowing its id: whoever opens it signs up (or signs in) through the Proofs crossing and their org joins the beta, free through exempt_until, with this reason on its row. Good for 30 days; signed, not stored. Args: exempt_until, reason. Refused unless the key was created by Letter staff. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"exempt_until":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"}],"description":"Free through this date (YYYY-MM-DD means through the end of that day, UTC) or timestamp."},"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Why this org is in the beta."}},"required":["exempt_until","reason"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"url":{"type":"string","description":"The link to send: it signs the person up (or in) and lets their org into the beta."},"token":{"type":"string"},"exempt_until":{"type":"string"},"expires_at":{"type":"string","description":"The link stops working after this."}},"required":["url","token","exempt_until","expires_at"]}},{"name":"letterprove_claim_beta_invite","description":"Join the Letterprove beta with an invite link someone at Letter sent: pass the link's `invite` token. The organization's agent reads are then free through the link's date (letterprove_create_vendor next, if it has no vendor yet). An org already in the beta keeps its row. Expired or altered links are refused. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"token":{"type":"string","minLength":10,"maxLength":2000,"description":"The invite link's token (its `invite` parameter)."}},"required":["token"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"beta":{"type":"object","properties":{"org_id":{"type":"string"},"exempt_until":{"type":"string","description":"Free through this moment; agent-read billing skips months that start before it."},"reason":{"type":"string"},"added_by":{"type":["string","null"]},"added_at":{"type":"string"}},"required":["org_id","exempt_until","reason","added_by","added_at"]},"added":{"type":"boolean","description":"False when the org was already in the beta."}},"required":["beta","added"]}},{"name":"letterprove_remove_beta_org","description":"Staff only. Take an org off the Letterprove beta list: its agent reads are billed normally from then on (it keeps Proofs). Args: org_id. Refused unless the key was created by Letter staff. Requires capability vendor:write.","capability":"vendor:write","inputSchema":{"type":"object","properties":{"org_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["org_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean","const":true},"org_id":{"type":"string"}},"required":["removed","org_id"]}},{"name":"visitors_list_sites","description":"The caller's Visitors sites: each site's domain, publishable key, modules (AI agents, identification), install checks (script seen, server snippet seen, site proven) and identification state. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"object","properties":{"site_id":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"]},"key":{"description":"The publishable key in the site's script tag (lv_pub_…, or a pre-split lp_live_… key).","type":["string","null"]},"modules":{"type":"object","properties":{"agent_visitors":{"type":"boolean"},"identification":{"type":"boolean"},"editor":{"type":"boolean","description":"Edit with Letter: on the site's own pages, ⌘L (Ctrl+L), or a link ending in #letter, opens Letter for the site's team, who log in and say what to change on the page; a build starts with the page and a screenshot. On by default with the script tag on every site with a domain, from the first page view, whatever its proof or identification: nothing else to add or enable for it (no attribute, no second script). Only data-editor=\"off\" on the script tag turns it off; add that only when the site's owner asks for ⌘L off."}},"required":["agent_visitors","identification","editor"]},"script_seen_at":{"description":"First time the script tag reached us.","type":["string","null"]},"server_seen_at":{"description":"First time the server snippet forwarded a request.","type":["string","null"]},"site_verified_at":{"description":"When the site's key was found on its homepage (site proof).","type":["string","null"]},"identification":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"identification_set_by":{"anyOf":[{"type":"string","enum":["auto","user","staff"]},{"type":"null"}],"description":"Who last turned identification on or off: auto (turned on when the script was detected on the homepage, before it was billed), user (the site's owner) or staff (Letter; identification_set_reason says why). Null = nobody yet. Only the owner turns it on."},"identification_set_reason":{"description":"Why Letter's staff turned identification off (or on), e.g. it was switched on automatically before it was billed per person, so it waits for the owner's opt-in. Null when the owner or the script detection decided.","type":["string","null"]},"identification_paused":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["card_required","payment_failed","spend_cap"],"description":"card_required: the account's free tokens are used. payment_failed: its payment didn't go through (update the card or pay the invoice). spend_cap: its monthly spend cap is reached."},"since":{"type":"string","description":"When Letter paused it."},"message":{"type":"string","description":"What to tell the person, and what resumes it."}},"required":["reason","since","message"]},{"type":"null"}],"description":"Letter paused identification because new work would be refused: nobody more is identified (each person is billed) until tokens or a card arrive, or the cap is raised; then it resumes by itself. Null while it runs. Turning it on while paused goes through the same gate."},"letter_edit":{"type":"object","properties":{"link":{"description":"https://<domain>/#letter: opens Letter Edit on the site's homepage in every browser and on phones (Safari keeps ⌘L for its address bar; Control+L works there). Null when the site has no domain.","type":["string","null"]},"off":{"anyOf":[{"type":"string","enum":["no_domain"]},{"type":"null"}],"description":"Why Letter Edit is off: no_domain (the site has no domain). Null while it's on. data-editor=\"off\" on the script tag also turns it off, and isn't read here."}},"required":["link","off"],"description":"Edit with Letter (⌘L) on this site: how to open it, or why it's off. modules.editor is the same reading."},"created_at":{"type":"string"}},"required":["site_id","name","domain","key","modules","script_seen_at","server_seen_at","site_verified_at","identification","identification_set_by","identification_set_reason","identification_paused","letter_edit","created_at"]}}},"required":["sites"]}},{"name":"visitors_add_site","description":"Add a site to Visitors (or return the org's existing site on that domain). The answer carries the site's publishable key; visitors_get_script returns the tag and server snippet to install. AI-agent counting is on by default; identification stays off until the site is proven and turned on. Args: domain, name?. Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":3,"maxLength":253,"description":"The site's domain, e.g. acme.com."},"name":{"description":"A display name; defaults to the domain.","type":"string","minLength":1,"maxLength":120}},"required":["domain"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"created":{"type":"boolean"},"site":{"type":"object","properties":{"site_id":{"type":"string"},"name":{"type":"string"},"domain":{"type":["string","null"]},"key":{"description":"The publishable key in the site's script tag (lv_pub_…, or a pre-split lp_live_… key).","type":["string","null"]},"modules":{"type":"object","properties":{"agent_visitors":{"type":"boolean"},"identification":{"type":"boolean"},"editor":{"type":"boolean","description":"Edit with Letter: on the site's own pages, ⌘L (Ctrl+L), or a link ending in #letter, opens Letter for the site's team, who log in and say what to change on the page; a build starts with the page and a screenshot. On by default with the script tag on every site with a domain, from the first page view, whatever its proof or identification: nothing else to add or enable for it (no attribute, no second script). Only data-editor=\"off\" on the script tag turns it off; add that only when the site's owner asks for ⌘L off."}},"required":["agent_visitors","identification","editor"]},"script_seen_at":{"description":"First time the script tag reached us.","type":["string","null"]},"server_seen_at":{"description":"First time the server snippet forwarded a request.","type":["string","null"]},"site_verified_at":{"description":"When the site's key was found on its homepage (site proof).","type":["string","null"]},"identification":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"identification_set_by":{"anyOf":[{"type":"string","enum":["auto","user","staff"]},{"type":"null"}],"description":"Who last turned identification on or off: auto (turned on when the script was detected on the homepage, before it was billed), user (the site's owner) or staff (Letter; identification_set_reason says why). Null = nobody yet. Only the owner turns it on."},"identification_set_reason":{"description":"Why Letter's staff turned identification off (or on), e.g. it was switched on automatically before it was billed per person, so it waits for the owner's opt-in. Null when the owner or the script detection decided.","type":["string","null"]},"identification_paused":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["card_required","payment_failed","spend_cap"],"description":"card_required: the account's free tokens are used. payment_failed: its payment didn't go through (update the card or pay the invoice). spend_cap: its monthly spend cap is reached."},"since":{"type":"string","description":"When Letter paused it."},"message":{"type":"string","description":"What to tell the person, and what resumes it."}},"required":["reason","since","message"]},{"type":"null"}],"description":"Letter paused identification because new work would be refused: nobody more is identified (each person is billed) until tokens or a card arrive, or the cap is raised; then it resumes by itself. Null while it runs. Turning it on while paused goes through the same gate."},"letter_edit":{"type":"object","properties":{"link":{"description":"https://<domain>/#letter: opens Letter Edit on the site's homepage in every browser and on phones (Safari keeps ⌘L for its address bar; Control+L works there). Null when the site has no domain.","type":["string","null"]},"off":{"anyOf":[{"type":"string","enum":["no_domain"]},{"type":"null"}],"description":"Why Letter Edit is off: no_domain (the site has no domain). Null while it's on. data-editor=\"off\" on the script tag also turns it off, and isn't read here."}},"required":["link","off"],"description":"Edit with Letter (⌘L) on this site: how to open it, or why it's off. modules.editor is the same reading."},"created_at":{"type":"string"}},"required":["site_id","name","domain","key","modules","script_seen_at","server_seen_at","site_verified_at","identification","identification_set_by","identification_set_reason","identification_paused","letter_edit","created_at"]}},"required":["created","site"]}},{"name":"visitors_get_script","description":"The Visitors script tag for a site (a small inline loader for every page's <head> that loads https://lettervisit.com/v.js?k=… so running A/B tests apply before the page shows, and never blocks it: if Letter is slow or down the page shows within a second without us), the server snippet that counts ChatGPT, Claude and crawlers (they never run JavaScript), and the install checks: script seen, server snippet seen, first agent visit, site proven, identification state. Args: site_id?. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"domain":{"type":["string","null"]},"key":{"type":["string","null"]},"visitors_snippet":{"description":"The script tag for every page's <head>.","type":["string","null"]},"server_snippet":{"anyOf":[{"type":"object","properties":{"endpoint":{"type":"string","description":"Where the server snippet posts (lettervisit.com/api/v1/agent-visit)."},"nextjs_middleware":{"type":"string"},"vercel_middleware":{"type":"string","description":"Vercel Routing Middleware (middleware.ts) for a site on Vercel that isn't Next.js: Vite, Astro, plain HTML."},"cloudflare_worker":{"type":"string","description":"A Cloudflare Worker (also how site builders like Webflow count agents)."},"express":{"type":"string"},"generic":{"type":"string"}},"required":["endpoint","nextjs_middleware","vercel_middleware","cloudflare_worker","express","generic"]},{"type":"null"}],"description":"Counts ChatGPT, Claude and crawlers, which never run JavaScript."},"check_command":{"description":"A terminal command that reads the install checks (script_seen, server_seen, verified) and records nothing: run it after deploying.","type":["string","null"]},"modules":{"type":"object","properties":{"agent_visitors":{"type":"boolean"},"identification":{"type":"boolean"},"editor":{"type":"boolean","description":"Edit with Letter: on the site's own pages, ⌘L (Ctrl+L), or a link ending in #letter, opens Letter for the site's team, who log in and say what to change on the page; a build starts with the page and a screenshot. On by default with the script tag on every site with a domain, from the first page view, whatever its proof or identification: nothing else to add or enable for it (no attribute, no second script). Only data-editor=\"off\" on the script tag turns it off; add that only when the site's owner asks for ⌘L off."}},"required":["agent_visitors","identification","editor"]},"status":{"type":"object","properties":{"script_seen_at":{"type":["string","null"]},"server_seen_at":{"type":["string","null"]},"site_verified_at":{"type":["string","null"]},"first_agent_visit_at":{"type":["string","null"]},"server_snippet_version":{"description":"2: installed once, takes what to send from us; 1: re-copy the snippet once; null before the first forward.","type":["number","null"]},"identification":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"identification_set_by":{"anyOf":[{"type":"string","enum":["auto","user","staff"]},{"type":"null"}],"description":"Who last turned identification on or off: auto (turned on when the script was detected on the homepage, before it was billed), user (the site's owner) or staff (Letter; identification_set_reason says why). Null = nobody yet. Only the owner turns it on."},"identification_set_reason":{"description":"Why Letter's staff turned identification off (or on), e.g. it was switched on automatically before it was billed per person, so it waits for the owner's opt-in. Null when the owner or the script detection decided.","type":["string","null"]},"identification_paused":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["card_required","payment_failed","spend_cap"],"description":"card_required: the account's free tokens are used. payment_failed: its payment didn't go through (update the card or pay the invoice). spend_cap: its monthly spend cap is reached."},"since":{"type":"string","description":"When Letter paused it."},"message":{"type":"string","description":"What to tell the person, and what resumes it."}},"required":["reason","since","message"]},{"type":"null"}],"description":"Letter paused identification because new work would be refused: nobody more is identified (each person is billed) until tokens or a card arrive, or the cap is raised; then it resumes by itself. Null while it runs. Turning it on while paused goes through the same gate."}},"required":["script_seen_at","server_seen_at","site_verified_at","first_agent_visit_at","server_snippet_version","identification","identification_set_by","identification_set_reason","identification_paused"]}},"required":["site_id","domain","key","visitors_snippet","server_snippet","check_command","modules","status"]}},{"name":"visitors_set_modules","description":"Switch a site's AI-agents module: counting AI agents and crawlers (server snippet and host connectors) and AI citations (click-throughs from AI answers). Off stops all three; nothing already counted is lost. Identification has its own tool, visitors_set_identification. Args: site_id?, agent_visitors?. Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"agent_visitors":{"description":"Count AI agents and crawlers reading the site.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"modules":{"type":"object","properties":{"agent_visitors":{"type":"boolean"}},"required":["agent_visitors"]}},"required":["site_id","modules"]}},{"name":"visitors_get_agent_visits","description":"AI agents and crawlers that read a site: totals per agent, why they came (by_purpose: live = answering a person, index, training, automated_browser), the most-read paths, and a daily series. `source` says where the numbers come from: `visitors` (the site's current traffic source, counted from `baseline`; agents named as their User-Agent claims, e.g. openai-crawler-user, unverified, with LetterVisit's own verified count beside it in lettervisit_verified) or `lettervisit` (LetterVisit's own counts, verified from the operator's published addresses vs claimed). On `visitors`, a window that reaches back before `baseline` also carries `earlier`: those days from LetterVisit's own count (v1, agents named by operator and verified), kept apart from the totals above and never added to them. Agents that never run JavaScript (ChatGPT, Claude, Perplexity, search crawlers) are counted once the server snippet is installed. Each path carries its kind (page, agent_file such as llms.txt or agents.md, site_file such as robots.txt, asset), path_kinds tallies them and path_count says how many paths there are. `served` says what agents were served: markdown or HTML (overall, per agent, per path; a site may answer one URL either way by the Accept header) and how the responses went (ok, blocked by a firewall, missing). `floors` says what the numbers can't see. Private. Args: site_id?, days (1-90, default 30), agent? (e.g. claude), path? (glob, e.g. /docs/*), purpose?, path_kind?, paths_limit? (default 20, up to 500). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90},"agent":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_-]+$","description":"An agent, by the id the report names (openai-crawler-user, anthropic-crawler, …), or a product token that names its operator: chatgpt, claude, perplexity, gemini, …"},"path":{"type":"string","minLength":1,"maxLength":300,"description":"A path, or a glob where * matches anything (e.g. /docs/*)."},"purpose":{"type":"string","enum":["live","index","training","automated_browser","unknown"],"description":"Why it came: live (answering a person's question right now), index (building a search index), training (collecting for a model), automated_browser (an agent driving a browser)."},"path_kind":{"type":"string","enum":["page","agent_file","site_file","asset"],"description":"What kind of file: page, agent_file (llms.txt, llms-full.txt, agents.md, a page's .md twin), site_file (robots.txt, sitemaps, ads.txt, /.well-known/), asset (images, scripts, styles, fonts)."},"paths_limit":{"description":"Most paths returned in top_paths, busiest first (default 20).","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"days":{"type":"number"},"since":{"type":"string","description":"The first day counted: the window's start, or the site's baseline when later."},"source":{"type":"string","enum":["visitors","lettervisit"],"description":"visitors: the site's current traffic source, counted from `baseline`, agents named as their User-Agent claims (openai-crawler-user, …) and not verified. lettervisit: LetterVisit's own counts, for a site not moved yet."},"baseline":{"description":"visitors: the first day the site counts from; earlier numbers are a different source.","type":["string","null"]},"total":{"type":"number"},"verified":{"description":"Visits from an address the claimed operator publishes for its agents. Null on the visitors source.","type":["number","null"]},"lettervisit_verified":{"description":"visitors only: LetterVisit's own count of verified agent visits since the baseline, kept beside it.","type":["number","null"]},"by_purpose":{"type":"object","properties":{"live":{"type":"number"},"index":{"type":"number"},"training":{"type":"number"},"automated_browser":{"type":"number"},"unknown":{"type":"number"}},"required":["live","index","training","automated_browser","unknown"],"description":"Why they came: live = answering a person's question, index = search index, training = model training crawl, automated_browser = an agent driving a browser."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"kind":{"type":"string","enum":["ai_agent","search_crawler","automated_browser"]},"total":{"type":"number"},"verified":{"type":["number","null"]},"claimed":{"type":["number","null"]},"last_at":{"type":"string"}},"required":["agent","kind","total","verified","claimed","last_at"]}},"top_paths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"total":{"type":"number"},"kind":{"type":"string","enum":["page","agent_file","site_file","asset"],"description":"page; agent_file (llms.txt, llms-full.txt, agents.md, a page's .md twin); site_file (robots.txt, sitemaps, ads.txt, /.well-known/); asset (images, scripts, styles, fonts)."}},"required":["path","total","kind"]},"description":"The most-read paths, busiest first, at most paths_limit."},"counting_since":{"description":"The first day anything could be counted (the baseline, or the day the server snippet was first heard from); days before it weren't counted, not zero.","type":["string","null"]},"path_count":{"type":"number","description":"Distinct paths matching the filter: how many top_paths was cut from."},"path_kinds":{"type":"object","properties":{"page":{"type":"object","properties":{"paths":{"type":"number"},"visits":{"type":"number"}},"required":["paths","visits"]},"agent_file":{"type":"object","properties":{"paths":{"type":"number"},"visits":{"type":"number"}},"required":["paths","visits"]},"site_file":{"type":"object","properties":{"paths":{"type":"number"},"visits":{"type":"number"}},"required":["paths","visits"]},"asset":{"type":"object","properties":{"paths":{"type":"number"},"visits":{"type":"number"}},"required":["paths","visits"]}},"required":["page","agent_file","site_file","asset"],"description":"Paths and visits by kind, before the path_kind filter."},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"total":{"type":"number"},"verified":{"type":["number","null"]}},"required":["day","total","verified"]}},"earlier":{"anyOf":[{"type":"object","properties":{"until":{"type":"string","description":"The last day it covers: the day before `baseline`."},"total":{"type":"number"},"verified":{"type":"number","description":"Visits from an address the claimed operator publishes."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"kind":{"type":"string","enum":["ai_agent","search_crawler","automated_browser"]},"total":{"type":"number"},"verified":{"type":"number"}},"required":["agent","kind","total","verified"]}},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"total":{"type":"number"},"verified":{"type":"number"}},"required":["day","total","verified"]}}},"required":["until","total","verified","agents","daily"]},{"type":"null"}],"description":"visitors only: the days of the window before `baseline`, from LetterVisit's own count (v1), kept apart from `total`, `agents` and `daily` above, which are the current count (v2). Agents are named by operator here (chatgpt, claude) and verified against published addresses; above they are named as their User-Agent claims. Null when the window starts on or after the baseline, or nothing was counted before it."},"floors":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["bots","verified","live_reads","citations","people"]},"notes":{"type":"array","items":{"type":"string"},"description":"What this number can't see, in a sentence each."}},"required":["measure","notes"]},"description":"Every number is a floor: per measure, what it can't see (structural, and from this site's own evidence)."},"served":{"type":"object","properties":{"total":{"type":"number"},"formats":{"type":"object","properties":{"markdown":{"type":"number","description":"Sent markdown: seen in the response's content type."},"html":{"type":"number","description":"Sent HTML: seen in the response's content type."},"other":{"type":"number","description":"Sent something else (JSON, an image…), seen in the response."},"asked_markdown":{"type":"number","description":"Asked for markdown (a .md path or the Accept header); what was sent wasn't seen. Next.js and Vercel snippets run before the response, so this is all they know."},"asked_html":{"type":"number","description":"Asked for HTML (the Accept header); what was sent wasn't seen."},"unknown":{"type":"number","description":"Neither the response's type nor the request's Accept header was reported (an older snippet)."}},"required":["markdown","html","other","asked_markdown","asked_html","unknown"]},"outcomes":{"type":"object","properties":{"ok":{"type":"number"},"redirect":{"type":"number"},"blocked":{"type":"number"},"missing":{"type":"number"},"error":{"type":"number"},"unknown":{"type":"number"}},"required":["ok","redirect","blocked","missing","error","unknown"],"description":"How the responses went: blocked is 401, 403 or 429 (a firewall or the site refusing the agent)."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"total":{"type":"number"},"blocked":{"type":"number"},"markdown":{"type":"number","description":"Sent markdown: seen in the response's content type."},"html":{"type":"number","description":"Sent HTML: seen in the response's content type."},"other":{"type":"number","description":"Sent something else (JSON, an image…), seen in the response."},"asked_markdown":{"type":"number","description":"Asked for markdown (a .md path or the Accept header); what was sent wasn't seen. Next.js and Vercel snippets run before the response, so this is all they know."},"asked_html":{"type":"number","description":"Asked for HTML (the Accept header); what was sent wasn't seen."},"unknown":{"type":"number","description":"Neither the response's type nor the request's Accept header was reported (an older snippet)."}},"required":["agent","total","blocked","markdown","html","other","asked_markdown","asked_html","unknown"]}},"paths":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"total":{"type":"number"},"markdown":{"type":"number","description":"Sent markdown: seen in the response's content type."},"html":{"type":"number","description":"Sent HTML: seen in the response's content type."},"other":{"type":"number","description":"Sent something else (JSON, an image…), seen in the response."},"asked_markdown":{"type":"number","description":"Asked for markdown (a .md path or the Accept header); what was sent wasn't seen. Next.js and Vercel snippets run before the response, so this is all they know."},"asked_html":{"type":"number","description":"Asked for HTML (the Accept header); what was sent wasn't seen."},"unknown":{"type":"number","description":"Neither the response's type nor the request's Accept header was reported (an older snippet)."}},"required":["path","total","markdown","html","other","asked_markdown","asked_html","unknown"]}}},"required":["total","formats","outcomes","agents","paths"],"description":"Markdown or HTML, overall, per agent and per path, and how the responses went. Sent (markdown, html, other) is read from the response, which only a Cloudflare Worker, Express, Node snippet or Cloudflare Logpush sees; asked for (asked_markdown, asked_html) is read from the request alone, all a Next.js or Vercel snippet can see. A site that answers one URL either way by what is asked (content negotiation) sends what was asked for; a site that doesn't may send HTML whatever was asked. unknown: neither was reported (an older snippet)."}},"required":["site_id","days","since","source","baseline","total","verified","lettervisit_verified","by_purpose","agents","top_paths","counting_since","path_count","path_kinds","daily","earlier","floors","served"]}},{"name":"visitors_get_human_visits","description":"People who read a site, the human half of visitors_get_agent_visits: page loads by people per day (anonymous visitors included, not only the ones identified), distinct visitors and sessions, and how many were identified (`identified_people`, `identified_share`: most visitors match no person, so a low share is normal). Counted from `baseline` (earlier numbers are a different source), the same daily rows The Site reads. Who the identified people are is visitors_get_people. `people_counted` false says why people aren't counted (`message`). Private. Args: site_id?, days (1-90, default 30). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"host":{"type":["string","null"]},"days":{"type":"number"},"since":{"type":"string","description":"The first day counted: the window's start, or the site's baseline when later."},"until":{"type":"string"},"baseline":{"description":"The first day the site counts from; earlier numbers are a different source.","type":["string","null"]},"tracking":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"people_counted":{"type":"boolean","description":"False when people aren't counted for the site; `message` says why."},"message":{"type":["string","null"]},"humans":{"type":"number","description":"Page loads by people, anonymous visitors included."},"unique_visitors":{"description":"Distinct visitors in the window; null when the read failed.","type":["number","null"]},"sessions":{"type":["number","null"]},"uniques_exact":{"type":"boolean","description":"Unique visitors and sessions are exact only for a window read in one request."},"identified_people":{"description":"People named in the window (visitors_get_people's total); null when it couldn't be read.","type":["number","null"]},"identified_share":{"description":"identified_people / unique_visitors, 0 to 1. Most visitors match no person: a low share is normal.","type":["number","null"]},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"humans":{"type":"number"},"unique_visitors":{"type":"number"}},"required":["day","humans","unique_visitors"]}}},"required":["site_id","host","days","since","until","baseline","tracking","people_counted","message","humans","unique_visitors","sessions","uniques_exact","identified_people","identified_share","daily"]}},{"name":"visitors_get_citations","description":"AI referrals: visits that clicked through to the site from an AI answer (ChatGPT, Claude, Perplexity, Gemini, Copilot), per source, the pages they landed on (page_count of them), and a daily series. Reported by the Visitors script from the referrer or utm_source, once a visit stays past a glance (3s or 15% scroll); nothing about the person is kept, so these are visits, not people. Not how often AI answers cite the site: that is the GEO probes (get_ai_visibility_bank). Needs the AI-agents module on. counting_since is the day the script was first seen: days before it weren't counted, not zero. Private. Args: site_id?, days (1-90, default 30). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"days":{"type":"number"},"since":{"type":"string"},"total":{"type":"number","description":"Visits that clicked through to the site from an AI answer. Visits, not people: one person landing twice counts twice."},"counting_since":{"description":"The day the Visitors script was first seen on the site; nothing was counted before it.","type":["string","null"]},"page_count":{"type":"number","description":"Distinct pages landed on; top_pages is the busiest 20."},"by_source":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["chatgpt","claude","perplexity","gemini","copilot"]},"visits":{"type":"number"},"avg_seconds":{"type":"number"}},"required":["source","visits","avg_seconds"]}},"top_pages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"visits":{"type":"number"},"sources":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Visits per AI source landing on this page."}},"required":["path","visits","sources"]}},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"visits":{"type":"number"}},"required":["day","visits"]}},"floors":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["bots","verified","live_reads","citations","people"]},"notes":{"type":"array","items":{"type":"string"},"description":"What this number can't see, in a sentence each."}},"required":["measure","notes"]},"description":"Every number is a floor: per measure, what it can't see (structural, and from this site's own evidence)."}},"required":["site_id","days","since","total","counting_since","page_count","by_source","top_pages","daily","floors"]}},{"name":"visitors_add_connector","description":"Connect a site's host so Visitors reads every request it handles, with no code deployed: a Vercel log drain (Pro or Enterprise) or a Cloudflare Logpush job (Enterprise). Sees what a server snippet can't: requests the host's firewall blocked and pages served from its cache, and what each agent was served. Returns the delivery URL, the secret and the steps for the host's dashboard; adding the same kind again returns the live one. Args: site_id?, kind (vercel | cloudflare). Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"type":"string","enum":["vercel","cloudflare"],"description":"The host: vercel (a log drain; Pro or Enterprise) or cloudflare (a Logpush job; Enterprise)."}},"required":["kind"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["vercel","cloudflare"]},"url":{"type":"string","description":"Where the host delivers its request logs."},"secret":{"type":"string","description":"Authenticates the host's deliveries: Vercel's drain secret, or Cloudflare's bearer token."},"verify_token":{"type":"string","description":"Vercel only: what the endpoint check expects back (answered automatically)."},"created_at":{"type":"string"},"last_received_at":{"type":["string","null"]},"received_count":{"type":"number"},"last_error":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"string"},"description":"What to do in the host's dashboard, in order."}},"required":["id","kind","url","secret","verify_token","created_at","last_received_at","received_count","last_error","steps"]}},{"name":"visitors_list_connectors","description":"A site's host connectors (Vercel log drains, Cloudflare Logpush): each with its delivery URL, secret, steps, when it last delivered, how many requests it has delivered, and its last error. Args: site_id?. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"connectors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["vercel","cloudflare"]},"url":{"type":"string","description":"Where the host delivers its request logs."},"secret":{"type":"string","description":"Authenticates the host's deliveries: Vercel's drain secret, or Cloudflare's bearer token."},"verify_token":{"type":"string","description":"Vercel only: what the endpoint check expects back (answered automatically)."},"created_at":{"type":"string"},"last_received_at":{"type":["string","null"]},"received_count":{"type":"number"},"last_error":{"type":["string","null"]},"steps":{"type":"array","items":{"type":"string"},"description":"What to do in the host's dashboard, in order."}},"required":["id","kind","url","secret","verify_token","created_at","last_received_at","received_count","last_error","steps"]}}},"required":["connectors"]}},{"name":"visitors_remove_connector","description":"Stop accepting a host connector's deliveries (remove the drain or Logpush job in the host too). What it delivered so far stays counted. Args: site_id?, connector_id. Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"connector_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The connector to stop, from visitors_list_connectors."}},"required":["connector_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"removed":{"type":"boolean"}},"required":["removed"]}},{"name":"visitors_admin_coverage","description":"Staff only. How far to trust one Visitors site's numbers, and what we need to fix: every coverage check (server snippet reporting, agents recognized, visitor addresses passed, script on the site, people counted) with its state, what it measured, the reason, the fix and since when; per measure (bots, verified, live_reads, citations, people) the worst state, its reasons and its floors; and the User-Agents the snippet sent that name no agent we know. States: ok, warn, degraded (a check failed, the number is short), blind (not collected), off (the owner's choice), unknown (too early). Customers see only the floors (`floors` on each Visitors report); the checks are ours to act on, on /superadmin/lettervisit. Args: site_id (any org). Refused unless the key was created by Letter staff. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The Visitors site, in any org."}},"required":["site_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"domain":{"type":["string","null"]},"evaluated_at":{"type":"string"},"since":{"type":"string","description":"The checks read the evidence from this day (a 14-day window)."},"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"The worst measure's state."},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"measures":{"type":"array","items":{"type":"string","enum":["bots","verified","live_reads","citations","people"]}},"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"ok: checks pass (still a floor); warn: worth a look; degraded: a check failed, the number is short; blind: not collected (not installed); off: the owner turned it off; unknown: too little evidence yet."},"value":{"description":"What the check measured: a share, a rate, days quiet.","type":["number","null"]},"detail":{"type":"string"},"fix":{"type":["string","null"]},"since":{"description":"The first recorded day of the current state, when the daily sweep has it.","type":["string","null"]}},"required":["id","title","measures","state","value","detail","fix","since"]}},"measures":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["bots","verified","live_reads","citations","people"]},"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"ok: checks pass (still a floor); warn: worth a look; degraded: a check failed, the number is short; blind: not collected (not installed); off: the owner turned it off; unknown: too little evidence yet."},"headline":{"type":"string","description":"The worst failing check's reason, or the first floor when every check passes."},"reasons":{"type":"array","items":{"type":"object","properties":{"check":{"type":"string"},"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"ok: checks pass (still a floor); warn: worth a look; degraded: a check failed, the number is short; blind: not collected (not installed); off: the owner turned it off; unknown: too little evidence yet."},"detail":{"type":"string"},"fix":{"type":["string","null"]}},"required":["check","state","detail","fix"]}},"floors":{"type":"array","items":{"type":"string"},"description":"What this number can't see: structural, and from this site's own evidence."}},"required":["measure","state","headline","reasons","floors"],"description":"How far to trust one number, and why (docs/architecture/visitor-coverage.md)."}},"unknown_agents":{"type":"array","items":{"type":"object","properties":{"user_agent":{"type":"string"},"requests":{"type":"number"}},"required":["user_agent","requests"]},"description":"User-Agents the snippet sent that name no agent we know, most first (\"(empty)\": none was sent)."}},"required":["site_id","domain","evaluated_at","since","state","checks","measures","unknown_agents"]}},{"name":"visitors_get_page_traffic","description":"Visitors page traffic on a site, per page: people (page loads, unique visitors and sessions, anonymous visitors included) and AI agents (requests seen by the server snippet, by_agent as each User-Agent claims, e.g. openai-crawler). Busiest pages first; totals are the whole site. Who was identified is totals.identified_people (the People tab's count; the list is visitors_get_people); identified_humans counts only explicit identify() calls and reads 0 under de-anonymization. People are counted only while identification is on (billed per identified person) and the site is proven (`unavailable` says why not, `people_counted` false); AI agents are listed either way (`source`) and need the server snippet. Private, read live, nothing stored. Args: site_id?, days (1-90, default 28), path? (glob, e.g. /docs/*), agent? (an agent id from the report), limit? (default 200). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"Window in days, today included (default 28).","type":"integer","minimum":1,"maximum":90},"path":{"type":"string","minLength":1,"maxLength":300,"description":"A path, or a glob where * matches anything (e.g. /docs/*)."},"agent":{"description":"Only this AI agent, by the id the report names (e.g. openai-crawler, anthropic-crawler).","type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_.-]+$"},"limit":{"description":"Most pages returned, busiest first (default 200).","type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"host":{"type":["string","null"]},"days":{"type":"number"},"since":{"type":"string"},"until":{"type":"string"},"tracking":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"source":{"type":"string","enum":["visitors","lettervisit"],"description":"Where the agent numbers come from: visitors (the site's traffic source) or lettervisit (LetterVisit's own rows, for a site that source doesn't count yet)."},"people_counted":{"type":"boolean","description":"Whether people are counted per page. False: AI agents only."},"unavailable":{"anyOf":[{"type":"string","enum":["not_enabled","identification_off","pending_registration","site_not_verified","unconfigured","failed"]},{"type":"null"}],"description":"Why people aren't counted per page: they need identification on, registered, and the site proven. AI agents are still listed, from LetterVisit's own rows."},"message":{"type":["string","null"]},"totals":{"type":"object","properties":{"humans":{"type":"number","description":"Page loads by people, anonymous visitors included."},"unique_visitors":{"type":"number"},"sessions":{"type":"number"},"identified_humans":{"description":"Page loads the site's own code named with an explicit identify() call. Not de-anonymization: reads 0 even when visitors are identified. For who was identified read identified_people or visitors_get_people.","type":["number","null"]},"identified_people":{"description":"People identified on the site in the window, as the People tab counts them; null when unread.","type":["number","null"]},"ai_requests":{"type":"number","description":"Requests from AI agents, seen by the server snippet."},"other_bots":{"type":"number","description":"Other bots and unclassified requests; in neither count."}},"required":["humans","unique_visitors","sessions","identified_humans","identified_people","ai_requests","other_bots"],"description":"The whole site over the window, whatever the path or agent filter."},"uniques_exact":{"type":"boolean","description":"Unique visitors and sessions are exact up to 31 days; longer windows add them per 31-day part."},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","description":"The agent as its User-Agent claims to be, e.g. openai-crawler."},"operator":{"type":"string","description":"Who runs it, e.g. openai."},"requests":{"type":"number"}},"required":["agent","operator","requests"]},"description":"AI agents across the site, most requests first."},"server_collected":{"type":"boolean","description":"Whether any server-snippet request has been counted (AI agents need it)."},"last_pixel_at":{"type":["string","null"]},"last_server_at":{"type":["string","null"]},"baseline":{"description":"The first day the site counts from (its move to this traffic source); since is never earlier.","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"humans":{"type":"number"},"unique_visitors":{"type":"number"},"sessions":{"type":"number"},"identified_humans":{"type":["number","null"]},"ai_requests":{"type":"number"},"by_agent":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string","description":"The agent as its User-Agent claims to be, e.g. openai-crawler."},"operator":{"type":"string","description":"Who runs it, e.g. openai."},"requests":{"type":"number"}},"required":["agent","operator","requests"]}},"other_bots":{"type":"number"},"people":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"company":{"type":["string","null"]}},"required":["id","name","company"]}},{"type":"null"}],"description":"Identified people on this page."}},"required":["path","humans","unique_visitors","sessions","identified_humans","ai_requests","by_agent","other_bots","people"]}},"floors":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string","enum":["bots","verified","live_reads","citations","people"]},"notes":{"type":"array","items":{"type":"string"},"description":"What this number can't see, in a sentence each."}},"required":["measure","notes"]},"description":"Every number is a floor: per measure, what it can't see (structural, and from this site's own evidence)."}},"required":["site_id","host","days","since","until","tracking","source","people_counted","unavailable","message","totals","uniques_exact","agents","server_collected","last_pixel_at","last_server_at","baseline","pages","floors"]}},{"name":"visitors_compare_agents","description":"Two agents side by side on one site: the pages each read that the other didn't, and the pages both read, with counts. E.g. which docs ChatGPT read this week that Claude didn't: a=chatgpt, b=claude, days=7, path=/docs/*. Private. Args: site_id?, a, b, days (1-90, default 30), path? (glob), purpose?. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"a":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_-]+$","description":"The first agent (e.g. chatgpt)."},"b":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9_-]+$","description":"The agent to compare against (e.g. claude)."},"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90},"path":{"type":"string","minLength":1,"maxLength":300,"description":"A path, or a glob where * matches anything (e.g. /docs/*)."},"purpose":{"type":"string","enum":["live","index","training","automated_browser","unknown"],"description":"Why it came: live (answering a person's question right now), index (building a search index), training (collecting for a model), automated_browser (an agent driving a browser)."}},"required":["a","b"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"days":{"type":"number"},"since":{"type":"string"},"source":{"type":"string","enum":["visitors","lettervisit"],"description":"visitors: the site's current traffic source, counted from `baseline`, agents named as their User-Agent claims (openai-crawler-user, …) and not verified. lettervisit: LetterVisit's own counts, for a site not moved yet."},"a":{"type":"object","properties":{"agent":{"type":"string"},"total":{"type":"number"},"pages":{"type":"number"}},"required":["agent","total","pages"]},"b":{"type":"object","properties":{"agent":{"type":"string"},"total":{"type":"number"},"pages":{"type":"number"}},"required":["agent","total","pages"]},"only_a":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"a":{"type":"number"}},"required":["path","a"]},"description":"Pages a read and b didn't."},"only_b":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"b":{"type":"number"}},"required":["path","b"]},"description":"Pages b read and a didn't."},"both":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"a":{"type":"number"},"b":{"type":"number"}},"required":["path","a","b"]}}},"required":["site_id","days","since","source","a","b","only_a","only_b","both"]}},{"name":"visitors_export","description":"Export a site's Visitors data for a window: every agent visit row (date, agent, kind, purpose, verified, path, requests) and every AI citation row (date, source, path, visits, seconds). format=csv adds CSV text of both. Private. Args: site_id?, days (1-90, default 30), format? (json|csv). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90},"format":{"description":"json rows only, or csv text as well (default json).","type":"string","enum":["json","csv"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"days":{"type":"number"},"since":{"type":"string"},"source":{"type":"string","enum":["visitors","lettervisit"],"description":"visitors: the site's current traffic source, counted from `baseline`, agents named as their User-Agent claims (openai-crawler-user, …) and not verified. lettervisit: LetterVisit's own counts, for a site not moved yet."},"format":{"type":"string","enum":["json","csv"]},"agent_visits":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"agent":{"type":"string"},"kind":{"type":"string"},"purpose":{"type":"string","enum":["live","index","training","automated_browser","unknown"]},"verified":{"type":["boolean","null"]},"path":{"type":"string"},"requests":{"type":"number"}},"required":["date","agent","kind","purpose","verified","path","requests"]}},"citations":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"source":{"type":"string"},"path":{"type":"string"},"visits":{"type":"number"},"seconds_total":{"type":"number"}},"required":["date","source","path","visits","seconds_total"]}},"csv":{"anyOf":[{"type":"object","properties":{"agent_visits":{"type":"string"},"citations":{"type":"string"}},"required":["agent_visits","citations"]},{"type":"null"}]}},"required":["site_id","days","since","source","format","agent_visits","citations","csv"]}},{"name":"visitors_get_people","description":"Visitors de-anonymization: the people and companies identified on a site (who visited), pulled live and never stored, most recent visit first. The answer to 'who visited' / 'which identified visitors came'; not Letterprove (signed proof of a product's customers) and not identified_humans in traffic reads. Each person first identified on the site is billed once in tokens (identified_visitor); reading is free. Empty when identification is off or pending, the site isn't proven (`tracking`), the read failed (`error`), or nobody came in the window; `paused` says why nobody new is being identified. Private. Args: site_id?, limit (1-200, default 50), days (1-90, default 30). Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"Most people returned (default 50).","type":"integer","minimum":1,"maximum":200},"days":{"description":"Window in days (default 30).","type":"integer","minimum":1,"maximum":90}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"enabled":{"type":"boolean"},"tracking":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."},"configured":{"type":"boolean"},"site_verified":{"type":"boolean"},"window_days":{"type":"number"},"error":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["forbidden","failed"]},"message":{"type":"string"}},"required":["reason","message"]},{"type":"null"}]},"companies":{"type":"array","items":{"type":"object","properties":{"company":{"type":["string","null"]},"domain":{"type":["string","null"]},"people":{"type":"number"},"visits":{"type":"number"},"last_seen":{"type":"string"}},"required":["company","domain","people","visits","last_seen"]}},"visitors":{"type":"array","items":{"type":"object","properties":{"person_id":{"type":"string"},"name":{"type":["string","null"]},"title":{"type":["string","null"]},"company":{"type":["string","null"]},"company_domain":{"type":["string","null"]},"email":{"type":["string","null"]},"linkedin_url":{"type":["string","null"]},"location":{"type":["string","null"]},"seniority":{"type":["string","null"]},"job_function":{"type":["string","null"]},"visit_count":{"type":"number"},"first_seen":{"type":"string"},"last_seen":{"type":"string"},"is_hot_lead":{"type":"boolean"}},"required":["person_id","name","title","company","company_domain","email","linkedin_url","location","seniority","job_function","visit_count","first_seen","last_seen","is_hot_lead"]},"description":"Most recent visit first."},"total":{"type":"number","description":"People identified in the window."},"paused":{"anyOf":[{"type":"object","properties":{"reason":{"type":"string","enum":["card_required","payment_failed","spend_cap"],"description":"card_required: the account's free tokens are used. payment_failed: its payment didn't go through (update the card or pay the invoice). spend_cap: its monthly spend cap is reached."},"since":{"type":"string","description":"When Letter paused it."},"message":{"type":"string","description":"What to tell the person, and what resumes it."}},"required":["reason","since","message"]},{"type":"null"}],"description":"Letter paused identification because new work would be refused: nobody more is identified (each person is billed) until tokens or a card arrive, or the cap is raised; then it resumes by itself. Null while it runs. Turning it on while paused goes through the same gate."}},"required":["site_id","enabled","tracking","configured","site_verified","window_days","error","companies","visitors","total","paused"]}},{"name":"visitors_set_identification","description":"Turn visitor identification (de-anonymization) on or off for a site. Turning it on needs site proof: the site's key in its Visitors script tag on its homepage (visitors_verify_site); refused with site_not_verified otherwise, and with payment_required when the account can't take new work. It is on by default: it turns on by itself once site proof passes, on every account. Billed per person identified: 25 tokens each (identified_visitor); a turn-on the account can't take yet is paused and resumes by itself. A call here records the owner's choice, and an owner's off is never overridden. No DNS record. The same script tag then loads identification; no second script. Args: site_id?, enabled. Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"enabled":{"type":"boolean","description":"Turn visitor identification (de-anonymization) on or off."}},"required":["enabled"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"enabled":{"type":"boolean"},"tracking":{"type":"string","enum":["off","active","pending_registration"],"description":"Visitor identification: off; active (registered, identifying); pending_registration (on, registration not landed yet; turning it on again retries)."}},"required":["site_id","enabled","tracking"]}},{"name":"visitors_verify_site","description":"Check now that the site's Visitors script, carrying its own key, is in the HTML of its homepage (https://<domain>/ or www.). That proves control of the site, which visitor identification needs; it does not turn identification on (visitors_set_identification does, billed per person). Runs on its own every ten minutes after the script first loads until proven; a failed check here never clears a proof, but the daily re-check clears it after three misses over two days. A tag injected by a tag manager can't be seen. Args: site_id?. Requires capability visitors:write.","capability":"visitors:write","inputSchema":{"type":"object","properties":{"site_id":{"description":"The Visitors site. Optional when the org has exactly one site.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site_id":{"type":"string"},"verified":{"type":"boolean"},"checked_url":{"type":["string","null"]},"reason":{"type":"string"},"verified_at":{"type":["string","null"]}},"required":["site_id","verified","checked_url","reason","verified_at"]}},{"name":"visitors_admin_report","description":"Staff only. Lettervisit across every org: each Visitors site with who signed up, how far the install got (script, site proof, server snippet, identification) and its agent visits, crawler visits, AI citations and human visits in the window; totals against the previous window, a daily series, the agents reading them, and lettervisit.com's own traffic. Test orgs and staff-only orgs are left out unless include_internal. Args: days (1-90, default 30), include_internal?. Refused unless the key was created by Letter staff. Requires capability visitors:read.","capability":"visitors:read","inputSchema":{"type":"object","properties":{"days":{"description":"Window in days, today included (default 30).","type":"integer","minimum":1,"maximum":90},"include_internal":{"description":"Include test orgs and orgs with only Letter staff in them (default false).","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"days":{"type":"number"},"since":{"type":"string"},"generated_at":{"type":"string"},"include_internal":{"type":"boolean"},"totals":{"type":"object","properties":{"sites":{"type":"number"},"signups":{"type":"number","description":"Visitors sites added in the window."},"signups_previous":{"type":"number","description":"Sites added in the window before it, same length."},"new_orgs":{"type":"number","description":"Signups whose org was created in the same sitting."},"agent_visits":{"type":"number"},"crawler_visits":{"type":"number"},"citations":{"type":"number"},"human_visits":{"type":"number"},"sites_with_agent_visits":{"type":"number"}},"required":["sites","signups","signups_previous","new_orgs","agent_visits","crawler_visits","citations","human_visits","sites_with_agent_visits"]},"install":{"type":"object","properties":{"signed_up":{"type":"number"},"script_installed":{"type":"number"},"site_verified":{"type":"number"},"server_snippet":{"type":"number"},"identification_on":{"type":"number"}},"required":["signed_up","script_installed","site_verified","server_snippet","identification_on"],"description":"All time, over the sites in scope: how many reached each install step."},"daily":{"type":"array","items":{"type":"object","properties":{"day":{"type":"string"},"signups":{"type":"number"},"agent_visits":{"type":"number"},"citations":{"type":"number"}},"required":["day","signups","agent_visits","citations"]}},"top_agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"visits":{"type":"number"},"sites":{"type":"number"}},"required":["agent","visits","sites"]}},"sites":{"type":"array","items":{"type":"object","properties":{"site_id":{"type":"string"},"domain":{"type":"string"},"org_id":{"type":"string"},"org_name":{"type":["string","null"]},"owner_email":{"description":"The org's first member outside Letter staff.","type":["string","null"]},"new_org":{"type":"boolean"},"internal":{"type":"boolean"},"created_at":{"type":"string"},"stage":{"type":"string","enum":["signed_up","script_installed","site_verified","fully_installed"]},"script_seen_at":{"type":["string","null"]},"server_seen_at":{"type":["string","null"]},"site_verified_at":{"type":["string","null"]},"site_failing":{"type":"boolean"},"identification":{"type":"string","enum":["off","on"]},"agent_visits":{"type":"number"},"crawler_visits":{"type":"number"},"citations":{"type":"number"},"human_visits":{"type":"number"},"last_agent_visit_at":{"type":["string","null"]},"coverage":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"ok: checks pass (still a floor); warn: worth a look; degraded: a check failed, the number is short; blind: not collected (not installed); off: the owner turned it off; unknown: too little evidence yet."},"headline":{"description":"The worst failing check's reason; null when all pass.","type":["string","null"]},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"state":{"type":"string","enum":["ok","warn","degraded","blind","off","unknown"],"description":"ok: checks pass (still a floor); warn: worth a look; degraded: a check failed, the number is short; blind: not collected (not installed); off: the owner turned it off; unknown: too little evidence yet."}},"required":["id","state"]}}},"required":["state","headline","checks"]},{"type":"null"}],"description":"How far to trust this site's numbers now; null when it couldn't be read."}},"required":["site_id","domain","org_id","org_name","owner_email","new_org","internal","created_at","stage","script_seen_at","server_seen_at","site_verified_at","site_failing","identification","agent_visits","crawler_visits","citations","human_visits","last_agent_visit_at","coverage"]}},"coverage_gaps":{"type":"object","properties":{"since":{"type":"string"},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"states":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"},"description":"Sites per state for this check."}},"required":["id","title","states"]},"description":"A check failing on many sites at once is a Visitors bug, not the sites'."},"unknown_agents":{"type":"array","items":{"type":"object","properties":{"user_agent":{"type":"string"},"requests":{"type":"number"},"sites":{"type":"number"}},"required":["user_agent","requests","sites"]},"description":"User-Agents naming no agent we know: candidates for the agent registry."},"unverifiable":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"requests":{"type":"number"},"sites":{"type":"number"}},"required":["agent","requests","sites"]},"description":"AI-agent visits from vendors that publish nothing to verify against."}},"required":["since","checks","unknown_agents","unverifiable"],"description":"What to fix in Visitors itself, across the sites in view."},"marketing":{"anyOf":[{"type":"object","properties":{"configured":{"type":"boolean"},"hint":{"type":["string","null"]},"days":{"type":"number"},"pageviews":{"type":"number"},"signup_started":{"type":"number"},"signup_completed":{"type":"number"}},"required":["configured","hint","days","pageviews","signup_started","signup_completed"]},{"type":"null"}],"description":"lettervisit.com traffic from the marketing funnel; null when PostHog couldn't be read."}},"required":["days","since","generated_at","include_internal","totals","install","daily","top_agents","sites","coverage_gaps","marketing"]}},{"name":"run_site_audit","description":"Start a site audit: crawls the site (robots.txt-aware, same-origin), checks every page for SEO issues (broken links, duplicate or missing titles and descriptions, redirect chains, orphan pages, canonical problems, thin content, and more), and optionally runs Lighthouse on a sample of pages. Runs in the background. Poll get_audit_status, then read get_audit_issues. If the site rate limits the crawler it slows down and retries; pages it still cannot read are honestly flagged as blocked or rate-limited rather than misreported. Requires capability audit:write.","capability":"audit:write","inputSchema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"Start URL to crawl."},"maxPages":{"description":"Page budget for the crawl (default 100).","type":"integer","minimum":10,"maximum":10000},"renderJavaScript":{"description":"Render JavaScript before auditing, for sites whose content or links load client-side or whose bot protection blocks the plain crawler. Slower, and limited to 1,000 pages. Default false.","type":"boolean"},"runLighthouse":{"description":"Run Lighthouse on a sample of up to 10 representative pages (default false, it adds several minutes of wall-clock time). Pass true only when the user wants performance or Core Web Vitals detail.","type":"boolean"}},"required":["url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auditId":{"description":"The started audit, or null on an expected refusal (capacity, already running, rendering unavailable).","type":["string","null"]},"message":{"type":"string","description":"What happened and what to call next."}},"required":["auditId","message"]}},{"name":"get_audit_status","description":"Check the progress of a site audit (phase, pages crawled, Lighthouse progress). Not read-only: it may reconcile a dead run by marking its audit failed. Omit auditId for the most recent audit. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{"auditId":{"description":"Audit ID. If omitted, uses the organization's most recent audit.","type":"string"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"object","properties":{"id":{"type":"string"},"startUrl":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed"]},"pagesCrawled":{"type":"number"},"pagesTotal":{"type":"number"},"lighthouseTotal":{"type":"number"},"lighthouseCompleted":{"type":"number"},"lighthouseFailed":{"type":"number"},"currentPhase":{"type":"string","enum":["discovery","crawling","lighthouse","finalizing","completed","failed"]},"errorCode":{"type":["string","null"]},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]}},"required":["id","startUrl","status","pagesCrawled","pagesTotal","lighthouseTotal","lighthouseCompleted","lighthouseFailed","currentPhase","errorCode","startedAt","completedAt"]},"summary":{"type":"string","description":"One line: status, phase, page and Lighthouse progress, and the useful next step."}},"required":["status","summary"]}},{"name":"get_audit_issues","description":"Read the prioritized issue report from a site audit. Every issue carries a howToFix with concrete remediation steps an agent can act on. Reading only. Omit auditId for the most recent audit. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{"auditId":{"description":"Audit ID. If omitted, uses the organization's most recent audit.","type":"string"},"severity":{"description":"Only return issues of this severity.","type":"string","enum":["critical","warning","info"]},"issueType":{"description":"Only return issues of this type. One of: blocked-page, javascript-rendering-suspected, rate-limited-page, crawl-rate-limited, server-error, broken-internal-link, missing-title, broken-page, duplicate-title, duplicate-meta-description, duplicate-content, missing-meta-description, missing-h1, multiple-h1, redirect-chain, redirect-loop, canonical-conflict, thin-content, images-missing-alt, orphan-page, no-outgoing-links, title-too-long, title-too-short, meta-description-too-long, meta-description-too-short, heading-order-skip, slow-response, noindex-page, canonicalized-page, deep-page","type":"string"},"limit":{"description":"Max issues to return (default 200).","type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auditId":{"type":"string"},"startUrl":{"type":"string"},"totalIssues":{"type":"number","description":"Matching issues before the limit was applied."},"summary":{"type":"array","items":{"type":"object","properties":{"issueType":{"type":"string"},"title":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"count":{"type":"number"}},"required":["issueType","title","severity","count"]}},"issues":{"type":"array","items":{"type":"object","properties":{"severity":{"type":"string","enum":["critical","warning","info"]},"issueType":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"details":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"howToFix":{"type":["string","null"]}},"required":["severity","issueType","title","url","details","howToFix"]}},"message":{"description":"Present when the audit recorded no matching issues.","type":"string"}},"required":["auditId","startUrl","totalIssues","summary","issues"]}},{"name":"get_audit_pages","description":"List crawled pages from a site audit with per-page SEO data (status, title, description, word count, indexability, crawl depth, link counts). Reading only. Omit auditId for the most recent audit. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{"auditId":{"description":"Audit ID. If omitted, uses the organization's most recent audit.","type":"string"},"fetchClass":{"description":"Filter by fetch outcome (\"blocked\" = the site's bot protection challenged the crawler; \"rate_limited\" = a 429 prevented the crawler from reading the page).","type":"string","enum":["ok","blocked","rate_limited","error"]},"statusCode":{"description":"Filter by exact HTTP status code.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"urlContains":{"description":"Filter to URLs containing this substring.","type":"string"},"limit":{"description":"Max pages to return (default 100).","type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auditId":{"type":"string"},"startUrl":{"type":"string"},"total":{"type":"number","description":"Matching pages before the limit was applied."},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"statusCode":{"type":"number"},"fetchClass":{"type":"string","enum":["ok","blocked","rate_limited","error"],"description":"How the fetch went as an ACCESS outcome: a plain 404/500 is \"ok\" (the server answered); \"blocked\" is bot protection, \"rate_limited\" a 429 the crawler could not wait out, \"error\" a transport failure."},"redirectUrl":{"type":["string","null"]},"title":{"type":["string","null"]},"metaDescription":{"type":["string","null"]},"wordCount":{"type":"number"},"isIndexable":{"type":"boolean"},"crawlDepth":{"description":"Null for pages discovered only through a sitemap.","type":["number","null"]},"inSitemap":{"type":"boolean"},"internalLinkCount":{"type":"number"},"responseTimeMs":{"type":["number","null"]}},"required":["id","url","statusCode","fetchClass","redirectUrl","title","metaDescription","wordCount","isIndexable","crawlDepth","inSitemap","internalLinkCount","responseTimeMs"]}}},"required":["auditId","startUrl","total","pages"]}},{"name":"list_site_audits","description":"List the organization's site audits, newest first, with status, page counts and whether Lighthouse ran. Reading only. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"audits":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"startUrl":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed"]},"pagesCrawled":{"type":"number"},"pagesTotal":{"type":"number"},"ranLighthouse":{"type":"boolean"},"purpose":{"anyOf":[{"type":"string","enum":["print"]},{"type":"null"}],"description":"\"print\" when an enclosure ran it for a prospect's picture; it doesn't count toward the org's audit capacity."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]}},"required":["id","startUrl","status","pagesCrawled","pagesTotal","ranLighthouse","purpose","startedAt","completedAt"]}},"message":{"description":"Present when the organization has no audits yet.","type":"string"}},"required":["audits"]}},{"name":"delete_site_audit","description":"Delete one site audit and everything it stored: its crawled pages, issues and Lighthouse results. This also frees the audit capacity those reservations held. Needs a key minted by an organization admin. Requires an exact auditId; it never defaults to the most recent audit. Destructive and irreversible. Requires capability audit:write.","capability":"audit:write","inputSchema":{"type":"object","properties":{"auditId":{"type":"string","minLength":1,"description":"Audit ID to delete. Required; never defaults to the most recent audit."}},"required":["auditId"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"message":{"type":"string"}},"required":["success","message"]}},{"name":"run_trust_audit","description":"Start a trust audit: a model review of marketing copy. Reads the subject's and every tracked competitor's site, has a fixed panel of trust lenses (skeptical buyer, security reviewer, burned operator, technical evaluator, budget owner) judge why each would or wouldn't be trusted, has buyer personas pick a company from the copy alone, then writes a report plus a fix prompt constrained to the org's recorded product truth. Reads site copy only: not Letterprove proofs, not Visitors traffic. Runs in the background (several minutes). Poll get_trust_audit_status, then read get_trust_audit_report. Competitors come from list_competitors; none tracked = refused. Requires capability audit:write.","capability":"audit:write","inputSchema":{"type":"object","properties":{"subjectUrl":{"description":"The subject site to audit. Defaults to the organization's own domain.","type":"string","minLength":1,"maxLength":2048},"maxCompetitors":{"description":"How many tracked competitors to compare against (default 5).","type":"integer","minimum":1,"maximum":8},"personaCount":{"description":"How many buyer personas to simulate (default 4).","type":"integer","minimum":2,"maximum":6},"excludeDomains":{"description":"Tracked competitor domains to leave out of this audit. Per-run only: the organization's tracked competitor list is untouched.","maxItems":64,"type":"array","items":{"type":"string","minLength":1,"maxLength":253}}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auditId":{"description":"The started trust audit, or null on an expected refusal (no competitors, already running).","type":["string","null"]},"message":{"type":"string","description":"What happened and what to call next."}},"required":["auditId","message"]}},{"name":"get_trust_audit_status","description":"Check the progress of a trust audit (phase, sites read, panel verdicts, persona choices). Not read-only: it may reconcile a dead run by marking its audit failed. Omit auditId for the most recent trust audit. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{"auditId":{"description":"Trust audit ID. If omitted, uses the organization's most recent trust audit.","type":"string"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"object","properties":{"id":{"type":"string"},"subjectDomain":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed","cancelled"]},"currentPhase":{"type":"string","enum":["gathering","trust_panel","personas","synthesis","completed","failed"]},"companiesTotal":{"type":"number"},"companiesRead":{"type":"number"},"verdictsTotal":{"type":"number"},"verdictsCompleted":{"type":"number"},"personasTotal":{"type":"number"},"personasCompleted":{"type":"number"},"errorCode":{"type":["string","null"]},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]}},"required":["id","subjectDomain","status","currentPhase","companiesTotal","companiesRead","verdictsTotal","verdictsCompleted","personasTotal","personasCompleted","errorCode","startedAt","completedAt"]},"summary":{"type":"string","description":"One line: status, phase, judgment progress, and the useful next step."}},"required":["status","summary"]}},{"name":"get_trust_audit_report","description":"Read a trust audit's full machine-readable report: the scoreboard (panel trust score and persona votes per company), every lens verdict with its reasons and missing signals, every persona's choice and reasoning, the computed standing (rank with ties, the leader, vote share), the synthesized findings (v2: each issue bound to its fix, explained in plain words, with who raised it; older runs carry v1 lists), and the fix prompt (copy fixes constrained to the recorded product truth, so applying it cannot drift the feature set). Reading only. Omit auditId for the most recent trust audit. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{"auditId":{"description":"Trust audit ID. If omitted, uses the organization's most recent trust audit.","type":"string"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"auditId":{"type":"string"},"subjectDomain":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed","cancelled"]},"companies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"name":{"type":["string","null"]},"isSubject":{"type":"boolean"},"siteSource":{"description":"Which reader produced the copy (context-dev | firecrawl | site-text).","type":["string","null"]},"readError":{"description":"Set when no reader could reach the site; it was left out of the comparison.","type":["string","null"]},"trustScore":{"description":"Mean of the trust panel's scores for this company (0-100).","type":["number","null"]},"personaVotes":{"type":"number","description":"How many personas chose this company."}},"required":["id","domain","name","isSubject","siteSource","readError","trustScore","personaVotes"]}},"verdicts":{"type":"array","items":{"type":"object","properties":{"companyId":{"type":"string"},"lens":{"type":"string"},"lensLabel":{"type":"string"},"wouldTrust":{"type":"boolean"},"trustScore":{"type":"number"},"reasonsFor":{"type":"array","items":{"type":"string"}},"reasonsAgainst":{"type":"array","items":{"type":"string"}},"signalsPresent":{"type":"array","items":{"type":"string"}},"signalsMissing":{"type":"array","items":{"type":"string"},"description":"Trust signals missing FROM THE SITE — never claims about the product itself."},"summary":{"type":["string","null"]}},"required":["companyId","lens","lensLabel","wouldTrust","trustScore","reasonsFor","reasonsAgainst","signalsPresent","signalsMissing","summary"]}},"personas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"profile":{"type":"object","properties":{"role":{"type":"string"},"context":{"type":"string"},"priorities":{"type":"array","items":{"type":"string"}}},"required":["role","context","priorities"]},"chosenDomain":{"type":["string","null"]},"choseSubject":{"type":["boolean","null"]},"ranking":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"rank":{"type":"number"},"note":{"type":"string"}},"required":["domain","rank","note"]}},{"type":"null"}]},"reasoning":{"type":["string","null"]},"subjectObjection":{"description":"What held this persona back from the subject specifically — the raw material for the fix prompt.","type":["string","null"]}},"required":["id","slug","name","profile","chosenDomain","choseSubject","ranking","reasoning","subjectObjection"]}},"standing":{"anyOf":[{"type":"object","properties":{"rank":{"description":"Competition rank on panel trust score (ties share a rank).","type":["number","null"]},"tiedWith":{"type":"array","items":{"type":"string"}},"total":{"type":"number"},"leader":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"trustScore":{"type":"number"}},"required":["label","trustScore"]},{"type":"null"}]},"subjectScore":{"type":["number","null"]},"subjectVotes":{"type":"number"},"totalVotes":{"type":"number"},"mostVotes":{"type":"boolean"}},"required":["rank","tiedWith","total","leader","subjectScore","subjectVotes","totalVotes","mostVotes"]},{"type":"null"}],"description":"Where the subject stands, computed from the scoreboard (never model-written)."},"report":{"anyOf":[{"oneOf":[{"type":"object","properties":{"version":{"type":"number","const":1},"headline":{"type":"string"},"whatsWorking":{"type":"array","items":{"type":"object","properties":{"finding":{"type":"string"},"evidence":{"type":"string"}},"required":["finding","evidence"]}},"whatsNotWorking":{"type":"array","items":{"type":"object","properties":{"finding":{"type":"string"},"evidence":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]}},"required":["finding","evidence","severity"]}},"quickWins":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"detail":{"type":"string"}},"required":["title","detail"]}}},"required":["version","headline","whatsWorking","whatsNotWorking","quickWins"]},{"type":"object","properties":{"version":{"type":"number","const":2},"headline":{"type":"string"},"issues":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"whyItMatters":{"type":"string","description":"Plain-language consequence, terms of art defined in place."},"fix":{"type":"string","description":"The one copy change that resolves this issue."},"evidence":{"type":"string"},"flaggedBy":{"type":"array","items":{"type":"string"},"description":"Lens labels and persona names that raised it, validated against this run's rows."}},"required":["title","severity","whyItMatters","fix","evidence","flaggedBy"]},"description":"What holds the subject's copy back, most damaging first, each bound to its fix."},"strengths":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"evidence":{"type":"string"},"flaggedBy":{"type":"array","items":{"type":"string"}}},"required":["title","evidence","flaggedBy"]}},"glossary":{"type":"array","items":{"type":"object","properties":{"term":{"type":"string"},"meaning":{"type":"string"}},"required":["term","meaning"]},"description":"Every term of art the report uses, with a one-sentence plain meaning."}},"required":["version","headline","issues","strengths","glossary"]}]},{"type":"null"}],"description":"The synthesized findings: version 2 binds each issue to its fix; version 1 (older runs) keeps them in separate lists. Null until the audit completes."},"fixPrompt":{"description":"A ready-to-use prompt that fixes the easy copy problems without drifting the product's truth or feature set. Null until the audit completes.","type":["string","null"]},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"message":{"description":"Present while the audit is still running.","type":"string"}},"required":["auditId","subjectDomain","status","companies","verdicts","personas","standing","report","fixPrompt","startedAt","completedAt"]}},{"name":"list_trust_audits","description":"List the organization's trust audits, newest first, with status and company counts. Reading only. Requires capability audit:read.","capability":"audit:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"audits":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"subjectDomain":{"type":"string"},"status":{"type":"string","enum":["running","completed","failed","cancelled"]},"companiesTotal":{"type":"number"},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]}},"required":["id","subjectDomain","status","companiesTotal","startedAt","completedAt"]}},"message":{"description":"Present when the organization has no trust audits yet.","type":"string"}},"required":["audits"]}},{"name":"cancel_trust_audit","description":"Cancel a running trust audit. Everything already judged (site reads, verdicts, personas) is kept and readable via get_trust_audit_report; the run just stops and cannot be resumed. Idempotent: cancelling an already-finished audit reports its final state. Requires an exact auditId; it never defaults to the most recent audit. Requires capability audit:write.","capability":"audit:write","inputSchema":{"type":"object","properties":{"auditId":{"type":"string","minLength":1,"description":"Trust audit ID to cancel. Required; never defaults to the most recent audit."}},"required":["auditId"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["running","completed","failed","cancelled"],"description":"The run's state after the call (idempotent on already-terminal runs)."},"message":{"type":"string"}},"required":["status","message"]}},{"name":"delete_trust_audit","description":"Delete one trust audit and everything it stored: the read site copy, panel verdicts, personas, report and fix prompt. Needs a key minted by an organization admin. Requires an exact auditId; it never defaults to the most recent audit. Destructive and irreversible. Requires capability audit:write.","capability":"audit:write","inputSchema":{"type":"object","properties":{"auditId":{"type":"string","minLength":1,"description":"Trust audit ID to delete. Required; never defaults to the most recent audit."}},"required":["auditId"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"message":{"type":"string"}},"required":["success","message"]}},{"name":"get_eval_scoreboard","description":"How the writing pipeline is doing on YOUR recently published posts: every measure in the catalogue (blocked sources admitted, never-named companies mentioned, links that split a number, unsourced figures, stale prices, …) grouped by pipeline stage, with how many posts it applied to, how many it failed on and the failure rate over the window. `scope: org` (default) reads the whole org; `scope: collection` with `collection_id` reads one collection. `judgeReliance` says how much of the catalogue, and how many of your cases, still rest on a model judge rather than a deterministic check. Read-only; the scan that fills it runs daily. Requires capability evals:read.","capability":"evals:read","inputSchema":{"type":"object","properties":{"scope":{"description":"Your whole org (the default), or one collection","type":"string","enum":["org","collection"]},"collection_id":{"description":"Required when scope is collection; must belong to your org","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"days":{"description":"The window in days, 1–90; the scoreboard reads 14 when omitted","type":"integer","minimum":1,"maximum":90}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"scope":{"type":"string","enum":["fleet","org","collection"]},"id":{"type":["string","null"]},"days":{"type":"number"},"since":{"type":"string"},"stages":{"type":"array","items":{"type":"object","properties":{"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]},"measures":{"type":"array","items":{"type":"object","properties":{"measure":{"type":"string"},"title":{"type":"string"},"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]},"posts":{"type":"number","description":"Posts the measure applied to in the window"},"failing":{"type":"number","description":"Posts it failed on"},"count":{"type":"number","description":"Offending items summed over those posts"},"rate":{"type":"number","description":"failing / posts; 0 when nothing applied"},"severity":{"type":"string","enum":["critical","warn"]},"kind":{"type":"string","enum":["deterministic","judge"]},"owner":{"type":"string","enum":["research","kernel","links","mentions","accuracy","pipeline"],"description":"The measure's default owner, where a failure goes when no chain step can be placed"},"routed":{"description":"Failing posts by the owner each failure is routed to: the owner of the chain step whose output first carried it","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"introducedAt":{"description":"Failing posts by the chain step the failure came in at (outline, kernel, a pass); `unplaced` when none","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}}},"required":["measure","title","stage","posts","failing","count","rate","severity","kind","owner"]}},"failingPosts":{"type":"number","description":"Distinct posts failing at least one measure of the stage"}},"required":["stage","measures","failingPosts"]}},"totals":{"type":"object","properties":{"measures":{"type":"number"},"posts":{"type":"number","description":"Distinct posts measured in the window"},"failingPosts":{"type":"number"},"failures":{"type":"number","description":"Failing (post, measure) pairs"},"count":{"type":"number","description":"Offending items in total"}},"required":["measures","posts","failingPosts","failures","count"]},"judgeReliance":{"type":"object","properties":{"measures":{"type":"number"},"cases":{"type":"number"}},"required":["measures","cases"],"description":"How much of the catalogue, and how many of your cases, still rest on a model judge"},"coverage":{"type":"object","properties":{"measured":{"type":"number","description":"Posts in the window the scan has measured"},"publishedInWindow":{"type":"number"},"share":{"type":"number","description":"measured / publishedInWindow, capped at 1"}},"required":["measured","publishedInWindow","share"]}},"required":["scope","id","days","since","stages","totals","judgeReliance","coverage"]}},{"name":"list_eval_cases","description":"The regression cases captured from YOUR posts, newest first, each with its latest grading. A case is one bug stated as 'this measure (or this assertion) must pass on this frozen post record'. Status tells you where it is: `open` = captured and expected to FAIL until the fix lands; `guarding` = fixed, must PASS on every commit and fails CI if it regresses; `proposed` = awaiting staff confirmation; `retired` / `rejected` carry a reason. Filter with `status` and `stage`. Requires capability evals:read.","capability":"evals:read","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["proposed","open","guarding","retired","rejected"]},"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"cases":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Stable slug the case is addressed by everywhere"},"title":{"type":"string"},"description":{"type":"string"},"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]},"expected":{"type":"object","properties":{"measure":{"description":"A catalogue measure that must pass once the bug is fixed","type":["string","null"]},"assertion":{"description":"Else a plain-language assertion the calibrated judge grades","type":["string","null"]},"owner":{"description":"A routing case: the measure fails on the fixture and must route to this owner (the step that introduced it)","anyOf":[{"type":"string","enum":["research","kernel","links","mentions","accuracy","pipeline"]},{"type":"null"}]}},"required":["measure","assertion"]},"replay":{"description":"The pure pipeline stage re-run over the fixture before grading","type":["string","null"]},"status":{"type":"string","enum":["proposed","open","guarding","retired","rejected"],"description":"proposed (awaiting staff), open (expected to FAIL until fixed), guarding (must PASS on every commit), retired, rejected"},"source":{"type":"string","enum":["staff","customer","agent","scoreboard","backfill","repo"]},"incident":{"type":["string","null"]},"fixPr":{"type":["string","null"]},"fixCommit":{"type":["string","null"]},"firstPassedSha":{"type":["string","null"]},"judgeCandidate":{"type":"boolean","description":"True while the case rests on a judge rather than a deterministic measure"},"fixture":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"repo"},"path":{"type":"string"}},"required":["kind","path"]},{"type":"object","properties":{"kind":{"type":"string","const":"db"},"fixtureId":{"type":"string"}},"required":["kind","fixtureId"]},{"type":"object","properties":{"kind":{"type":"string","const":"inline"}},"required":["kind"]}]},"orgId":{"type":["string","null"]},"articleId":{"type":["string","null"]},"reportedBy":{"type":["string","null"]},"createdAt":{"type":"string"},"latestResult":{"anyOf":[{"type":"object","properties":{"caseKey":{"type":"string"},"measure":{"type":"string"},"verdict":{"type":"string","enum":["pass","fail","skip","error"]},"gate":{"type":"string","enum":["ok","regression","unexpected_pass","expected_fail","ignored"],"description":"regression = a guarding case failed (CI fails); unexpected_pass = an open case now passes (ready to guard); expected_fail = an open case still fails, as it should"},"delta":{"type":"string","enum":["same","regressed","fixed","new","no_baseline"],"description":"Compared with the baseline run"},"count":{"type":"number"},"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":["string","null"]},"index":{"type":"number"}},"required":["kind","id"]},"excerpt":{"type":"string"},"note":{"type":"string"}},"required":["ref","excerpt"]}},"ms":{"type":"number"},"replay":{"type":["string","null"]},"judge":{"anyOf":[{"type":"object","properties":{"model":{"type":"string"},"confidence":{"type":"number"},"rationale":{"type":"string"}},"required":["model","confidence","rationale"]},{"type":"null"}]},"runId":{"type":"string"}},"required":["caseKey","measure","verdict","gate","delta","count","summary","evidence","ms","replay","runId"]},{"type":"null"}],"description":"The case's most recent grading, or null when it has never run"}},"required":["key","title","description","stage","expected","replay","status","source","incident","fixPr","fixCommit","firstPassedSha","judgeCandidate","fixture","orgId","articleId","reportedBy","createdAt","latestResult"]}}},"required":["cases"]}},{"name":"capture_eval_case","description":"Turn a bad post into a regression case: freeze the post's whole pipeline record (research sources, ledger, outline, brief, kernel trace, live HTML) as a fixture and state what must hold once the bug is fixed — EITHER `expected.measure` (a catalogue key, see get_eval_scoreboard) OR `expected.assertion` (one plain sentence the calibrated judge grades). Name the post by `article_id`, or by the `research_run_id` / `kernel_job_id` that produced it; it must belong to your org. The case is graded immediately on current code and the answer comes back as `detectsIssue`: TRUE means the case FAILS today, i.e. it catches what it claims — this is what you want when reporting a live bug. FALSE means the measure or assertion does NOT fire on this post, so the case would guard nothing: tighten the assertion or pick the right measure before relying on it. `source` is recorded as agent and `reported_by` as this key's name. Writes only the eval ledger; the post itself is untouched. Requires capability evals:write.","capability":"evals:write","inputSchema":{"type":"object","properties":{"article_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"research_run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kernel_job_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":"string","minLength":3,"maxLength":200},"description":{"type":"string","minLength":1,"maxLength":4000},"expected":{"type":"object","properties":{"measure":{"type":"string","minLength":1,"maxLength":80},"assertion":{"type":"string","minLength":8,"maxLength":1000}}},"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]},"auto_pen_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"published_version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"replay":{"type":"string","minLength":1,"maxLength":60},"link":{"type":"string","maxLength":2000,"format":"uri"},"incident":{"type":"string","minLength":1,"maxLength":500},"status":{"description":"open (the default: expected to fail until fixed) or proposed (awaiting staff confirmation)","type":"string","enum":["open","proposed"]}},"required":["title","description","expected"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"case":{"type":"object","properties":{"key":{"type":"string","description":"Stable slug the case is addressed by everywhere"},"title":{"type":"string"},"description":{"type":"string"},"stage":{"type":"string","enum":["research","brief","links","mentions","accuracy","pipeline","shred","enhance"]},"expected":{"type":"object","properties":{"measure":{"description":"A catalogue measure that must pass once the bug is fixed","type":["string","null"]},"assertion":{"description":"Else a plain-language assertion the calibrated judge grades","type":["string","null"]},"owner":{"description":"A routing case: the measure fails on the fixture and must route to this owner (the step that introduced it)","anyOf":[{"type":"string","enum":["research","kernel","links","mentions","accuracy","pipeline"]},{"type":"null"}]}},"required":["measure","assertion"]},"replay":{"description":"The pure pipeline stage re-run over the fixture before grading","type":["string","null"]},"status":{"type":"string","enum":["proposed","open","guarding","retired","rejected"],"description":"proposed (awaiting staff), open (expected to FAIL until fixed), guarding (must PASS on every commit), retired, rejected"},"source":{"type":"string","enum":["staff","customer","agent","scoreboard","backfill","repo"]},"incident":{"type":["string","null"]},"fixPr":{"type":["string","null"]},"fixCommit":{"type":["string","null"]},"firstPassedSha":{"type":["string","null"]},"judgeCandidate":{"type":"boolean","description":"True while the case rests on a judge rather than a deterministic measure"},"fixture":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"repo"},"path":{"type":"string"}},"required":["kind","path"]},{"type":"object","properties":{"kind":{"type":"string","const":"db"},"fixtureId":{"type":"string"}},"required":["kind","fixtureId"]},{"type":"object","properties":{"kind":{"type":"string","const":"inline"}},"required":["kind"]}]},"orgId":{"type":["string","null"]},"articleId":{"type":["string","null"]},"reportedBy":{"type":["string","null"]},"createdAt":{"type":"string"}},"required":["key","title","description","stage","expected","replay","status","source","incident","fixPr","fixCommit","firstPassedSha","judgeCandidate","fixture","orgId","articleId","reportedBy","createdAt"]},"fixtureId":{"type":"string"},"firstResult":{"type":"object","properties":{"caseKey":{"type":"string"},"measure":{"type":"string"},"verdict":{"type":"string","enum":["pass","fail","skip","error"]},"gate":{"type":"string","enum":["ok","regression","unexpected_pass","expected_fail","ignored"],"description":"regression = a guarding case failed (CI fails); unexpected_pass = an open case now passes (ready to guard); expected_fail = an open case still fails, as it should"},"delta":{"type":"string","enum":["same","regressed","fixed","new","no_baseline"],"description":"Compared with the baseline run"},"count":{"type":"number"},"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":["string","null"]},"index":{"type":"number"}},"required":["kind","id"]},"excerpt":{"type":"string"},"note":{"type":"string"}},"required":["ref","excerpt"]}},"ms":{"type":"number"},"replay":{"type":["string","null"]},"judge":{"anyOf":[{"type":"object","properties":{"model":{"type":"string"},"confidence":{"type":"number"},"rationale":{"type":"string"}},"required":["model","confidence","rationale"]},{"type":"null"}]}},"required":["caseKey","measure","verdict","gate","delta","count","summary","evidence","ms","replay"],"description":"The case graded immediately on current code"},"detectsIssue":{"type":"boolean","description":"True when the case FAILS on current code, i.e. it catches the bug it claims to. False means it does not — the measure or assertion does not detect the issue on this post, so fix the expectation before relying on it"}},"required":["case","fixtureId","firstResult","detectsIssue"]}},{"name":"run_eval_suite","description":"Grade the regression suite now, record-only (no model, no network): every open and guarding case captured from YOUR posts plus the repo's synthetic corpus, each replayed over its frozen fixture with the code that is deployed and graded by its catalogue measure. Cases captured with `expected.assertion` need the model judge, so they show verdict skip here: a skip is not a pass. Narrow to `case_keys`; pass `baseline: \"latest\"` (or a run id) to get per-case deltas against that run. The run's `verdict` is `fail` only when a GUARDING case regressed; an open case that still fails is the expected state and shows as `gate: expected_fail`, and an open case that now passes shows as `unexpected_pass`, ready for staff to flip to guarding. Synchronous and quick (seconds); the run is persisted and readable with get_eval_suite_run. Requires capability evals:write.","capability":"evals:write","inputSchema":{"type":"object","properties":{"case_keys":{"maxItems":500,"type":"array","items":{"type":"string","minLength":1}},"baseline":{"anyOf":[{"type":"string","const":"latest"},{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"description":"The persisted run id (poll it with get_eval_suite_run)","type":["string","null"]},"mode":{"type":"string","enum":["record","full"]},"gitSha":{"type":["string","null"]},"deploymentUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"baselineRunId":{"type":["string","null"]},"results":{"type":"array","items":{"type":"object","properties":{"caseKey":{"type":"string"},"measure":{"type":"string"},"verdict":{"type":"string","enum":["pass","fail","skip","error"]},"gate":{"type":"string","enum":["ok","regression","unexpected_pass","expected_fail","ignored"],"description":"regression = a guarding case failed (CI fails); unexpected_pass = an open case now passes (ready to guard); expected_fail = an open case still fails, as it should"},"delta":{"type":"string","enum":["same","regressed","fixed","new","no_baseline"],"description":"Compared with the baseline run"},"count":{"type":"number"},"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":["string","null"]},"index":{"type":"number"}},"required":["kind","id"]},"excerpt":{"type":"string"},"note":{"type":"string"}},"required":["ref","excerpt"]}},"ms":{"type":"number"},"replay":{"type":["string","null"]},"judge":{"anyOf":[{"type":"object","properties":{"model":{"type":"string"},"confidence":{"type":"number"},"rationale":{"type":"string"}},"required":["model","confidence","rationale"]},{"type":"null"}]}},"required":["caseKey","measure","verdict","gate","delta","count","summary","evidence","ms","replay"]}},"totals":{"type":"object","properties":{"cases":{"type":"number"},"pass":{"type":"number"},"fail":{"type":"number"},"skip":{"type":"number"},"error":{"type":"number"},"regressed":{"type":"number"},"fixed":{"type":"number"},"expectedFail":{"type":"number"},"readyToGuard":{"type":"number"},"judgeGraded":{"type":"number"}},"required":["cases","pass","fail","skip","error","regressed","fixed","expectedFail","readyToGuard","judgeGraded"]},"verdict":{"type":"string","enum":["pass","fail"],"description":"fail = at least one guarding case regressed; open cases that still fail do not fail the run"},"triggeredBy":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","error"]},"isBaseline":{"type":"boolean"},"error":{"type":["string","null"]},"unresolved":{"description":"Ledger cases the run could not load a fixture for; the run went ahead without them","type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"reason":{"type":"string"}},"required":["key","reason"]}}},"required":["id","mode","gitSha","deploymentUrl","startedAt","finishedAt","baselineRunId","results","totals","verdict","triggeredBy"]}},"required":["run"]}},{"name":"get_eval_suite_run","description":"One suite run by id — its verdict, totals and per-case results. Results are scoped to what you can see: cases captured from YOUR posts and the repo's synthetic corpus; another org's cases are left out and the totals and verdict are over what remains. `gate: regression` on a guarding case is the thing to act on; `delta` compares each case with the run's baseline. Requires capability evals:read.","capability":"evals:read","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["run_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"description":"The persisted run id (poll it with get_eval_suite_run)","type":["string","null"]},"mode":{"type":"string","enum":["record","full"]},"gitSha":{"type":["string","null"]},"deploymentUrl":{"type":["string","null"]},"startedAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"baselineRunId":{"type":["string","null"]},"results":{"type":"array","items":{"type":"object","properties":{"caseKey":{"type":"string"},"measure":{"type":"string"},"verdict":{"type":"string","enum":["pass","fail","skip","error"]},"gate":{"type":"string","enum":["ok","regression","unexpected_pass","expected_fail","ignored"],"description":"regression = a guarding case failed (CI fails); unexpected_pass = an open case now passes (ready to guard); expected_fail = an open case still fails, as it should"},"delta":{"type":"string","enum":["same","regressed","fixed","new","no_baseline"],"description":"Compared with the baseline run"},"count":{"type":"number"},"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":["string","null"]},"index":{"type":"number"}},"required":["kind","id"]},"excerpt":{"type":"string"},"note":{"type":"string"}},"required":["ref","excerpt"]}},"ms":{"type":"number"},"replay":{"type":["string","null"]},"judge":{"anyOf":[{"type":"object","properties":{"model":{"type":"string"},"confidence":{"type":"number"},"rationale":{"type":"string"}},"required":["model","confidence","rationale"]},{"type":"null"}]}},"required":["caseKey","measure","verdict","gate","delta","count","summary","evidence","ms","replay"]}},"totals":{"type":"object","properties":{"cases":{"type":"number"},"pass":{"type":"number"},"fail":{"type":"number"},"skip":{"type":"number"},"error":{"type":"number"},"regressed":{"type":"number"},"fixed":{"type":"number"},"expectedFail":{"type":"number"},"readyToGuard":{"type":"number"},"judgeGraded":{"type":"number"}},"required":["cases","pass","fail","skip","error","regressed","fixed","expectedFail","readyToGuard","judgeGraded"]},"verdict":{"type":"string","enum":["pass","fail"],"description":"fail = at least one guarding case regressed; open cases that still fail do not fail the run"},"triggeredBy":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","error"]},"isBaseline":{"type":"boolean"},"error":{"type":["string","null"]},"unresolved":{"description":"Ledger cases the run could not load a fixture for; the run went ahead without them","type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"reason":{"type":"string"}},"required":["key","reason"]}}},"required":["id","mode","gitSha","deploymentUrl","startedAt","finishedAt","baselineRunId","results","totals","verdict","triggeredBy"]}},"required":["run"]}},{"name":"list_sites","description":"The hosts this organization has mapped into The Site (each is a root of the tree), plus read-only suggestions of hosts it already has elsewhere (its domain, phantom domains, collections' public URLs, audited sites, repo sites). Every host carries `kind`: `site` (the org's own site; its domain always is one), `staging` (a free or preview host such as *.vercel.app, *.netlify.app or *.pages.dev, a staging./dev./preview. subdomain, a deploy preview) or `phantom` (one of the org's phantom blogs). The Instances → Site view offers only `site` hosts; the others stay readable with get_site_tree. Mapping is always an explicit map_site call. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"siteGroupId":{"description":"Seam: several hosts under one site. Always null for now.","type":["string","null"]},"sitemapUrl":{"type":["string","null"]},"mapStatus":{"type":"string","enum":["unmapped","mapping","mapped","failed"]},"mapTruncated":{"type":"boolean","description":"The last map hit its cap; the sitemap lists more."},"mapCap":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"mapError":{"type":["string","null"]},"readNote":{"description":"A mapped site Letter could not read (it refused the crawler, or never answered): why, and what to allow. Null when it read.","type":["string","null"]},"mapStartedAt":{"type":["string","null"]},"mappedAt":{"type":["string","null"]},"mappedAuditId":{"description":"The completed audit folded in at map time.","type":["string","null"]},"pagesTotal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","description":"Who mapped it: user | api_key | agent."},"createdByUserId":{"type":["string","null"]},"createdAt":{"type":"string"},"relation":{"type":"string","enum":["owned","watched"]},"refreshEnabled":{"type":"boolean"},"lastRefreshAt":{"type":["string","null"]},"lastRefresh":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"suggested":{"description":"Pages the suggestion pass added; absent when it didn't run.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"description":"Pages the promise pass read for what they promise (Build's first source); absent when none.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},{"type":"null"}],"description":"What the last refresh did and skipped."},"lastJevAt":{"type":["string","null"]},"sitemapReport":{"description":"Sitemap entries the last map read and did not keep: URLs on another host.","anyOf":[{"type":"object","properties":{"offHost":{"type":"array","items":{"type":"string"}},"offHostCount":{"type":"number"}},"required":["offHost","offHostCount"]},{"type":"null"}]},"kind":{"type":"string","enum":["site","staging","phantom"],"description":"site: the org's own site (always its domain); staging: a free or preview host (*.vercel.app, *.netlify.app, *.pages.dev …), a staging./dev./preview. subdomain or a deploy preview; phantom: one of the org's phantom blogs. The Site view offers only `site` hosts."}},"required":["id","host","siteGroupId","sitemapUrl","mapStatus","mapTruncated","mapCap","mapError","mapStartedAt","mappedAt","mappedAuditId","pagesTotal","source","createdByUserId","createdAt","relation","refreshEnabled","lastRefreshAt","lastRefresh","lastJevAt","kind"]}},"suggestions":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"from":{"type":"array","items":{"type":"string"},"description":"org_domain | phantom | collection | audit | repo_site"},"sitemapUrl":{"type":["string","null"]},"kind":{"type":"string","enum":["site","staging","phantom"],"description":"site: the org's own site (always its domain); staging: a free or preview host (*.vercel.app, *.netlify.app, *.pages.dev …), a staging./dev./preview. subdomain or a deploy preview; phantom: one of the org's phantom blogs. The Site view offers only `site` hosts."}},"required":["host","from","sitemapUrl","kind"]},"description":"Hosts the org already has, not yet mapped. Read-only."}},"required":["sites","suggestions"]}},{"name":"map_site","description":"Map a host: read its sitemap (discovered from robots.txt and the usual paths when not given, sitemap indexes followed), fold in the latest completed audit's crawl and canonicals, merge URL variants (www, http/https, trailing slash, query), lift locale prefixes into an attribute, and run the deterministic family detectors. Bounded by max_pages and idempotent: run it again any time. Pages that vanish from the sitemap are marked gone, never deleted. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"url":{"description":"Any URL on the site; its host is mapped.","type":"string","maxLength":2048},"sitemap_url":{"description":"The sitemap to read. Discovered from robots.txt and the usual paths when omitted.","type":"string","maxLength":2048},"max_pages":{"description":"URLs to keep this run (default 2000, max 20000). More than that marks the map truncated.","type":"integer","minimum":10,"maximum":20000},"audit":{"description":"Start a site audit when the host has none (default true), so one call yields map and audit.","type":"boolean"},"relation":{"description":"owned: the full loop (signals, Jev, proposals); watched: map and audit facts only. Decided from the org's domains when omitted.","type":"string","enum":["owned","watched"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"siteGroupId":{"description":"Seam: several hosts under one site. Always null for now.","type":["string","null"]},"sitemapUrl":{"type":["string","null"]},"mapStatus":{"type":"string","enum":["unmapped","mapping","mapped","failed"]},"mapTruncated":{"type":"boolean","description":"The last map hit its cap; the sitemap lists more."},"mapCap":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"mapError":{"type":["string","null"]},"readNote":{"description":"A mapped site Letter could not read (it refused the crawler, or never answered): why, and what to allow. Null when it read.","type":["string","null"]},"mapStartedAt":{"type":["string","null"]},"mappedAt":{"type":["string","null"]},"mappedAuditId":{"description":"The completed audit folded in at map time.","type":["string","null"]},"pagesTotal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","description":"Who mapped it: user | api_key | agent."},"createdByUserId":{"type":["string","null"]},"createdAt":{"type":"string"},"relation":{"type":"string","enum":["owned","watched"]},"refreshEnabled":{"type":"boolean"},"lastRefreshAt":{"type":["string","null"]},"lastRefresh":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"suggested":{"description":"Pages the suggestion pass added; absent when it didn't run.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"description":"Pages the promise pass read for what they promise (Build's first source); absent when none.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},{"type":"null"}],"description":"What the last refresh did and skipped."},"lastJevAt":{"type":["string","null"]},"sitemapReport":{"description":"Sitemap entries the last map read and did not keep: URLs on another host.","anyOf":[{"type":"object","properties":{"offHost":{"type":"array","items":{"type":"string"}},"offHostCount":{"type":"number"}},"required":["offHost","offHostCount"]},{"type":"null"}]}},"required":["id","host","siteGroupId","sitemapUrl","mapStatus","mapTruncated","mapCap","mapError","mapStartedAt","mappedAt","mappedAuditId","pagesTotal","source","createdByUserId","createdAt","relation","refreshEnabled","lastRefreshAt","lastRefresh","lastJevAt"]},"summary":{"type":"object","properties":{"host":{"type":"string"},"sitemapUrl":{"type":["string","null"]},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fromSitemap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fromAuditOnly":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"aliases":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Listed URLs whose canonical points at another page."},"locales":{"type":"array","items":{"type":"string"}},"gone":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"truncated":{"type":"boolean"},"cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"families":{"type":"object","properties":{"created":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"updated":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"members":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["created","updated","members"]},"auditId":{"type":["string","null"]},"auditStarted":{"description":"The audit this map started, when the host had none.","type":["string","null"]}},"required":["host","sitemapUrl","pages","fromSitemap","fromAuditOnly","aliases","locales","gone","truncated","cap","families","auditId","auditStarted"]},"message":{"type":"string"}},"required":["site","summary","message"]}},{"name":"refresh_site","description":"Run The Site's refresh loop for one host now, under the same bounds the daily cron uses: re-map when the sitemap moved or the map is 7+ days old (or force_map), re-audit an owned host whose audit is older than 14 days (or force_audit), run the quick check, refresh the per-page signal cache (Search Console, citations in Letter's own AI probes, AI-crawler reads, links), read focus pages with Jev under the host's daily budget, and after a re-map run the page-idea, Build and Fix suggestion passes (skip_suggest skips them). Spends model credit. Stops starting steps after 8 minutes, so a slow host still records what it did; the steps it left are named in skipped. Returns what it did and what it skipped. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"force_map":{"description":"Re-map even when nothing moved.","type":"boolean"},"force_audit":{"description":"Start an audit even when a recent one exists.","type":"boolean"},"skip_observe":{"description":"Skip the Jev pass.","type":"boolean"},"skip_suggest":{"description":"Skip the suggestion passes (missing pages, Build and Fix suggestions): no model calls, for a light check such as an open console's.","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"site":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"siteGroupId":{"description":"Seam: several hosts under one site. Always null for now.","type":["string","null"]},"sitemapUrl":{"type":["string","null"]},"mapStatus":{"type":"string","enum":["unmapped","mapping","mapped","failed"]},"mapTruncated":{"type":"boolean","description":"The last map hit its cap; the sitemap lists more."},"mapCap":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"mapError":{"type":["string","null"]},"readNote":{"description":"A mapped site Letter could not read (it refused the crawler, or never answered): why, and what to allow. Null when it read.","type":["string","null"]},"mapStartedAt":{"type":["string","null"]},"mappedAt":{"type":["string","null"]},"mappedAuditId":{"description":"The completed audit folded in at map time.","type":["string","null"]},"pagesTotal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","description":"Who mapped it: user | api_key | agent."},"createdByUserId":{"type":["string","null"]},"createdAt":{"type":"string"},"relation":{"type":"string","enum":["owned","watched"]},"refreshEnabled":{"type":"boolean"},"lastRefreshAt":{"type":["string","null"]},"lastRefresh":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"suggested":{"description":"Pages the suggestion pass added; absent when it didn't run.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"description":"Pages the promise pass read for what they promise (Build's first source); absent when none.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},{"type":"null"}],"description":"What the last refresh did and skipped."},"lastJevAt":{"type":["string","null"]},"sitemapReport":{"description":"Sitemap entries the last map read and did not keep: URLs on another host.","anyOf":[{"type":"object","properties":{"offHost":{"type":"array","items":{"type":"string"}},"offHostCount":{"type":"number"}},"required":["offHost","offHostCount"]},{"type":"null"}]}},"required":["id","host","siteGroupId","sitemapUrl","mapStatus","mapTruncated","mapCap","mapError","mapStartedAt","mappedAt","mappedAuditId","pagesTotal","source","createdByUserId","createdAt","relation","refreshEnabled","lastRefreshAt","lastRefresh","lastJevAt"]},"report":{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},"message":{"type":"string"}},"required":["site","report","message"]}},{"name":"unmap_site","description":"Remove a mapped host from The Site with everything that hangs off it: pages, raw URLs, signals, observations (dismissals included), families and proposals. The audits stay; map it again any time. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"message":{"type":"string"}},"required":["host","message"]}},{"name":"get_llms_txt","description":"The llms.txt Letter writes for one of the organization's sites: the company's name and one line, then its main pages by section, each a link with what it covers, from the latest audit's pages (their titles and descriptions). Returns the file, the URL it should answer at, and where it goes. For a site no connected repository builds: put it beside the site's root files (a site whose repo is connected gets it as a pull request through fix_site_observations instead). Read-only. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"text":{"type":"string","description":"The llms.txt, as it should be served at /llms.txt (plain text, markdown)."},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many pages it lists."},"serve_at":{"type":"string","description":"The URL it should answer at."},"where":{"type":"string","description":"Where it goes, in one sentence."}},"required":["host","text","pages","serve_at","where"]}},{"name":"get_site_tree","description":"The site tree, one folder at a time: the node at `path` with its children to `depth`, each carrying rollups (pages, gone, open observations by tone and by rung, bound counts, families, proposals), the family coverage living there (\"3 of 11\"), `next` (what is next in the pattern the folder keeps: a family's next uncovered item, or, when a letterbase owns the folder, its automation's next idea with the letterbase's state) and ONE item: the worst open observation at or under it, paired with its proposal or the registry's default action. `items` are the open items at `path` left to choose; with `subtree` they cover everything under it, the same key across pages collapsed into one group (the Issues panel on a folder). An item a repo change is already fixing (running, sent to the org's agent, or its PR open) is in neither `items` nor `focus`: both are for choosing what to fix (list_repo_changes shows what is under way). `items` lists the node's fixes first, then what it lacks (remedy build: no page for a ranking target, offering or competitor; a section of one page) and what could do more there (remedy improve: stale, losing clicks, losing readers); only fixes count on a node's badge, and the Site's Issues panel lists only fixes (what it lacks is Build's, a page that could do more is its letterbase's). Host-wide the same split is `focus` (the attention policy's short list for the host, the Fix tab; `focus_size` asks for the ranking's top n or all instead; a high-`risk` migration waits for accepted fixes whatever its rung), `build` (what Build's suggested builds are grounded in) and `improve` (a letterbase's posts that could do more, the page with the most reach first; never shown on the Site). Children in a fold-by-default family (archive pages) are hidden unless `unfold`. With `since`, every rollup also counts pages `added` and `removed` and the tree carries the host-wide `changes`. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"A tree path such as /blog or /pricing; the root (/) when omitted.","type":"string","maxLength":2048},"depth":{"description":"How many levels of children to include (default 1). Each folder carries its rollups either way.","type":"integer","minimum":0,"maximum":6},"unfold":{"description":"Include children a fold-by-default family (archive pages) would hide.","type":"boolean"},"subtree":{"description":"Return `items` for the whole subtree under `path`, the same key across its pages collapsed into one group with `members` (what the Issues panel shows on a folder). Default: the items at exactly `path`.","type":"boolean"},"since":{"description":"ISO time. Count what changed since then: every node's rollup carries `added` (live pages first seen since, never the first map's pages) and `removed` (pages gone since), and `changes` lists them host-wide with per-top-folder counts. Omit for 0 counts and no `changes`.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"focus_size":{"description":"How many items `focus` returns: the top n of the ranking, or \"all\" (the Fix tab's Show top 10 / Show all). Omit for the attention policy's short list: the 3 highest-priority open items, always, one structure item among them.","anyOf":[{"type":"integer","minimum":1,"maximum":1000},{"type":"string","const":"all"}]}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"site":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"siteGroupId":{"description":"Seam: several hosts under one site. Always null for now.","type":["string","null"]},"sitemapUrl":{"type":["string","null"]},"mapStatus":{"type":"string","enum":["unmapped","mapping","mapped","failed"]},"mapTruncated":{"type":"boolean","description":"The last map hit its cap; the sitemap lists more."},"mapCap":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"mapError":{"type":["string","null"]},"readNote":{"description":"A mapped site Letter could not read (it refused the crawler, or never answered): why, and what to allow. Null when it read.","type":["string","null"]},"mapStartedAt":{"type":["string","null"]},"mappedAt":{"type":["string","null"]},"mappedAuditId":{"description":"The completed audit folded in at map time.","type":["string","null"]},"pagesTotal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","description":"Who mapped it: user | api_key | agent."},"createdByUserId":{"type":["string","null"]},"createdAt":{"type":"string"},"relation":{"type":"string","enum":["owned","watched"]},"refreshEnabled":{"type":"boolean"},"lastRefreshAt":{"type":["string","null"]},"lastRefresh":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"suggested":{"description":"Pages the suggestion pass added; absent when it didn't run.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"description":"Pages the promise pass read for what they promise (Build's first source); absent when none.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},{"type":"null"}],"description":"What the last refresh did and skipped."},"lastJevAt":{"type":["string","null"]},"sitemapReport":{"description":"Sitemap entries the last map read and did not keep: URLs on another host.","anyOf":[{"type":"object","properties":{"offHost":{"type":"array","items":{"type":"string"}},"offHostCount":{"type":"number"}},"required":["offHost","offHostCount"]},{"type":"null"}]}},"required":["id","host","siteGroupId","sitemapUrl","mapStatus","mapTruncated","mapCap","mapError","mapStartedAt","mappedAt","mappedAuditId","pagesTotal","source","createdByUserId","createdAt","relation","refreshEnabled","lastRefreshAt","lastRefresh","lastJevAt"]},{"type":"null"}]},"root":{"$ref":"#/$defs/__schema0"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},"description":"The open items at exactly the requested node."},"focus":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},"description":"The few items to put in front of the person now, across the whole host: most pressing, least friction, growing as proposals are accepted. With `focus_size`, the ranking's top n or all of it."},"hidden":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Open fix items stored but not in focus yet."},"build":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},"description":"What the site is missing (remedy build), most pressing first: what the Site's page ideas are grounded in."},"improve":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},"description":"Pages that could do more (remedy improve), the page with the most reach first: the Site's page improvements."},"open":{"description":"The host's open issues, groups counted once, by the one test the map's badges count with.","type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"here":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix's list: focus plus hidden"},"elsewhere":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Open issues whose words are edited outside the repo (a CMS)"}},"required":["total","here","elsewhere"]},"accepted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Accepted or shipped proposals on this host."},"changes":{"anyOf":[{"type":"object","properties":{"since":{"type":"string"},"baselineAt":{"description":"The end of the host's first map: pages first seen before it were already there and never count as new.","type":["string","null"]},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live pages now."},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"addedPages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string"},"at":{"type":"string","description":"When the path was first seen (added) or went (removed)."}},"required":["path","url","at"]},"description":"Newest first, capped."},"removedPages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string"},"at":{"type":"string","description":"When the path was first seen (added) or went (removed)."}},"required":["path","url","at"]},"description":"Newest first, capped."},"folders":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["path","pages","added","removed"]},"description":"Top-level folders with any change (e.g. /blog +4), most added first."}},"required":["since","baselineAt","pages","added","removed","addedPages","removedPages","folders"]},{"type":"null"}],"description":"What changed host-wide since `since`; null without one."},"patterns":{"type":"array","items":{"type":"object","properties":{"folder":{"type":"string","description":"The folder the row sits under."},"source":{"type":"string","enum":["family","automation"]},"title":{"type":"string","description":"What to add: a page's title, an idea's headline."},"why":{"type":"string","description":"Why, in the numbers held: \"Comparison pages 3 of 11: a comparison for Hooli is the next in the pattern.\""},"path":{"description":"The path the family's URL pattern implies; null when it has no template.","type":["string","null"]},"follow":{"description":"A sibling page to follow.","type":["string","null"]},"family":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"list":{"type":"string"},"item":{"type":"string"},"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","name","list","item","covered","total"]},{"type":"null"}]},"owner":{"anyOf":[{"type":"object","properties":{"collectionId":{"type":"string"},"name":{"type":"string"},"automationId":{"type":"string"},"automationName":{"type":"string"},"kind":{"type":"string","enum":["topic","series","signal","best"]},"seriesId":{"type":["string","null"]}},"required":["collectionId","name","automationId","automationName","kind","seriesId"]},{"type":"null"}],"description":"The letterbase that owns the folder and the automation whose next this is; null for a page of code."},"state":{"type":"string","enum":["suggested","queued","writing","pr_open","live"],"description":"The letterbase's own state for it; always suggested for a family's next page."},"suggestionId":{"description":"The letterbase idea behind it (queue_suggestion), when one exists.","type":["string","null"]},"runId":{"description":"The scheduled or running post, when it is already one.","type":["string","null"]},"articleId":{"description":"The post, once it exists: open it in Letterpress.","type":["string","null"]}},"required":["folder","source","title","why","path","follow","family","owner","state","suggestionId","runId","articleId"]},"description":"What is next in every pattern the site keeps, host-wide, one per folder, most open first: the violet rows the map prefers (at most three show)."}},"required":["host","site","root","items","focus","hidden","build","improve","accepted","changes","patterns"],"$defs":{"__schema0":{"type":"object","properties":{"host":{"type":"string"},"path":{"type":"string"},"name":{"type":"string"},"hasChildren":{"type":"boolean"},"childCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"page":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"locales":{"type":"array","items":{"type":"string"}},"lastmod":{"type":["string","null"]},"goneAt":{"type":["string","null"]},"firstSeenAt":{"type":"string","description":"When the path was first seen (its earliest locale)."},"added":{"type":"boolean","description":"A new page: live and first seen since the request's `since` (false without one)."},"sources":{"type":"array","items":{"type":"string","enum":["sitemap","audit"]}},"canonicalPath":{"type":["string","null"]},"aliases":{"type":"array","items":{"type":"string"}},"audit":{"anyOf":[{"type":"object","properties":{"auditId":{"type":"string"},"pageId":{"type":"string"},"url":{"type":"string"},"statusCode":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fetchClass":{"type":"string"},"redirectUrl":{"type":["string","null"]},"title":{"type":["string","null"]},"metaDescription":{"type":["string","null"]},"canonicalUrl":{"type":["string","null"]},"headingOrder":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"h1Count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"wordCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"internalLinkCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"externalLinkCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"isIndexable":{"type":"boolean"},"inSitemap":{"type":"boolean"},"crawlDepth":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"contentHash":{"type":["string","null"]}},"required":["auditId","pageId","url","statusCode","fetchClass","redirectUrl","title","metaDescription","canonicalUrl","headingOrder","h1Count","wordCount","internalLinkCount","externalLinkCount","isIndexable","inSitemap","crawlDepth","contentHash"]},{"type":"null"}]},"bindings":{"type":"object","properties":{"article":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"collectionId":{"type":"string"},"collectionName":{"type":["string","null"]}},"required":["id","title","collectionId","collectionName"]},{"type":"null"}]},"landing":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"offeringId":{"type":["string","null"]}},"required":["id","url","offeringId"]},{"type":"null"}]},"offering":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name","url"]},{"type":"null"}]},"collection":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"mountPath":{"type":"string"},"hosted":{"type":"boolean"},"cmsProvider":{"type":["string","null"]},"publishControl":{"type":["string","null"]}},"required":["id","name","mountPath","hosted","cmsProvider","publishControl"]},{"type":"null"}]},"repo":{"anyOf":[{"type":"object","properties":{"owner":{"type":"string"},"name":{"type":"string"},"verdict":{"type":"string"},"decidedBy":{"type":"string"}},"required":["owner","name","verdict","decidedBy"]},{"type":"null"}]},"auditPage":{"anyOf":[{"type":"object","properties":{"auditId":{"type":"string"},"pageId":{"type":"string"}},"required":["auditId","pageId"]},{"type":"null"}]}},"required":["article","landing","offering","collection","repo","auditPage"]},"signals":{"anyOf":[{"type":"object","properties":{"path":{"type":"string"},"locale":{"type":"string"},"clicks28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clicksPrev28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"impressions28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"impressionsPrev28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"position":{"type":["number","null"]},"ctr":{"type":["number","null"]},"topQuery":{"type":["string","null"]},"topQueryPosition":{"type":["number","null"]},"competingQuery":{"description":"A search this page splits with a stronger page of the same site (search.competing_pages).","type":["string","null"]},"competingPath":{"description":"The page of the same site that leads competingQuery.","type":["string","null"]},"competingShare":{"description":"This page's share (0..1) of competingQuery's impressions.","type":["number","null"]},"citations90d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastCitedAt":{"type":["string","null"]},"citedNotNamed":{"type":"boolean"},"aiReads30d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastAiReadAt":{"type":["string","null"]},"aiVisits30d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"peopleViews30d":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"People page views in the last 30 days (null: the site is not measured; counts start at its traffic baseline)."},"peopleViewsPrev30d":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The 30 days before that; null unless that window starts on or after the traffic baseline."},"indexed":{"type":["boolean","null"]},"lastCrawlAt":{"type":["string","null"]},"inboundLinks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"outboundLinks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reach":{"type":"number","description":"0..1, log-scaled per host; AI-crawler reads weigh most, then people where measured."},"computedAt":{"type":"string"},"measured":{"description":"Which sources were connected when the row was computed. A zero from a source not measured is unknown, never zero.","anyOf":[{"type":"object","properties":{"search":{"type":"boolean"},"aiReads":{"type":"boolean"},"citations":{"type":"boolean"},"people":{"type":"boolean"}},"required":["search","aiReads","citations","people"]},{"type":"null"}]}},"required":["path","locale","clicks28d","clicksPrev28d","impressions28d","impressionsPrev28d","position","ctr","topQuery","topQueryPosition","competingQuery","competingPath","competingShare","citations90d","lastCitedAt","citedNotNamed","aiReads30d","lastAiReadAt","aiVisits30d","peopleViews30d","peopleViewsPrev30d","indexed","lastCrawlAt","inboundLinks","outboundLinks","reach","computedAt"]},{"type":"null"}]}},"required":["id","url","locales","lastmod","goneAt","firstSeenAt","added","sources","canonicalPath","aliases","audit","bindings","signals"]},{"type":"null"}]},"rollup":{"type":"object","properties":{"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"gone":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live pages first seen since the request's `since` (0 without one)."},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Pages gone since the request's `since` (0 without one)."},"open":{"type":"object","properties":{"ok":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"warn":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"risk":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"info":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["ok","warn","risk","info"]},"byRung":{"type":"object","properties":{"1":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"2":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"3":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"4":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"5":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["1","2","3","4","5"],"description":"The open Fix items by ladder rung (1 reach, 2 map, 3 trust, 4 use, 5 fast): what `open` counts, placed."},"bound":{"type":"object","properties":{"article":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"landing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"offering":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"collection":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"repo":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"auditPage":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["article","landing","offering","collection","repo","auditPage"]},"families":{"type":"array","items":{"type":"string"}},"coverage":{"anyOf":[{"type":"object","properties":{"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["covered","total"]},{"type":"null"}],"description":"The families homed under the node that cover a list the org keeps, summed (\"3 of 11\"); null when none."},"proposals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["pages","gone","added","removed","open","byRung","bound","families","coverage","proposals"]},"item":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},{"type":"null"}],"description":"The worst open item at or under the node; null when quiet."},"more":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"families":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"fold":{"type":"boolean"}},"required":["id","name","status","fold"]}},"folded":{"type":"array","items":{"type":"object","properties":{"familyId":{"type":"string"},"name":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["familyId","name","count"]}},"coverage":{"description":"Families living in this folder that cover one of the org's lists: \"3 of 11\" and the items with no page yet. Absent where none does.","type":"array","items":{"type":"object","properties":{"familyId":{"type":"string"},"name":{"type":"string"},"list":{"type":"string","enum":["competitors","offerings","integrations"]},"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"missing":{"type":"array","items":{"type":"string"}}},"required":["familyId","name","list","covered","total","missing"]}},"next":{"description":"What is next in the pattern this folder keeps: a family's next uncovered item, or the owning letterbase's automation's next idea with its state. The map's violet row at this folder. Absent where the folder keeps no pattern.","type":"object","properties":{"folder":{"type":"string","description":"The folder the row sits under."},"source":{"type":"string","enum":["family","automation"]},"title":{"type":"string","description":"What to add: a page's title, an idea's headline."},"why":{"type":"string","description":"Why, in the numbers held: \"Comparison pages 3 of 11: a comparison for Hooli is the next in the pattern.\""},"path":{"description":"The path the family's URL pattern implies; null when it has no template.","type":["string","null"]},"follow":{"description":"A sibling page to follow.","type":["string","null"]},"family":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"list":{"type":"string"},"item":{"type":"string"},"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","name","list","item","covered","total"]},{"type":"null"}]},"owner":{"anyOf":[{"type":"object","properties":{"collectionId":{"type":"string"},"name":{"type":"string"},"automationId":{"type":"string"},"automationName":{"type":"string"},"kind":{"type":"string","enum":["topic","series","signal","best"]},"seriesId":{"type":["string","null"]}},"required":["collectionId","name","automationId","automationName","kind","seriesId"]},{"type":"null"}],"description":"The letterbase that owns the folder and the automation whose next this is; null for a page of code."},"state":{"type":"string","enum":["suggested","queued","writing","pr_open","live"],"description":"The letterbase's own state for it; always suggested for a family's next page."},"suggestionId":{"description":"The letterbase idea behind it (queue_suggestion), when one exists.","type":["string","null"]},"runId":{"description":"The scheduled or running post, when it is already one.","type":["string","null"]},"articleId":{"description":"The post, once it exists: open it in Letterpress.","type":["string","null"]}},"required":["folder","source","title","why","path","follow","family","owner","state","suggestionId","runId","articleId"]},"children":{"type":"array","items":{"$ref":"#/$defs/__schema0"}}},"required":["host","path","name","hasChildren","childCount","page","rollup","item","more","families","folded"]}}}},{"name":"get_site_metrics","description":"How much each page of a mapped site is read over a time frame (`window`: 1d, 7d, 28d or 90d, ending today UTC; 1d is today so far, against yesterday), against the equal window before (`compare`, default on). Counts only: never who visited (people and companies identified: visitors_get_people). `source` says where the numbers come from: `visitors` (the Visitors script with identification on and the site proven: per page `accesses` with humans from the visitor pixel, anonymous visitors included, unique_visitors counted once a day, ai_agents from the server snippet with a by_agent breakdown, and other_bots; identified_humans counts explicit identify() calls only and reads 0 even when de-anonymization works), `lettervisit` (the same Visitors script's own counts, used when identification is off or unproven, the provider can't be read, or the host is a phantom: per page `accesses` split into ai_agents, search_crawlers, humans, of whom ai_referred_humans came from an AI assistant, and other_bots), `gsc` (no script: Search Console `clicks` per page instead) or `none` (neither; `install_href` is where to install the script). Search Console clicks come beside accesses whenever the org's property covers the host. With source `visitors`, `baseline` is the first day counted: nothing before it is counted, and previous_accesses stays null until the previous window starts on or after it. Each page carries `indicators`, readings only: `stale` (last changed 12+ months ago), `revamp` (traffic down 30%+ against the previous window, from at least 20) and `outperformer` (5x or more the median of 3+ sibling pages in the same folder). Pages with no data and no indicator are left out (they read zero). This is what Instances → Site's Metrics menu renders on the map. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"Only pages at or under this path, e.g. /blog. The whole site when omitted.","type":"string","maxLength":2048},"paths":{"description":"Only these page paths (tree paths such as /pricing). Combines with `path`.","maxItems":500,"type":"array","items":{"type":"string","minLength":1,"maxLength":2048}},"window":{"description":"The time frame, ending today (UTC): 1d is today so far, against yesterday. Default 28d.","type":"string","enum":["1d","7d","28d","90d"]},"compare":{"description":"Also read the previous equal window, for deltas and the revamp indicator (default true). False skips both.","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"window":{"type":"string","enum":["1d","7d","28d","90d"]},"compare":{"type":"boolean"},"range":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"previous_start":{"type":["string","null"]},"previous_end":{"type":["string","null"]}},"required":["start","end","previous_start","previous_end"]},"source":{"type":"string","enum":["visitors","lettervisit","gsc","none"],"description":"Both counts come from the one Visitors script (lettervisit.com). visitors: the identification provider's counts (identification on, site proven): people per page from the visitor pixel (anonymous included) and AI agents per page, by agent. lettervisit: the script's own counts, used when identification is off or unproven, the provider couldn't be read (see message), or the host is a phantom; says nothing about whether visitors are identified. gsc: no script, Search Console clicks instead; none: neither (prompt installing the script)."},"unit":{"anyOf":[{"type":"string","enum":["accesses","clicks"]},{"type":"null"}],"description":"What the indicators measured."},"script_installed":{"type":"boolean"},"baseline":{"description":"Source visitors: the first day counted (when the site moved to the new traffic source). Nothing before it is counted, and previous_accesses is null until the previous window starts on or after it.","type":["string","null"]},"gsc_connected":{"type":"boolean","description":"The org's Search Console property covers this host."},"install_href":{"type":"string","description":"Where a person installs the LetterVisit script."},"pages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"accesses":{"anyOf":[{"type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"ai_agents + search_crawlers + humans; other bots are counted apart."},"ai_agents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Requests from AI models' crawlers and live fetches (ai_agent)."},"search_crawlers":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"other_bots":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Other automated requests; not in `total`."},"humans":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"People. visitors: page loads from the visitor pixel, anonymous visitors included. lettervisit: the beacon's visits, or the server's browser requests when higher."},"ai_referred_humans":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of `humans`, the ones who arrived from an AI assistant (lettervisit only)."},"by_agent":{"description":"visitors: AI requests by agent (User-Agent claims, e.g. openai-crawler), most first.","type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"operator":{"type":"string"},"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["agent","operator","requests"]}},"identified_humans":{"description":"visitors: of `humans`, page loads named by an explicit identify() call on the site only. NOT de-anonymization: 0 here does not mean nobody was identified. Who visited is visitors_get_people.","anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"unique_visitors":{"description":"visitors: people on the page, each counted once a day (anonymous included); a person seen on two days counts twice. Exact for window 1d. Not summed across pages (one person reads many).","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["total","ai_agents","search_crawlers","other_bots","humans","ai_referred_humans"]},{"type":"null"}]},"previous_accesses":{"anyOf":[{"type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"ai_agents + search_crawlers + humans; other bots are counted apart."},"ai_agents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Requests from AI models' crawlers and live fetches (ai_agent)."},"search_crawlers":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"other_bots":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Other automated requests; not in `total`."},"humans":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"People. visitors: page loads from the visitor pixel, anonymous visitors included. lettervisit: the beacon's visits, or the server's browser requests when higher."},"ai_referred_humans":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of `humans`, the ones who arrived from an AI assistant (lettervisit only)."},"by_agent":{"description":"visitors: AI requests by agent (User-Agent claims, e.g. openai-crawler), most first.","type":"array","items":{"type":"object","properties":{"agent":{"type":"string"},"operator":{"type":"string"},"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["agent","operator","requests"]}},"identified_humans":{"description":"visitors: of `humans`, page loads named by an explicit identify() call on the site only. NOT de-anonymization: 0 here does not mean nobody was identified. Who visited is visitors_get_people.","anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"unique_visitors":{"description":"visitors: people on the page, each counted once a day (anonymous included); a person seen on two days counts twice. Exact for window 1d. Not summed across pages (one person reads many).","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["total","ai_agents","search_crawlers","other_bots","humans","ai_referred_humans"]},{"type":"null"}]},"accesses_delta_pct":{"type":["number","null"]},"clicks":{"type":["number","null"]},"previous_clicks":{"type":["number","null"]},"clicks_delta_pct":{"type":["number","null"]},"lastmod":{"type":["string","null"]},"indicators":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["stale","revamp","outperformer"]},"tone":{"type":"string","enum":["warn","falling","rising"]},"label":{"type":"string","description":"The badge's words, e.g. \"Stale\", \"Revamp\", \"5x similar pages\"."},"detail":{"type":"string"},"multiple":{"description":"Outperformer: times the median of its sibling pages.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["key","tone","label","detail"]}}},"required":["path","accesses","previous_accesses","accesses_delta_pct","clicks","previous_clicks","clicks_delta_pct","lastmod","indicators"]},"description":"Pages with data or an indicator; a page not listed read zero."},"message":{"type":"string"}},"required":["host","window","compare","range","source","unit","script_installed","baseline","gsc_connected","install_href","pages","message"]}},{"name":"list_competitor_sites","description":"Every tracked competitor's site as The Site maps it (a watched host), with what changed since `since` (default 30 days): live pages, pages added (first seen since, never the first map's pages) and removed, the newest of each, and the top-level folders that moved (e.g. /blog +4, /products +2). A competitor with `site: null` isn't mapped yet; watch_competitor_sites maps it. `highlighted` marks the org's highlighted competitors (set_competitor_highlight), and `topChanges` is the top changes across them (every competitor when none is highlighted): one short line per competitor and folder, newest first, each with a Build prompt. Open one in get_site_tree with the same `since` for the folder-by-folder view. This is what Memory → Competitors → Sitemaps renders. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"since":{"description":"ISO time; count pages added and removed since then (default 30 days ago).","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"limit":{"description":"Added and removed pages to list per competitor, newest first (default 50).","type":"integer","minimum":1,"maximum":500}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"since":{"type":"string"},"competitors":{"type":"array","items":{"type":"object","properties":{"competitorDomainId":{"type":"string"},"name":{"type":"string"},"domain":{"type":"string"},"host":{"type":["string","null"]},"site":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"siteGroupId":{"description":"Seam: several hosts under one site. Always null for now.","type":["string","null"]},"sitemapUrl":{"type":["string","null"]},"mapStatus":{"type":"string","enum":["unmapped","mapping","mapped","failed"]},"mapTruncated":{"type":"boolean","description":"The last map hit its cap; the sitemap lists more."},"mapCap":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"mapError":{"type":["string","null"]},"readNote":{"description":"A mapped site Letter could not read (it refused the crawler, or never answered): why, and what to allow. Null when it read.","type":["string","null"]},"mapStartedAt":{"type":["string","null"]},"mappedAt":{"type":["string","null"]},"mappedAuditId":{"description":"The completed audit folded in at map time.","type":["string","null"]},"pagesTotal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","description":"Who mapped it: user | api_key | agent."},"createdByUserId":{"type":["string","null"]},"createdAt":{"type":"string"},"relation":{"type":"string","enum":["owned","watched"]},"refreshEnabled":{"type":"boolean"},"lastRefreshAt":{"type":["string","null"]},"lastRefresh":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"mapped":{"type":"boolean"},"audited":{"type":["string","null"]},"signals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"suggested":{"description":"Pages the suggestion pass added; absent when it didn't run.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"promised":{"description":"Pages the promise pass read for what they promise (Build's first source); absent when none.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"array","items":{"type":"string"}}},"required":["at","mapped","audited","signals","observed","skipped"]},{"type":"null"}],"description":"What the last refresh did and skipped."},"lastJevAt":{"type":["string","null"]},"sitemapReport":{"description":"Sitemap entries the last map read and did not keep: URLs on another host.","anyOf":[{"type":"object","properties":{"offHost":{"type":"array","items":{"type":"string"}},"offHostCount":{"type":"number"}},"required":["offHost","offHostCount"]},{"type":"null"}]}},"required":["id","host","siteGroupId","sitemapUrl","mapStatus","mapTruncated","mapCap","mapError","mapStartedAt","mappedAt","mappedAuditId","pagesTotal","source","createdByUserId","createdAt","relation","refreshEnabled","lastRefreshAt","lastRefresh","lastJevAt"]},{"type":"null"}],"description":"The watched host; null until mapped (watch_competitor_sites)."},"changes":{"anyOf":[{"type":"object","properties":{"since":{"type":"string"},"baselineAt":{"description":"The end of the host's first map: pages first seen before it were already there and never count as new.","type":["string","null"]},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live pages now."},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"addedPages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string"},"at":{"type":"string","description":"When the path was first seen (added) or went (removed)."}},"required":["path","url","at"]},"description":"Newest first, capped."},"removedPages":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string"},"at":{"type":"string","description":"When the path was first seen (added) or went (removed)."}},"required":["path","url","at"]},"description":"Newest first, capped."},"folders":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["path","pages","added","removed"]},"description":"Top-level folders with any change (e.g. /blog +4), most added first."}},"required":["since","baselineAt","pages","added","removed","addedPages","removedPages","folders"]},{"type":"null"}],"description":"What changed since `since`; null until mapped."},"highlighted":{"type":"boolean","description":"The org highlighted it (set_competitor_highlight); topChanges reads the highlighted ones."}},"required":["competitorDomainId","name","domain","host","site","changes","highlighted"]}},"topChanges":{"type":"array","items":{"type":"object","properties":{"competitorDomainId":{"type":"string"},"name":{"type":"string"},"host":{"type":"string"},"kind":{"type":"string","enum":["added","removed"]},"folder":{"type":"string","description":"The folder the pages sit in, e.g. /blog/compare; / for pages at the root."},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"paths":{"type":"array","items":{"type":"string"},"description":"Some of the pages, newest first."},"at":{"type":"string","description":"When the newest of them was added (or went)."},"label":{"type":"string","description":"e.g. \"+3 pages in /blog\"."},"summary":{"type":"string","description":"e.g. \"arcade.software · +3 pages in /blog\"."},"prompt":{"type":"string","description":"What to ask Build for in answer: the prompt Instances → Build plans (propose_account_plan `prompt`, then start_growth_build)."}},"required":["competitorDomainId","name","host","kind","folder","count","paths","at","label","summary","prompt"]},"description":"The top changes across the highlighted competitors (every competitor when none is): pages added or removed in the window, one line per competitor and folder, grouped by competitor, newest first. Each carries a ready Build prompt."}},"required":["since","competitors","topChanges"]}},{"name":"watch_competitor_sites","description":"Map tracked competitors' sitemaps into The Site as watched hosts (facts only: no audit, signals or Jev), so list_competitor_sites can show what they add. With `domain`, maps (or re-maps) that one competitor now; without it, maps the tracked competitors not mapped yet, `limit` per call (default and most 5) one after another, least recently tried first, so one that just failed goes to the back (`remaining` says how many are left, failed ones included: call again, one call at a time, until a call returns a host you already tried). A competitor whose map is still running is left alone, so calling twice never starts the same crawl twice. Adding a competitor starts this on its own, and every mapped competitor's sitemap is read again daily (a competitor whose map failed is retried daily too). Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"domain":{"description":"One tracked competitor's domain to map (again) now. Omit to map every tracked competitor that isn't mapped yet, a few per call.","type":"string","minLength":1,"maxLength":253},"limit":{"description":"Without `domain`: how many unmapped competitors this call maps, one after another (default and most 5), least recently tried first. Call again while `remaining` > 0 and the last call returned a host you hadn't tried yet.","type":"integer","minimum":1,"maximum":5}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"ok":{"type":"boolean"},"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"truncated":{"type":"boolean"},"message":{"type":["string","null"]}},"required":["host","ok","pages","truncated","message"]}},"remaining":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Tracked competitors still unmapped, ones that just failed included; call again to map the next few."},"message":{"type":"string"}},"required":["results","remaining","message"]}},{"name":"set_competitor_highlight","description":"Highlight a tracked competitor (or stop highlighting it), by its domain. The highlighted set is the org's, shared by Memory → Competitors → Sitemaps, the CLI and every agent: list_competitor_sites marks it (`highlighted`) and reads its `topChanges` over the highlighted competitors only (all of them when none is). Changes nothing about the competitor itself. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"A tracked competitor's domain (or the host its site is mapped under), e.g. arcade.software."},"highlighted":{"type":"boolean","description":"True to highlight it, false to stop."}},"required":["domain","highlighted"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"competitor":{"type":"object","properties":{"competitorDomainId":{"type":"string"},"name":{"type":"string"},"domain":{"type":"string"},"host":{"type":["string","null"]},"highlighted":{"type":"boolean"}},"required":["competitorDomainId","name","domain","host","highlighted"]},"highlighted":{"type":"array","items":{"type":"string"},"description":"The domains of every highlighted competitor, after this call."},"message":{"type":"string"}},"required":["competitor","highlighted","message"]}},{"name":"get_site_page","description":"One page in full: its node (facts, audit join, bindings), every locale row, every raw URL it was seen at, its open items, all its observations, proposals and families. The inspect (i) view in the console shows this payload. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"type":"string","minLength":1,"maxLength":2048,"description":"The page's tree path, e.g. /pricing."}},"required":["host","path"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"page":{"type":"object","properties":{"host":{"type":"string"},"path":{"type":"string"},"node":{"$ref":"#/$defs/__schema0"},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"locale":{"type":"string"},"url":{"type":"string"},"canonicalPath":{"type":["string","null"]},"canonicalUrl":{"type":["string","null"]},"lastmod":{"type":["string","null"]},"sources":{"type":"array","items":{"type":"string","enum":["sitemap","audit"]}},"firstSeenAt":{"type":"string"},"lastSeenAt":{"type":"string"},"goneAt":{"type":["string","null"]}},"required":["id","host","path","locale","url","canonicalPath","canonicalUrl","lastmod","sources","firstSeenAt","lastSeenAt","goneAt"]},"description":"One row per locale."},"rawUrls":{"type":"array","items":{"type":"object","properties":{"rawUrl":{"type":"string"},"source":{"type":"string","enum":["sitemap","audit"]},"sitemapUrl":{"type":["string","null"]},"lastmod":{"type":["string","null"]},"firstSeenAt":{"type":"string"},"lastSeenAt":{"type":"string"},"goneAt":{"type":["string","null"]}},"required":["rawUrl","source","sitemapUrl","lastmod","firstSeenAt","lastSeenAt","goneAt"]}},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]}},"observations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"A uuid for a stored observation; the read-time id (key@path) for a derived one."},"dedupeKey":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"scope":{"type":"string","enum":["site","folder","page"]},"pageId":{"type":["string","null"]},"key":{"type":"string","description":"An open key from the observation registry; no consumer branches on it."},"source":{"type":"string","description":"audit | map | jev | agent …"},"sourceVersion":{"type":["string","null"]},"model":{"type":["string","null"]},"confidence":{"type":["number","null"]},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"summary":{"type":"string","description":"One plain-English line, written by whatever detected it."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"status":{"type":"string","enum":["open","dismissed","resolved"]},"statusReason":{"type":["string","null"]},"reasonKind":{"anyOf":[{"type":"string","enum":["not_relevant","already_handled","wrong","later"]},{"type":"null"}]},"snoozedUntil":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"stored":{"type":"boolean"},"draft":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":"string"}},"required":["tool","input","label","rationale"]},{"type":"null"}]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"reach":{"type":"number"},"remedy":{"description":"What acting on it means: fix (a code change: Fix), build (something the site lacks: the Site's page ideas read it), improve (a page that could do more: the Site's page improvements), status (housekeeping), reading (never an issue).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: the fix moves pages or adds redirects; ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"contentHome":{"description":"Where the page's words live when not in the repo (a CMS, Letter), set when the fix rewrites them: edit them there; no repo change takes it.","type":["string","null"]}},"required":["id","dedupeKey","host","path","scope","pageId","key","source","sourceVersion","model","confidence","tone","summary","evidence","status","statusReason","reasonKind","snoozedUntil","decidedBy","decidedAt","createdAt","stored","draft","action","reach"]}},"proposals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"pageId":{"type":["string","null"]},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":["string","null"]},"observationIds":{"type":"array","items":{"type":"string"}},"channel":{"type":"string","enum":["repo_pr","cms","publish","handoff"]},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"shippedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","host","path","pageId","tool","input","label","rationale","observationIds","channel","status","statusReason","source","proposedByUserId","decidedBy","decidedAt","shippedAt","shipResult","createdAt"]}},"families":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"name":{"type":"string"},"detectorKey":{"type":["string","null"]},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"confidence":{"type":["number","null"]},"rules":{"type":"object","properties":{"path_prefix":{"type":"string"},"url_template":{"type":"string"}}},"fold":{"type":"boolean","description":"Folded away by default by the tree (archive noise)."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"universe":{"anyOf":[{"type":"object","properties":{"list":{"type":"string","enum":["competitors","offerings","integrations"],"description":"Which list: competitors (org_competitor_domains), offerings, or integrations (the ones the connected product repo ships)."},"items":{"type":"array","items":{"type":"string"},"description":"Every item of the list, by name."}},"required":["list","items"]},{"type":"null"}],"description":"The org list it should cover (its competitors or its offerings), set by the binding detector; null when its pages map onto no list."},"coverage":{"anyOf":[{"type":"object","properties":{"covered":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"path":{"type":"string"}},"required":["item","path"]},"description":"Each covered item and its page."},"missing":{"type":"array","items":{"type":"string"},"description":"Items with no page in the family yet."},"ratio":{"type":"number","description":"covered / items, 0..1."}},"required":["covered","missing","ratio"]},{"type":"null"}],"description":"\"3 of 11\": the page that covers each covered item and the items with no page yet. Read against today's members, so an excluded page no longer covers its item. Null without a universe."},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"members":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"source":{"type":"string"},"confidence":{"type":["number","null"]},"included":{"type":"boolean"},"reason":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]}},"required":["path","source","confidence","included","reason","decidedBy","decidedAt"]}},"createdAt":{"type":"string"}},"required":["id","host","name","detectorKey","status","statusReason","source","confidence","rules","fold","evidence","universe","coverage","decidedBy","decidedAt","members","createdAt"]}}},"required":["host","path","node","pages","rawUrls","items","observations","proposals","families"]}},"required":["page"],"$defs":{"__schema0":{"type":"object","properties":{"host":{"type":"string"},"path":{"type":"string"},"name":{"type":"string"},"hasChildren":{"type":"boolean"},"childCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"page":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"locales":{"type":"array","items":{"type":"string"}},"lastmod":{"type":["string","null"]},"goneAt":{"type":["string","null"]},"firstSeenAt":{"type":"string","description":"When the path was first seen (its earliest locale)."},"added":{"type":"boolean","description":"A new page: live and first seen since the request's `since` (false without one)."},"sources":{"type":"array","items":{"type":"string","enum":["sitemap","audit"]}},"canonicalPath":{"type":["string","null"]},"aliases":{"type":"array","items":{"type":"string"}},"audit":{"anyOf":[{"type":"object","properties":{"auditId":{"type":"string"},"pageId":{"type":"string"},"url":{"type":"string"},"statusCode":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"fetchClass":{"type":"string"},"redirectUrl":{"type":["string","null"]},"title":{"type":["string","null"]},"metaDescription":{"type":["string","null"]},"canonicalUrl":{"type":["string","null"]},"headingOrder":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"h1Count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"wordCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"internalLinkCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"externalLinkCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"isIndexable":{"type":"boolean"},"inSitemap":{"type":"boolean"},"crawlDepth":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"contentHash":{"type":["string","null"]}},"required":["auditId","pageId","url","statusCode","fetchClass","redirectUrl","title","metaDescription","canonicalUrl","headingOrder","h1Count","wordCount","internalLinkCount","externalLinkCount","isIndexable","inSitemap","crawlDepth","contentHash"]},{"type":"null"}]},"bindings":{"type":"object","properties":{"article":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"collectionId":{"type":"string"},"collectionName":{"type":["string","null"]}},"required":["id","title","collectionId","collectionName"]},{"type":"null"}]},"landing":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"offeringId":{"type":["string","null"]}},"required":["id","url","offeringId"]},{"type":"null"}]},"offering":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"required":["id","name","url"]},{"type":"null"}]},"collection":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"mountPath":{"type":"string"},"hosted":{"type":"boolean"},"cmsProvider":{"type":["string","null"]},"publishControl":{"type":["string","null"]}},"required":["id","name","mountPath","hosted","cmsProvider","publishControl"]},{"type":"null"}]},"repo":{"anyOf":[{"type":"object","properties":{"owner":{"type":"string"},"name":{"type":"string"},"verdict":{"type":"string"},"decidedBy":{"type":"string"}},"required":["owner","name","verdict","decidedBy"]},{"type":"null"}]},"auditPage":{"anyOf":[{"type":"object","properties":{"auditId":{"type":"string"},"pageId":{"type":"string"}},"required":["auditId","pageId"]},{"type":"null"}]}},"required":["article","landing","offering","collection","repo","auditPage"]},"signals":{"anyOf":[{"type":"object","properties":{"path":{"type":"string"},"locale":{"type":"string"},"clicks28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clicksPrev28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"impressions28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"impressionsPrev28d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"position":{"type":["number","null"]},"ctr":{"type":["number","null"]},"topQuery":{"type":["string","null"]},"topQueryPosition":{"type":["number","null"]},"competingQuery":{"description":"A search this page splits with a stronger page of the same site (search.competing_pages).","type":["string","null"]},"competingPath":{"description":"The page of the same site that leads competingQuery.","type":["string","null"]},"competingShare":{"description":"This page's share (0..1) of competingQuery's impressions.","type":["number","null"]},"citations90d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastCitedAt":{"type":["string","null"]},"citedNotNamed":{"type":"boolean"},"aiReads30d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastAiReadAt":{"type":["string","null"]},"aiVisits30d":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"peopleViews30d":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"People page views in the last 30 days (null: the site is not measured; counts start at its traffic baseline)."},"peopleViewsPrev30d":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The 30 days before that; null unless that window starts on or after the traffic baseline."},"indexed":{"type":["boolean","null"]},"lastCrawlAt":{"type":["string","null"]},"inboundLinks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"outboundLinks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reach":{"type":"number","description":"0..1, log-scaled per host; AI-crawler reads weigh most, then people where measured."},"computedAt":{"type":"string"},"measured":{"description":"Which sources were connected when the row was computed. A zero from a source not measured is unknown, never zero.","anyOf":[{"type":"object","properties":{"search":{"type":"boolean"},"aiReads":{"type":"boolean"},"citations":{"type":"boolean"},"people":{"type":"boolean"}},"required":["search","aiReads","citations","people"]},{"type":"null"}]}},"required":["path","locale","clicks28d","clicksPrev28d","impressions28d","impressionsPrev28d","position","ctr","topQuery","topQueryPosition","competingQuery","competingPath","competingShare","citations90d","lastCitedAt","citedNotNamed","aiReads30d","lastAiReadAt","aiVisits30d","peopleViews30d","peopleViewsPrev30d","indexed","lastCrawlAt","inboundLinks","outboundLinks","reach","computedAt"]},{"type":"null"}]}},"required":["id","url","locales","lastmod","goneAt","firstSeenAt","added","sources","canonicalPath","aliases","audit","bindings","signals"]},{"type":"null"}]},"rollup":{"type":"object","properties":{"pages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"gone":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"added":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live pages first seen since the request's `since` (0 without one)."},"removed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Pages gone since the request's `since` (0 without one)."},"open":{"type":"object","properties":{"ok":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"warn":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"risk":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"info":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["ok","warn","risk","info"]},"byRung":{"type":"object","properties":{"1":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"2":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"3":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"4":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"5":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["1","2","3","4","5"],"description":"The open Fix items by ladder rung (1 reach, 2 map, 3 trust, 4 use, 5 fast): what `open` counts, placed."},"bound":{"type":"object","properties":{"article":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"landing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"offering":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"collection":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"repo":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"auditPage":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["article","landing","offering","collection","repo","auditPage"]},"families":{"type":"array","items":{"type":"string"}},"coverage":{"anyOf":[{"type":"object","properties":{"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["covered","total"]},{"type":"null"}],"description":"The families homed under the node that cover a list the org keeps, summed (\"3 of 11\"); null when none."},"proposals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["pages","gone","added","removed","open","byRung","bound","families","coverage","proposals"]},"item":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"]},{"type":"null"}],"description":"The worst open item at or under the node; null when quiet."},"more":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"families":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"fold":{"type":"boolean"}},"required":["id","name","status","fold"]}},"folded":{"type":"array","items":{"type":"object","properties":{"familyId":{"type":"string"},"name":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["familyId","name","count"]}},"coverage":{"description":"Families living in this folder that cover one of the org's lists: \"3 of 11\" and the items with no page yet. Absent where none does.","type":"array","items":{"type":"object","properties":{"familyId":{"type":"string"},"name":{"type":"string"},"list":{"type":"string","enum":["competitors","offerings","integrations"]},"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"missing":{"type":"array","items":{"type":"string"}}},"required":["familyId","name","list","covered","total","missing"]}},"next":{"description":"What is next in the pattern this folder keeps: a family's next uncovered item, or the owning letterbase's automation's next idea with its state. The map's violet row at this folder. Absent where the folder keeps no pattern.","type":"object","properties":{"folder":{"type":"string","description":"The folder the row sits under."},"source":{"type":"string","enum":["family","automation"]},"title":{"type":"string","description":"What to add: a page's title, an idea's headline."},"why":{"type":"string","description":"Why, in the numbers held: \"Comparison pages 3 of 11: a comparison for Hooli is the next in the pattern.\""},"path":{"description":"The path the family's URL pattern implies; null when it has no template.","type":["string","null"]},"follow":{"description":"A sibling page to follow.","type":["string","null"]},"family":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"list":{"type":"string"},"item":{"type":"string"},"covered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","name","list","item","covered","total"]},{"type":"null"}]},"owner":{"anyOf":[{"type":"object","properties":{"collectionId":{"type":"string"},"name":{"type":"string"},"automationId":{"type":"string"},"automationName":{"type":"string"},"kind":{"type":"string","enum":["topic","series","signal","best"]},"seriesId":{"type":["string","null"]}},"required":["collectionId","name","automationId","automationName","kind","seriesId"]},{"type":"null"}],"description":"The letterbase that owns the folder and the automation whose next this is; null for a page of code."},"state":{"type":"string","enum":["suggested","queued","writing","pr_open","live"],"description":"The letterbase's own state for it; always suggested for a family's next page."},"suggestionId":{"description":"The letterbase idea behind it (queue_suggestion), when one exists.","type":["string","null"]},"runId":{"description":"The scheduled or running post, when it is already one.","type":["string","null"]},"articleId":{"description":"The post, once it exists: open it in Letterpress.","type":["string","null"]}},"required":["folder","source","title","why","path","follow","family","owner","state","suggestionId","runId","articleId"]},"children":{"type":"array","items":{"$ref":"#/$defs/__schema0"}}},"required":["host","path","name","hasChildren","childCount","page","rollup","item","more","families","folded"]}}}},{"name":"list_site_observations","description":"Everything noticed on a site at or under a path: audit issues (read from the latest completed audit, never copied), map facts (gone, unlisted, canonical aliases), Jev readings and agent findings. Each carries its source, confidence, evidence, tone and a one-line summary; a dismissed one carries who dismissed it and why. A whole site is large: read `keys_only: true` first (each kind noticed, e.g. facts.schema_missing, with its count and pages), then pass one `key`. With no key and no limit, more than 50 come back as kinds, the way keys_only would. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"A tree path such as /blog or /pricing; the root (/) when omitted.","type":"string","maxLength":2048},"status":{"type":"string","enum":["open","dismissed","resolved"]},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"source":{"description":"audit | map | jev | agent …","type":"string","maxLength":100},"key":{"description":"One observation key, e.g. search.low_ctr.","type":"string","maxLength":100},"since":{"description":"ISO date; only observations created at or after it.","type":"string","maxLength":40},"min_reach":{"description":"Only pages with at least this reach (0..1).","type":"number","minimum":0,"maximum":1},"limit":{"description":"Default 500, worst tone first.","type":"integer","minimum":1,"maximum":2000},"keys_only":{"description":"Return only `keys`: each kind noticed (key, summary, tone, count, paths), not the observations. Read this first, then pass one `key`.","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"observations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"A uuid for a stored observation; the read-time id (key@path) for a derived one."},"dedupeKey":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"scope":{"type":"string","enum":["site","folder","page"]},"pageId":{"type":["string","null"]},"key":{"type":"string","description":"An open key from the observation registry; no consumer branches on it."},"source":{"type":"string","description":"audit | map | jev | agent …"},"sourceVersion":{"type":["string","null"]},"model":{"type":["string","null"]},"confidence":{"type":["number","null"]},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"summary":{"type":"string","description":"One plain-English line, written by whatever detected it."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"status":{"type":"string","enum":["open","dismissed","resolved"]},"statusReason":{"type":["string","null"]},"reasonKind":{"anyOf":[{"type":"string","enum":["not_relevant","already_handled","wrong","later"]},{"type":"null"}]},"snoozedUntil":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"stored":{"type":"boolean"},"draft":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":"string"}},"required":["tool","input","label","rationale"]},{"type":"null"}]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"reach":{"type":"number"},"remedy":{"description":"What acting on it means: fix (a code change: Fix), build (something the site lacks: the Site's page ideas read it), improve (a page that could do more: the Site's page improvements), status (housekeeping), reading (never an issue).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: the fix moves pages or adds redirects; ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"contentHome":{"description":"Where the page's words live when not in the repo (a CMS, Letter), set when the fix rewrites them: edit them there; no repo change takes it.","type":["string","null"]}},"required":["id","dedupeKey","host","path","scope","pageId","key","source","sourceVersion","model","confidence","tone","summary","evidence","status","statusReason","reasonKind","snoozedUntil","decidedBy","decidedAt","createdAt","stored","draft","action","reach"]}},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many observations match every filter given (status included), before `limit`"},"keys":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"summary":{"type":"string"},"tone":{"type":"string"},"source":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"paths":{"type":"array","items":{"type":"string"}}},"required":["key","summary","tone","source","count","paths"]}},"message":{"description":"Set when a list too big to return whole came back as kinds: how to ask for the observations","type":"string"}},"required":["observations","total"]}},{"name":"observe_site","description":"Run the Jev pass over up to max_pages pages at or under a path: one batched call of typed questions per page (is the meta description accurate, is the title descriptive, how likely is the page outdated), page text fetched only because the questions need it. Over-threshold answers become observations with source jev; tracked families get a same-kind-of-page check for nearby candidates. Returns skipped: typesafe_not_configured when TYPESAFE_API_KEY is unset; everything else keeps working. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"A tree path such as /blog or /pricing; the root (/) when omitted.","type":"string","maxLength":2048},"max_pages":{"description":"Pages to read this run (default 20, max 50). Pages already read at this question version are skipped.","type":"integer","minimum":1,"maximum":50},"ignore_budget":{"description":"Ignore the per-host daily Jev budget for this explicit run.","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"summary":{"type":"object","properties":{"skipped":{"type":"string","enum":["typesafe_not_configured","budget_exhausted","watched_host"]},"pagesRead":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pagesSkipped":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pagesWaiting":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"observations":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"readings":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"resolved":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"familiesChecked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"membersAdded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"model":{"type":["string","null"]},"budgetLeftToday":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["pagesRead","pagesSkipped","pagesWaiting","observations","readings","resolved","familiesChecked","membersAdded","model","budgetLeftToday"]},"message":{"type":"string"}},"required":["summary","message"]}},{"name":"dismiss_site_observation","description":"Dismiss an observation, with a reason if there is one (or reopen one). This is the only opinion stored on an observation: who decided and, if given, why. Dismissals of a key on a host bias what is shown there later. A read-time observation (an audit issue) gets a row under its stable key so the dismissal survives re-audits; the audit row stays the source of truth. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"observation_id":{"type":"string","minLength":1,"maxLength":600,"description":"The observation's id: a uuid for a stored one, or the read-time id (e.g. audit.missing-title@/pricing)."},"decision":{"description":"Default dismiss.","type":"string","enum":["dismiss","reopen"]},"reason":{"description":"Optional. Recorded as the only opinion on the observation.","type":"string","minLength":1,"maxLength":1000},"reason_kind":{"description":"The quick pick behind the reason. not_relevant, or a dismissal with no kind, counts toward demoting the key on this host (after three); later snoozes it for 14 days.","type":"string","enum":["not_relevant","already_handled","wrong","later"]}},"required":["host","observation_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"observation":{"type":"object","properties":{"id":{"type":"string","description":"A uuid for a stored observation; the read-time id (key@path) for a derived one."},"dedupeKey":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"scope":{"type":"string","enum":["site","folder","page"]},"pageId":{"type":["string","null"]},"key":{"type":"string","description":"An open key from the observation registry; no consumer branches on it."},"source":{"type":"string","description":"audit | map | jev | agent …"},"sourceVersion":{"type":["string","null"]},"model":{"type":["string","null"]},"confidence":{"type":["number","null"]},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"summary":{"type":"string","description":"One plain-English line, written by whatever detected it."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"status":{"type":"string","enum":["open","dismissed","resolved"]},"statusReason":{"type":["string","null"]},"reasonKind":{"anyOf":[{"type":"string","enum":["not_relevant","already_handled","wrong","later"]},{"type":"null"}]},"snoozedUntil":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"createdAt":{"type":"string"},"stored":{"type":"boolean"},"draft":{"anyOf":[{"type":"object","properties":{"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":"string"}},"required":["tool","input","label","rationale"]},{"type":"null"}]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"reach":{"type":"number"},"remedy":{"description":"What acting on it means: fix (a code change: Fix), build (something the site lacks: the Site's page ideas read it), improve (a page that could do more: the Site's page improvements), status (housekeeping), reading (never an issue).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: the fix moves pages or adds redirects; ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"contentHome":{"description":"Where the page's words live when not in the repo (a CMS, Letter), set when the fix rewrites them: edit them there; no repo change takes it.","type":["string","null"]}},"required":["id","dedupeKey","host","path","scope","pageId","key","source","sourceVersion","model","confidence","tone","summary","evidence","status","statusReason","reasonKind","snoozedUntil","decidedBy","decidedAt","createdAt","stored","draft","action","reach"]}},"required":["observation"]}},{"name":"list_site_families","description":"Recognised groups of pages on a host (a comparison series, the integration pages, archive noise), suggested by detectors or Jev and tracked or dismissed by a person, with every member's source, confidence and inclusion. Where a page lives is the tree; what it belongs to is a family. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"path":{"description":"Only families with a member at or under this path.","type":"string","maxLength":2048}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"families":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"name":{"type":"string"},"detectorKey":{"type":["string","null"]},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"confidence":{"type":["number","null"]},"rules":{"type":"object","properties":{"path_prefix":{"type":"string"},"url_template":{"type":"string"}}},"fold":{"type":"boolean","description":"Folded away by default by the tree (archive noise)."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"universe":{"anyOf":[{"type":"object","properties":{"list":{"type":"string","enum":["competitors","offerings","integrations"],"description":"Which list: competitors (org_competitor_domains), offerings, or integrations (the ones the connected product repo ships)."},"items":{"type":"array","items":{"type":"string"},"description":"Every item of the list, by name."}},"required":["list","items"]},{"type":"null"}],"description":"The org list it should cover (its competitors or its offerings), set by the binding detector; null when its pages map onto no list."},"coverage":{"anyOf":[{"type":"object","properties":{"covered":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"path":{"type":"string"}},"required":["item","path"]},"description":"Each covered item and its page."},"missing":{"type":"array","items":{"type":"string"},"description":"Items with no page in the family yet."},"ratio":{"type":"number","description":"covered / items, 0..1."}},"required":["covered","missing","ratio"]},{"type":"null"}],"description":"\"3 of 11\": the page that covers each covered item and the items with no page yet. Read against today's members, so an excluded page no longer covers its item. Null without a universe."},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"members":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"source":{"type":"string"},"confidence":{"type":["number","null"]},"included":{"type":"boolean"},"reason":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]}},"required":["path","source","confidence","included","reason","decidedBy","decidedAt"]}},"createdAt":{"type":"string"}},"required":["id","host","name","detectorKey","status","statusReason","source","confidence","rules","fold","evidence","universe","coverage","decidedBy","decidedAt","members","createdAt"]}}},"required":["families"]}},{"name":"decide_site_family","description":"Track, dismiss (with a reason) or rename a family, and correct its members. An exclusion records its reason and beats any rule or detector; no later pass silently re-adds it. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"family_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"decision":{"type":"string","enum":["tracked","dismissed","suggested"]},"reason":{"description":"Required to dismiss.","type":"string","minLength":1,"maxLength":1000},"name":{"description":"Rename the family.","type":"string","minLength":1,"maxLength":200},"members":{"maxItems":500,"type":"array","items":{"type":"object","properties":{"path":{"type":"string","minLength":1,"maxLength":2048},"included":{"type":"boolean"},"reason":{"description":"Required when excluding; an exclusion beats any detector.","type":"string","minLength":1,"maxLength":1000}},"required":["path","included"]}}},"required":["family_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"family":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"name":{"type":"string"},"detectorKey":{"type":["string","null"]},"status":{"type":"string","enum":["suggested","tracked","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"confidence":{"type":["number","null"]},"rules":{"type":"object","properties":{"path_prefix":{"type":"string"},"url_template":{"type":"string"}}},"fold":{"type":"boolean","description":"Folded away by default by the tree (archive noise)."},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"universe":{"anyOf":[{"type":"object","properties":{"list":{"type":"string","enum":["competitors","offerings","integrations"],"description":"Which list: competitors (org_competitor_domains), offerings, or integrations (the ones the connected product repo ships)."},"items":{"type":"array","items":{"type":"string"},"description":"Every item of the list, by name."}},"required":["list","items"]},{"type":"null"}],"description":"The org list it should cover (its competitors or its offerings), set by the binding detector; null when its pages map onto no list."},"coverage":{"anyOf":[{"type":"object","properties":{"covered":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"path":{"type":"string"}},"required":["item","path"]},"description":"Each covered item and its page."},"missing":{"type":"array","items":{"type":"string"},"description":"Items with no page in the family yet."},"ratio":{"type":"number","description":"covered / items, 0..1."}},"required":["covered","missing","ratio"]},{"type":"null"}],"description":"\"3 of 11\": the page that covers each covered item and the items with no page yet. Read against today's members, so an excluded page no longer covers its item. Null without a universe."},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"members":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"source":{"type":"string"},"confidence":{"type":["number","null"]},"included":{"type":"boolean"},"reason":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]}},"required":["path","source","confidence","included","reason","decidedBy","decidedAt"]}},"createdAt":{"type":"string"}},"required":["id","host","name","detectorKey","status","statusReason","source","confidence","rules","fold","evidence","universe","coverage","decidedBy","decidedAt","members","createdAt"]}},"required":["family"]}},{"name":"list_site_proposals","description":"Proposals on a host at or under a path: each is a registry tool call not yet made ({ tool, input }), with who proposed it and why, who decided and their reason, its channel (repo_pr, cms, publish, handoff) and status (proposed → accepted → shipped | failed, or dismissed). Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"Only proposals at or under this path.","type":"string","maxLength":2048},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"limit":{"description":"Default 200, newest first.","type":"integer","minimum":1,"maximum":500}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"proposals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"pageId":{"type":["string","null"]},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":["string","null"]},"observationIds":{"type":"array","items":{"type":"string"}},"channel":{"type":"string","enum":["repo_pr","cms","publish","handoff"]},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"shippedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","host","path","pageId","tool","input","label","rationale","observationIds","channel","status","statusReason","source","proposedByUserId","decidedBy","decidedAt","shippedAt","shipResult","createdAt"]}}},"required":["proposals"]}},{"name":"create_site_proposal","description":"Propose a change to a page or folder as a call to an existing tool ({ tool, input }). The input is validated against that tool's own schema now, so an accepted proposal can always run. Link the observations that motivated it; give a short imperative label (\"Rewrite it\", \"Link these 9 posts\") and the reason. Nothing runs until decide_site_proposal accepts it. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"The node the proposal concerns (a page or folder); the root when omitted.","type":"string","maxLength":2048},"tool":{"type":"string","minLength":1,"maxLength":100,"description":"A registry tool name, e.g. fix_audit_issue or start_repo_change."},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The tool's input; validated against the tool's own schema now."},"label":{"type":"string","minLength":1,"maxLength":80,"description":"Short imperative label shown as the action: \"Rewrite it\", \"Link these 9 posts\"."},"rationale":{"description":"Why, in plain English.","type":"string","maxLength":2000},"observation_ids":{"description":"The observations that motivated it.","maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":600}}},"required":["host","tool","input","label"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"proposal":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"pageId":{"type":["string","null"]},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":["string","null"]},"observationIds":{"type":"array","items":{"type":"string"}},"channel":{"type":"string","enum":["repo_pr","cms","publish","handoff"]},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"shippedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","host","path","pageId","tool","input","label","rationale","observationIds","channel","status","statusReason","source","proposedByUserId","decidedBy","decidedAt","shippedAt","shipResult","createdAt"]}},"required":["proposal"]}},{"name":"decide_site_proposal","description":"Accept or dismiss (optionally with a reason) a proposal. Accepting records who accepted it and when and hands it to the one ship function: fix_audit_issue, fix_site_findings and start_repo_change proposals start a repo change through the existing path; fix_site_observations on the handoff channel posts its instructions to Slack or Linear for the org's own agent; every other tool answers not_yet_supported, and the proposal stays accepted, honestly unshipped. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"proposal_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"decision":{"type":"string","enum":["accept","dismiss"]},"reason":{"description":"Optional: why it was dismissed, or a note when accepting.","type":"string","minLength":1,"maxLength":1000}},"required":["proposal_id","decision"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"proposal":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"pageId":{"type":["string","null"]},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":["string","null"]},"observationIds":{"type":"array","items":{"type":"string"}},"channel":{"type":"string","enum":["repo_pr","cms","publish","handoff"]},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"shippedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","host","path","pageId","tool","input","label","rationale","observationIds","channel","status","statusReason","source","proposedByUserId","decidedBy","decidedAt","shippedAt","shipResult","createdAt"]},"ship":{"anyOf":[{"type":"object","properties":{"shipped":{"type":"boolean"},"reason":{"type":"string","enum":["not_yet_supported","failed"]},"message":{"type":"string"},"result":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["shipped"]},{"type":"null"}],"description":"What the one ship function did on accept; null on dismiss."},"message":{"type":"string"}},"required":["proposal","ship","message"]}},{"name":"fix_site_observations","description":"Fix several observations on one host as ONE change: Fix's and the Issues panel's \"Fix all\" / \"Fix selected\" (the ticked rows; a migration, risk high, only when ticked on purpose) are this call, through start_growth_build's `fixes`. To do what Fix all does, pass the observation ids of get_site_tree's `focus` items that can go in a PR, leaving out any with risk high. An item standing for one issue on several pages (\"Broken internal link on 3 pages\") carries those pages' ids in `members` (its pages in `evidence.pages`, in the same order): pass them all to fix every page in one PR, or some of them to fix only those pages, as Fix does when a member unticks a page. Writes the instructions from what the site model already knows (the audit's own descriptors, the registry's drafts), records the batch as one accepted site proposal linked to its observations, and runs a repo change in the repo verified to build the host (list_repo_sites), never a guessed one. With no verified repo a build refuses (NO_REPO_FOR_SITE), but the fix can still go out: `action: \"handoff\"` returns the instructions in `handoff.instructions` to copy into any agent (records nothing), and `slack` / `linear` post them for the organization's own agent without a repo (recorded as a proposal on the handoff channel; `run` is null). Observations a repo change cannot fix come back in `skipped`. An observation on a post Letter publishes itself (a Letter collection served by Letter's hosting or its published API) is fixed on the post, never in the PR and with no repo needed: its words through edit_article_with_prompt, its meta title or description through update_article, each recorded as an accepted proposal and listed in `edited`; the edit waits in the post's Update live post (publish_article). Calling it again while a fix for the same set is running returns that run. Spends model credit; it stays open for review unless the org's auto-merge (set_repo_change_settings auto_merge_after_hours) or a build_merge action merges it. Poll get_repo_change. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"observation_ids":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"string","minLength":1,"maxLength":600},"description":"The observations to fix, all on this host: stored ids or derived `key@path` ids from list_site_observations / get_site_tree. A group item's `members` are its ids."},"base_branch":{"description":"Branch to change and open the PR against.","type":"string","minLength":1,"maxLength":200},"preview":{"description":"Serve the changed site for review first; the PR opens on publish_repo_change.","type":"boolean"},"action":{"description":"How the fix happens: build (preview first), build_pr (open the PR straight away), build_merge, slack (post it to the organization's Slack channel for its own coding agent), linear (file it as an issue in the organization's Linear team), or handoff (return the instructions in `handoff.instructions`, to copy into any agent; records nothing). slack and linear work with no repo connected too (recorded as a proposal on the handoff channel, no repo change). Wins over preview; with neither, the organization's default (get_repo_change_settings).","type":"string","enum":["build","build_pr","build_merge","slack","linear","handoff"]},"with_preview":{"description":"With action build_pr or build_merge: serve the fixed site at a preview URL too, and open the PR as soon as the preview is up (or has failed to start). The preview stays up next to the PR until it expires 72 hours later. Default false.","type":"boolean"},"harness":{"description":"The coding agent that makes the change: claude-code (Claude Code) or letterpress-1 (Letter's own harness). Omitted: this deployment's default. Recorded on the run (run.harness), and a revision keeps it.","type":"string","enum":["claude-code","letterpress-1"]}},"required":["host","observation_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"proposal":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string"},"pageId":{"type":["string","null"]},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"label":{"type":"string"},"rationale":{"type":["string","null"]},"observationIds":{"type":"array","items":{"type":"string"}},"channel":{"type":"string","enum":["repo_pr","cms","publish","handoff"]},"status":{"type":"string","enum":["proposed","accepted","shipped","failed","dismissed"]},"statusReason":{"type":["string","null"]},"source":{"type":"string"},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"shippedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"createdAt":{"type":"string"}},"required":["id","host","path","pageId","tool","input","label","rationale","observationIds","channel","status","statusReason","source","proposedByUserId","decidedBy","decidedAt","shippedAt","shipResult","createdAt"]},{"type":"null"}],"description":"The accepted proposal that records the batch; null only when a running fix was reused and its proposal row is gone."},"run":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["github","gitlab"],"description":"Where the repo lives. On gitlab, prNumber and prUrl are a merge request's (!12)."},"repo":{"type":"string","description":"owner/name; on GitLab the owner is the group path (group/subgroup)"},"baseBranch":{"type":"string"},"instructions":{"type":"string"},"status":{"type":"string","enum":["running","succeeded","no_changes","failed","cancelled","sent"]},"phase":{"type":"string","enum":["provisioning","staging","running","previewing","review","benching","publishing","done"],"description":"provisioning → staging → running (Claude Code works) → publishing → done. Preview runs add previewing (the changed site starts) and review (waiting for publish_repo_change) before publishing."},"model":{"type":"string"},"harness":{"type":"string","enum":["claude-code","letterpress-1","codex"],"description":"The coding agent the sandbox ran: Claude Code, letterpress-1 (Letter's own build of OpenCode), or codex (a staff benchmark's stock Codex)."},"budgetUsd":{"type":"number"},"costUsd":{"type":"number","description":"Model spend so far, at list price."},"branch":{"type":["string","null"]},"prUrl":{"description":"The pull request, once opened. Letter merges it only when asked (build_merge, or merge_repo_change once it's ready) or when the organization turned auto-merge on (autoMerge).","type":["string","null"]},"prNumber":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"prState":{"anyOf":[{"type":"string","enum":["open","merged","closed"]},{"type":"null"}],"description":"Where the pull request stands on GitHub, read live: open (waiting for the customer), merged, or closed without merging. Null before a PR exists or when GitHub couldn't be asked."},"prMergedAt":{"description":"When the pull request was merged, if it was.","type":["string","null"]},"prAuthor":{"description":"Who opened the PR, when an outside agent did (a change sent to Slack).","type":["string","null"]},"verification":{"anyOf":[{"type":"object","properties":{"state":{"type":"string","enum":["waiting","checking","verified","partial","still_there","timed_out","not_checked"]},"detail":{"type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["state","detail","checkedAt"]},{"type":"null"}],"description":"After the merge, whether the fix landed: the source that found it looks again (quick check, fresh audit). verified, partial, still_there; waiting/checking while it does; not_checked when nothing can re-check it."},"summary":{"description":"Claude Code's own summary of what it changed and why.","type":["string","null"]},"filesChanged":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"change":{"type":"string","enum":["added","modified","deleted"]}},"required":["path","change"]}},"preview":{"anyOf":[{"type":"object","properties":{"url":{"description":"The changed site, live, once it answers, opened on the first change (its review panel walks the rest). Share it for review.","type":["string","null"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"The page, as a path on the site."},"text":{"type":"string","description":"A few words of the new text as a visitor reads it; empty when nothing new is written."},"selector":{"type":"string","description":"A CSS selector for the elements the change touches on that page (an image, every heading restyled); empty when none was given."},"note":{"type":"string","description":"What changed there."},"url":{"description":"This change on the live preview: its page, scrolled to it. Null once the preview is down.","type":["string","null"]}},"required":["path","text","selector","note","url"]},"description":"Each place the change shows on the site, in reading order, as Claude Code listed it."},"revisionRunId":{"description":"A follow-up being built on this preview (revise_repo_change); it takes over this preview's link when ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["starting","ready","failed"]},{"type":"null"}]},"detail":{"description":"Why the preview failed; the PR can still be made.","type":["string","null"]},"expiresAt":{"description":"Review ends with no PR at this time unless Make PR comes first.","type":["string","null"]},"publishRequestedAt":{"description":"When Make PR (publish_repo_change) was asked for.","type":["string","null"]}},"required":["url","changes","revisionRunId","state","detail","expiresAt","publishRequestedAt"]},{"type":"null"}],"description":"Null unless the run serves a preview: action build (or preview: true, or the org default build), or build_pr / build_merge with with_preview."},"revisesRunId":{"description":"The preview this run revises (revise_repo_change); it started from that run's change.","type":["string","null"]},"revisedByRunId":{"description":"The follow-up that took over this run's preview: this run ended there (status cancelled) and its review, preview link and PR are that run's now. Follow it with get_repo_change.","type":["string","null"]},"continuesRunId":{"description":"The run whose open pull request this one follows up on (revise_repo_change once the PR is open): it checked out that PR's branch and pushed its commit onto it. prUrl is that PR; prNumber stays on the run that opened it.","type":["string","null"]},"rebuildsRunId":{"description":"Started by auto-merge: the run whose pull request this one makes again on the current code (that PR conflicted, or failed its checks once brought up to date).","type":["string","null"]},"autoMerge":{"anyOf":[{"type":"object","properties":{"dueAt":{"description":"When auto-merge merges this run's open pull request with nobody acting (from requestedAt once a person asked); null when the organization has it off or the PR isn't open.","type":["string","null"]},"requestedAt":{"description":"When a person asked to merge it (merge_repo_change): it merges once it's ready.","type":["string","null"]},"state":{"anyOf":[{"type":"string","enum":["syncing","waiting","blocked","fixing","rebuilding","merged","replaced","gave_up","skipped"]},{"type":"null"}],"description":"What auto-merge did last: syncing (brought the PR up to date, its checks are running), waiting (its checks are running), blocked (the repo's branch rules refused the merge, e.g. a review is required; it tries again hourly), fixing (its checks failed, and a follow-up is fixing them on the same PR: once for a window, up to 3 times for a merge a person asked for), rebuilding (the PR conflicted, so the change is being made again on the current code: once for a window, up to 3 times for a merge a person asked for), merged, replaced (the remade PR took its place and this one was closed), gave_up, skipped (closed on GitHub, or not Letter's to merge)."},"detail":{"description":"What happened, in a sentence.","type":["string","null"]},"checkedAt":{"type":["string","null"]}},"required":["dueAt","requestedAt","state","detail","checkedAt"]},{"type":"null"}],"description":"Auto-merge on this run's pull request (set_repo_change_settings auto_merge_after_hours), or a person's merge_repo_change, which runs the same steps. Null when neither applies."},"buildId":{"description":"The growth build this change is a step of (list_growth_builds); null when no build started it.","type":["string","null"]},"slack":{"anyOf":[{"type":"object","properties":{"channelId":{"type":"string"},"messageUrl":{"description":"The message in Slack, for a person to open.","type":["string","null"]}},"required":["channelId","messageUrl"]},{"type":"null"}],"description":"Where a `sent` run (action slack) was posted for the organization's own agent; null otherwise."},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"source":{"type":"string","enum":["url","upload"]},"reference":{"type":"boolean","description":"Context the agent read, not a file it put in the repo."}},"required":["name","source","reference"]},"description":"Files the request handed to the sandbox."},"errorCode":{"type":["string","null"]},"errorDetail":{"description":"The raw detail, for diagnosis; tell the person `failure.message`.","type":["string","null"]},"failure":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"What to tell the person: what happened and what to do next, in words"},"ours":{"type":"boolean","description":"Letter's fault (never billed); the person did nothing wrong"},"detail":{"description":"The detail, only when it helps them fix their request","type":["string","null"]}},"required":["message","ours","detail"]},{"type":"null"}],"description":"Set when the run failed."},"requestedVia":{"type":"string","enum":["member","api_key","org_agent","autopilot","auto_merge"],"description":"Who asked. auto_merge: auto-merge started it to make a conflicting PR again (rebuildsRunId)."},"source":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},{"type":"null"}],"description":"What in the application decided this change, e.g. { kind: \"site_audit_issue\", audit_id, issue_type, site_host }, or { kind: \"collection_publish\", article_id, collection_id, target_id, site_host, path } for a post published as a pull request (no sandbox ran; model \"none\"); null when the instructions were written by hand."},"brandCheckHost":{"description":"The site whose learned brand the build sandbox checked its pages against before the PR, fixing what failed; read the checks with list_brand_reviews { repo_change_run_id }. Null: no checks.","type":["string","null"]},"verifier":{"anyOf":[{"type":"object","properties":{"outcome":{"anyOf":[{"type":"string","enum":["passed","fixed","open_issues","skipped","error"]},{"type":"null"}],"description":"How the review left the change: passed (no blocking issues), fixed (passed after a fix round), open_issues (blocking issues left; the PR body lists them), skipped (never reviewed), error (the reviewer failed; the change went on as the agent left it). Null while the agent works."},"maxRounds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Fix rounds the review could ask for; reviews = maxRounds + 1."},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 = the first review"},"verdict":{"type":"string","enum":["pass","fix","error"]},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["blocking","minor"]},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string"}},"required":["id","severity","category","file","line","evidence","fix"]}}},"required":["round","verdict","issues"]},"description":"Each review, oldest first: blocking issues sent the agent back to fix them."}},"required":["outcome","maxRounds","rounds"]},{"type":"null"}],"description":"The verifier: an adversarial review of the diff against the brief before the change went out, with fix rounds for blocking issues. Null: the run had none."},"attention":{"anyOf":[{"type":"object","properties":{"message":{"type":"string","description":"One sentence: \"Needs attention: the review left N blocking issues open.\" and the first one's fix"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string","enum":["brief","brand","broken","scope","safety"]},"file":{"type":["string","null"]},"line":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"evidence":{"type":"string","description":"The brief's words it fails, or the diff's lines, quoted"},"fix":{"type":"string","description":"What to change, in the reviewer's words"}},"required":["id","category","file","line","evidence","fix"]},"description":"Each blocking issue the last review left open"}},"required":["message","issues"]},{"type":"null"}],"description":"Set when the review left the change with blocking issues after every fix round and the last-chance pass: it needs a person's attention before it is merged, whatever its preview or PR says. Null: nothing open (passed, fixed, not reviewed, or still under review)."},"startedAt":{"type":"string"},"completedAt":{"type":["string","null"]},"progress":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string"},"message":{"type":"string"},"sandbox":{"description":"On the line where the run got its sandbox: staged (the repo's own, checked out already), warm (a pool spare) or cold (built for the run, minutes slower).","type":"string","enum":["staged","warm","cold"]}},"required":["at","message"]},"description":"Newest first."}},"required":["id","provider","repo","baseBranch","instructions","status","phase","model","harness","budgetUsd","costUsd","branch","prUrl","prNumber","prState","prMergedAt","prAuthor","verification","summary","filesChanged","preview","revisesRunId","revisedByRunId","continuesRunId","rebuildsRunId","autoMerge","buildId","slack","attachments","errorCode","errorDetail","failure","requestedVia","source","brandCheckHost","verifier","attention","startedAt","completedAt","progress"]},{"type":"null"}],"description":"The repo change; null when the fix was handed off (action handoff, or slack / linear with no repo)."},"reused":{"type":"boolean","description":"True when a run for exactly these observations was already going and this is that run."},"fixing":{"type":"array","items":{"type":"string"},"description":"The observation ids the PR fixes."},"handoff":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["prompt","slack","linear"]},"instructions":{"type":"string","description":"The change as instructions for a coding agent: what, where, how."},"label":{"type":"string"},"permalink":{"description":"The Slack message, when sent there.","type":["string","null"]},"issue":{"anyOf":[{"type":"object","properties":{"identifier":{"type":"string"},"url":{"type":"string"}},"required":["identifier","url"]},{"type":"null"}],"description":"The Linear issue, when filed."}},"required":["kind","instructions","label","permalink","issue"]},{"type":"null"}],"description":"Set when the fix was handed off instead of built: the instructions, and where they went."},"skipped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"}},"required":["id","reason"]},"description":"Asked for, but not something a repo change can do, or already decided."},"edited":{"description":"Posts Letter publishes itself whose issues were fixed by editing the post (its words with edit_article_with_prompt, its meta title or description with update_article), not in a pull request. The edits wait in each post's Update live post (publish_article), which puts them live.","type":"array","items":{"type":"object","properties":{"article_id":{"type":"string"},"collection_id":{"type":"string"},"proposal_id":{"type":"string","description":"The accepted proposal that records the edit (list_site_proposals)."},"observation_ids":{"type":"array","items":{"type":"string"}},"edit":{"type":"string","enum":["words","fields"],"description":"words: the post's AI edit; fields: its meta title or description."}},"required":["article_id","collection_id","proposal_id","observation_ids","edit"]}},"message":{"type":"string"}},"required":["proposal","run","reused","fixing","handoff","skipped","message"]}},{"name":"read_site_page","description":"One page of a mapped site as markdown: the page's own content, with the chrome every page repeats (nav, header, footer, announcement bars) cut away, so you see what the page says without rendering it. Read through Context.dev's main-content markdown, then cut at the edges against a sibling page on the same host. Read live, as the site shows it now: a read is reused for at most five minutes, and kept so the map and page drafts (which branch from it) share it; `refresh` reads again even within those minutes (Context.dev credit). Any path on a host in the map can be read, listed in get_site_tree or not (the map is the sitemap). When the live read fails, the last read comes back with its readAt and fresh: false. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"type":"string","minLength":1,"maxLength":2048,"description":"The page's path, e.g. /pricing: any page on the host, in the map or not."},"refresh":{"description":"Read the page again even when it was read in the last five minutes (spends Context.dev credit).","type":"boolean"}},"required":["host","path"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"read":{"type":"object","properties":{"host":{"type":"string"},"path":{"type":"string"},"url":{"type":"string"},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"markdown":{"type":"string","description":"The page's own content as markdown, the chrome every page repeats cut away."},"chromeBlocksRemoved":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Blocks cut from the top and bottom as site chrome."},"comparedWithPath":{"description":"The sibling page the chrome was matched against.","type":["string","null"]},"provider":{"type":"string"},"readAt":{"type":"string"},"fresh":{"type":"boolean","description":"True when this call read the page; false when a stored read was reused."}},"required":["host","path","url","title","description","markdown","chromeBlocksRemoved","comparedWithPath","provider","readAt","fresh"]}},"required":["read"]}},{"name":"search_web","description":"Search the web through the deployment's research provider (the same one the writing pipeline gathers sources with): a few sources with their title, URL, text and date. Letterpress's own research beyond what Letter holds about the account (docs/architecture/one-recipe-engine.md, \"Own research\"): a rival's page, a claim to check, a market, a regulation. Read a result in full with read_url. Rate limited; spends provider credit. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":300,"description":"What to search for, in a few words."},"limit":{"description":"How many sources to return (1..10, default 5).","type":"integer","minimum":1,"maximum":10}},"required":["query"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"provider":{"type":"string","description":"The research provider that searched (exa, youcom, context-dev, native)"},"results":{"type":"array","items":{"type":"object","properties":{"url":{"type":["string","null"]},"title":{"type":["string","null"]},"text":{"type":"string","description":"The source's text, cut to a few hundred characters"},"published_date":{"type":["string","null"]}},"required":["url","title","text","published_date"]}}},"required":["provider","results"]}},{"name":"read_url","description":"One public page as readable text (up to 6,000 characters), with its title and date: any site, not only a mapped one. Behind the same guard every fetch in the product passes (no private addresses), with a rendered fallback for pages a plain fetch cannot read. Letterpress's own research beyond what Letter holds. Rate limited. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"The page's URL (http or https, public)."}},"required":["url"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ok":{"type":"boolean"},"url":{"type":"string"},"final_url":{"description":"Where the read landed after redirects, when it differs","type":["string","null"]},"title":{"type":["string","null"]},"text":{"type":"string","description":"Readable text, cut to 6,000 characters"},"published_date":{"type":["string","null"]},"reason":{"description":"With ok false: why it could not be read","type":["string","null"]}},"required":["ok","url","final_url","title","text","published_date","reason"]}},{"name":"list_site_page_drafts","description":"The pages a site doesn't have yet: landing pages suggested by research_site_pages or a person, and the ones already written as markdown or shipped. Each carries where it would live (`path`), the folder it shows under in the map (`parentPath`, drawn purple at the end of that folder), what it targets, why, the existing page it branches from, and its status: suggested → drafting → drafted → shipped (or failed / dismissed). The site map stays the source of truth for what exists; a draft is a proposal about what should. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"Only drafts at or under this path.","type":"string","maxLength":2048},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"include_dismissed":{"description":"Include dismissed drafts (default false).","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"drafts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]}}},"required":["drafts"]}},{"name":"get_site_page_draft","description":"One page draft in full: its markdown (with front matter), the open questions the writer couldn't support from what it was given, and the brand guidance it was written under. Poll this after start_growth_build names its idea until status leaves `drafting`. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"draft_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The page draft's id (list_site_page_drafts)."}},"required":["draft_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"draft":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]}},"required":["draft"]}},{"name":"get_site_brand","description":"The brand learned from one of the organization's own sites (default: its website): whether it has been learned (not_learned, learning, ready, failed), when and by whom, and once ready its palette and type roles. Page drafts are written under it and review_brand checks images and pages against it. It is learned when the website first arrives (sign-up) or when someone asks (learn_site_brand, Memory → Brand), never on its own otherwise. Reads only. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"description":"One of the organization's own hosts, e.g. acme.com. Defaults to the organization's website.","type":"string","minLength":1,"maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"brand":{"type":"object","properties":{"host":{"type":"string"},"state":{"type":"string","enum":["not_learned","learning","ready","failed","unavailable"],"description":"not_learned: nobody has asked yet. learning: the site is being read (a few minutes). ready: briefs and reviews use it. failed: see failure; learning again may help."},"failure":{"type":["string","null"]},"learnedAt":{"type":["string","null"]},"learnedBy":{"description":"The user who asked to learn it; null with learnedAt set means the system (at sign-up) or an agent.","type":["string","null"]},"profile":{"anyOf":[{"type":"object","properties":{"name":{"type":["string","null"]},"colors":{"type":"array","items":{"type":"object","properties":{"hex":{"type":"string"},"name":{"type":["string","null"]},"usage":{"type":["string","null"]}},"required":["hex","name","usage"]}},"type":{"type":"array","items":{"type":"object","properties":{"role":{"type":["string","null"]},"family":{"type":["string","null"]},"weight":{"type":["number","null"]}},"required":["role","family","weight"]}}},"required":["name","colors","type"]},{"type":"null"}],"description":"What was learned: palette and type roles. Present once ready."}},"required":["host","state","failure","learnedAt","learnedBy","profile"]}},"required":["brand"]}},{"name":"learn_site_brand","description":"Learn the brand of one of the organization's own sites (default: its website) from the site itself: voice, palette, typography, logo. Returns at once with state learning; it is usually ready in a few minutes (poll get_site_brand). A brand already learned or being learned is kept and nothing is spent, unless `relearn` (after the site's look changes). Spends credit when it starts. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"description":"One of the organization's own hosts, e.g. acme.com. Defaults to the organization's website.","type":"string","minLength":1,"maxLength":253},"relearn":{"description":"Learn it again from the site as it is now, replacing what was learned (spends credit). Default false: a brand already learned or being learned is kept.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"brand":{"type":"object","properties":{"host":{"type":"string"},"state":{"type":"string","enum":["not_learned","learning","ready","failed","unavailable"],"description":"not_learned: nobody has asked yet. learning: the site is being read (a few minutes). ready: briefs and reviews use it. failed: see failure; learning again may help."},"failure":{"type":["string","null"]},"learnedAt":{"type":["string","null"]},"learnedBy":{"description":"The user who asked to learn it; null with learnedAt set means the system (at sign-up) or an agent.","type":["string","null"]},"profile":{"anyOf":[{"type":"object","properties":{"name":{"type":["string","null"]},"colors":{"type":"array","items":{"type":"object","properties":{"hex":{"type":"string"},"name":{"type":["string","null"]},"usage":{"type":["string","null"]}},"required":["hex","name","usage"]}},"type":{"type":"array","items":{"type":"object","properties":{"role":{"type":["string","null"]},"family":{"type":["string","null"]},"weight":{"type":["number","null"]}},"required":["role","family","weight"]}}},"required":["name","colors","type"]},{"type":"null"}],"description":"What was learned: palette and type roles. Present once ready."}},"required":["host","state","failure","learnedAt","learnedBy","profile"]},"started":{"type":"boolean","description":"True when this call started reading the site (and spent credit)."},"message":{"type":"string"}},"required":["brand","started","message"]}},{"name":"review_brand","description":"Check whether an image or a page is on brand, against the brand learned from one of the organization's own sites (default: its website; get_site_brand). Give `image_url` (a cover, a hero, an ad: PNG, JPEG, WebP, GIF or SVG) or `page_url` (its top, about four screens, captured as a desktop screenshot). Each check (color, typography, logo, copy; default all four) comes back pass, fail, uncertain or not_applicable, with a short note and pictures of what was measured, plus an overall verdict. `copy` reads the text in the image (spelling, expected text, voice), not a whole article's prose. Refused when no brand has been learned (learn_site_brand). A review takes two to four minutes: it returns at once with status pending (poll get_brand_review) unless `wait`. Spends one review credit, plus a screenshot for a page. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"description":"One of the organization's own hosts, e.g. acme.com. Defaults to the organization's website.","type":"string","minLength":1,"maxLength":253},"image_url":{"description":"A public image to review (PNG, JPEG, WebP, GIF or SVG): a cover, a hero, an ad.","type":"string","maxLength":2048,"format":"uri"},"page_url":{"description":"A page to review as rendered: its top (about four screens) is captured as a desktop screenshot.","type":"string","maxLength":2048,"format":"uri"},"checks":{"description":"Which checks to run (default all four): color, typography, logo, copy.","minItems":1,"maxItems":4,"type":"array","items":{"type":"string","enum":["color","typography","logo","copy"]}},"notes":{"description":"What the image is for, the exact text it should carry, or anything else the review should know.","type":"string","maxLength":4000},"label":{"description":"What this is, for whoever reads it later, e.g. \"cover\".","type":"string","maxLength":200},"article_id":{"description":"The post this image or page belongs to, when there is one.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"wait":{"description":"Review it before returning (up to a few minutes). Default false: returns at once with status pending; poll get_brand_review.","type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"review":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string","description":"The site whose brand it was reviewed against."},"target":{"type":"object","properties":{"kind":{"type":"string","enum":["image","page"]},"url":{"type":"string"}},"required":["kind","url"]},"checks":{"type":"array","items":{"type":"string","enum":["color","typography","logo","copy"]}},"notes":{"type":["string","null"]},"label":{"type":["string","null"]},"articleId":{"type":["string","null"]},"status":{"type":"string","enum":["pending","completed","failed"]},"results":{"type":"object","properties":{"color":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"typography":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"logo":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"copy":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]}},"description":"One result per check run, once completed. not_applicable is not a pass."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}],"description":"fail if any check failed, else uncertain if any was, else pass. Null until completed."},"error":{"type":["string","null"]},"requestedVia":{"type":"string"},"repoChangeRunId":{"description":"The repo change whose build sandbox checked this page before its PR; null otherwise.","type":["string","null"]},"createdAt":{"type":"string"},"completedAt":{"type":["string","null"]},"againstLive":{"description":"A repo change's page check read against the live site: what the change introduced, which is what its build acts on. Absent for reviews not tied to a repo change.","anyOf":[{"type":"object","properties":{"baselineId":{"type":["string","null"]},"baselineUrl":{"description":"The live page it was compared with: the same path, or the home page for a page not live yet.","type":["string","null"]},"baselineStatus":{"type":"string","enum":["pending","completed","failed","missing"]},"results":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"type":["string","null"]},"reason":{"anyOf":[{"type":"string","enum":["introduced","pre_existing","baseline_pending","no_baseline"]},{"type":"null"}],"description":"Why a fail reads as it does: introduced by the change; pre_existing (the live page fails it too, so it reads as a pass); baseline_pending or no_baseline (not counted: uncertain). Null for a check that didn't fail."}},"required":["verdict","note","reason"]},"description":"Each check as the change is judged: a fail only where the change introduced it."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}]}},"required":["baselineId","baselineUrl","baselineStatus","results","overall"]},{"type":"null"}]}},"required":["id","host","target","checks","notes","label","articleId","status","results","overall","error","requestedVia","repoChangeRunId","createdAt","completedAt"]}},"required":["review"]}},{"name":"get_brand_review","description":"One brand review (review_brand): its status, each check's verdict and note, the pictures of what was measured, and the overall verdict. Poll this after review_brand until status leaves `pending`. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["review_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"review":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string","description":"The site whose brand it was reviewed against."},"target":{"type":"object","properties":{"kind":{"type":"string","enum":["image","page"]},"url":{"type":"string"}},"required":["kind","url"]},"checks":{"type":"array","items":{"type":"string","enum":["color","typography","logo","copy"]}},"notes":{"type":["string","null"]},"label":{"type":["string","null"]},"articleId":{"type":["string","null"]},"status":{"type":"string","enum":["pending","completed","failed"]},"results":{"type":"object","properties":{"color":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"typography":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"logo":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"copy":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]}},"description":"One result per check run, once completed. not_applicable is not a pass."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}],"description":"fail if any check failed, else uncertain if any was, else pass. Null until completed."},"error":{"type":["string","null"]},"requestedVia":{"type":"string"},"repoChangeRunId":{"description":"The repo change whose build sandbox checked this page before its PR; null otherwise.","type":["string","null"]},"createdAt":{"type":"string"},"completedAt":{"type":["string","null"]},"againstLive":{"description":"A repo change's page check read against the live site: what the change introduced, which is what its build acts on. Absent for reviews not tied to a repo change.","anyOf":[{"type":"object","properties":{"baselineId":{"type":["string","null"]},"baselineUrl":{"description":"The live page it was compared with: the same path, or the home page for a page not live yet.","type":["string","null"]},"baselineStatus":{"type":"string","enum":["pending","completed","failed","missing"]},"results":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"type":["string","null"]},"reason":{"anyOf":[{"type":"string","enum":["introduced","pre_existing","baseline_pending","no_baseline"]},{"type":"null"}],"description":"Why a fail reads as it does: introduced by the change; pre_existing (the live page fails it too, so it reads as a pass); baseline_pending or no_baseline (not counted: uncertain). Null for a check that didn't fail."}},"required":["verdict","note","reason"]},"description":"Each check as the change is judged: a fail only where the change introduced it."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}]}},"required":["baselineId","baselineUrl","baselineStatus","results","overall"]},{"type":"null"}]}},"required":["id","host","target","checks","notes","label","articleId","status","results","overall","error","requestedVia","repoChangeRunId","createdAt","completedAt"]}},"required":["review"]}},{"name":"list_brand_reviews","description":"Brand reviews, newest first: what was checked against which site's brand, and each verdict. Includes review_brand's, and the pages a site change's build sandbox checked before its PR (`repoChangeRunId`). Filter by host, by the post they belong to, or by repo change. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"description":"Only reviews against this host's brand.","type":"string","minLength":1,"maxLength":253},"article_id":{"description":"Only reviews of this post.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"repo_change_run_id":{"description":"Only the checks a repo change's build sandbox made of its pages before the PR (get_repo_change).","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"How many, newest first (default 20).","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"reviews":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string","description":"The site whose brand it was reviewed against."},"target":{"type":"object","properties":{"kind":{"type":"string","enum":["image","page"]},"url":{"type":"string"}},"required":["kind","url"]},"checks":{"type":"array","items":{"type":"string","enum":["color","typography","logo","copy"]}},"notes":{"type":["string","null"]},"label":{"type":["string","null"]},"articleId":{"type":["string","null"]},"status":{"type":"string","enum":["pending","completed","failed"]},"results":{"type":"object","properties":{"color":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"typography":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"logo":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]},"copy":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"description":"Why, in 25 words or fewer.","type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["url","expiresAt"]},"description":"Pictures of what was measured. Links last seven days."}},"required":["verdict","note","evidence"]}},"description":"One result per check run, once completed. not_applicable is not a pass."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}],"description":"fail if any check failed, else uncertain if any was, else pass. Null until completed."},"error":{"type":["string","null"]},"requestedVia":{"type":"string"},"repoChangeRunId":{"description":"The repo change whose build sandbox checked this page before its PR; null otherwise.","type":["string","null"]},"createdAt":{"type":"string"},"completedAt":{"type":["string","null"]},"againstLive":{"description":"A repo change's page check read against the live site: what the change introduced, which is what its build acts on. Absent for reviews not tied to a repo change.","anyOf":[{"type":"object","properties":{"baselineId":{"type":["string","null"]},"baselineUrl":{"description":"The live page it was compared with: the same path, or the home page for a page not live yet.","type":["string","null"]},"baselineStatus":{"type":"string","enum":["pending","completed","failed","missing"]},"results":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","uncertain","not_applicable"]},"note":{"type":["string","null"]},"reason":{"anyOf":[{"type":"string","enum":["introduced","pre_existing","baseline_pending","no_baseline"]},{"type":"null"}],"description":"Why a fail reads as it does: introduced by the change; pre_existing (the live page fails it too, so it reads as a pass); baseline_pending or no_baseline (not counted: uncertain). Null for a check that didn't fail."}},"required":["verdict","note","reason"]},"description":"Each check as the change is judged: a fail only where the change introduced it."},"overall":{"anyOf":[{"type":"string","enum":["pass","fail","uncertain"]},{"type":"null"}]}},"required":["baselineId","baselineUrl","baselineStatus","results","overall"]},{"type":"null"}]}},"required":["id","host","target","checks","notes","label","articleId","status","results","overall","error","requestedVia","repoChangeRunId","createdAt","completedAt"]}}},"required":["reviews"]}},{"name":"research_site_pages","description":"Find what kind of pages a mapped site should add, as IDEAS: one kind of page it is missing (\"a page for each industry we serve\", \"a comparison against Rival\") with the pages it means — one, or a family of up to 6. Two kinds only: pages that extend a pattern the site keeps (the next item of a family that covers one of the company's lists, the next page of a folder of landing pages) and landing pages a Build candidate implies (list_build_suggestions: the surfaces the site lacks, from its own words first). Reads the site's own structure, the home page as markdown, the family gaps, the Build candidates, the company's context and every idea and page already suggested, built or dismissed. Every idea cites what it rests on (a family, a folder, a Build candidate) or it is dropped; a surface is never proposed on the pass's own say-so, and proposing nothing is allowed. Stores each idea (list_page_ideas; shown on the Site) with its pages as `suggested` page drafts (purple in the map) and the cited candidate's evidence (the quotes, the counts). A page extends a folder the site already keeps; a page at the root or a new folder is a new surface, Build's (list_build_suggestions), never an idea, and a page that can't be placed is skipped with its reason. Few and sure: at most 4 ideas wait on an organization at a time (the Site shows 3, one waits in reserve; building, dismissing or cycle_page_ideas makes room; when full this returns at once and spends nothing), and only ideas the model rates high confidence are kept. First it reads the company from the site's own pages and fills only the fields that are empty. The pass also runs on its own (the first setup, each refresh that re-maps the site, and a visit to the Site that leaves room). Give `path` to suggest within one folder (/solutions). Spends model credit; builds nothing. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The site's host, e.g. acme.com (www. and the scheme are ignored)."},"path":{"description":"A folder to suggest pages within, e.g. /solutions. The whole site when omitted.","type":"string","maxLength":2048},"count":{"description":"How many ideas to suggest at most (default: the room left; at most 4 wait on an org at a time).","type":"integer","minimum":1,"maximum":4},"focus":{"description":"Direction for the pass, e.g. \"industries we serve\" or \"one page per integration\".","type":"string","maxLength":1000}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ideas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"title":{"type":"string","description":"The kind of page, as a person would say it: \"A page for each industry we serve\"."},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"folder":{"type":"string","description":"The folder its pages go in."},"newFolder":{"type":"boolean","description":"True when the site has no page in `folder` yet and this family starts it."},"status":{"type":"string","enum":["suggested","accepted","dismissed","cycled"],"description":"cycled: shown on the Site on an earlier visit and left unbuilt, so a later visit replaced it."},"statusReason":{"type":["string","null"]},"shownAt":{"description":"When Build first showed it. Null for a suggested idea waiting in reserve for the next visit.","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: research | user | agent | api_key."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"buildId":{"description":"The growth build that accepted it (list_growth_builds).","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"The pages it means, as drafts: one, or a family."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","title","angle","rationale","evidence","folder","newFolder","status","statusReason","shownAt","source","proposedByUserId","decidedBy","decidedAt","buildId","pages","createdAt","updatedAt"]},"description":"The ideas suggested this run, each with its pages."},"drafts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"Every page of those ideas, stored as `suggested` drafts."},"skipped":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"title":{"type":"string"},"reason":{"type":"string"}},"required":["path","title","reason"]}},"model":{"type":"string"},"message":{"type":"string"}},"required":["ideas","drafts","skipped","model","message"]}},{"name":"list_build_suggestions","description":"BUILD'S SUGGESTIONS — what the site should have and doesn't, in its own words first (Instances → Build → Suggested builds). The unit is a SURFACE (the pricing page, an integrations family, the customers page, docs, a comparison against a rival, a section on an existing page, a proof embed); a page is the side effect. A surface is offered only when this site makes it relevant, never because most companies have it. Sources, strongest first, each cited in `evidence` with what was measured: the site's own words (a page that says \"see our pricing\" with no pricing page, a home page naming integrations with no integrations page, \"trusted by 200 companies\" with no customers page: quoted, judged by Jev, set against the map), the org's offerings where the site corroborates them (whole words, never a substring), top rivals' consensus on the surface vocabulary only (highlighted rivals or the ones AI answers name most, a majority, each counted only when its section holds 2+ pages, and the site's words mention it; security, trust and comparisons never from consensus), paths people and AI agents request (25 people or 5 AI assistants in 30 days) that answer 404 with no near match and whose slug is a standard surface or named by the site's own words (a folder and its children are one section), comparison and pricing questions a rival wins, and Proofs' verified companies (5 or more) with no proof page. Behind those obvious yeses, the floor, so there is always a suggestion: an offering the org listed with no page, a section a tracked rival keeps, a comparison against a rival the org tracks, a standard surface the map lacks (sources context, rivals or map; never security, never pricing for a sales-led company), each saying plainly what it rests on and ranked after every evidenced row. Not here: nav and footer entries, an index over a folder, a thin hub, redirects, OG images (Fix's: get_site_tree.focus), article gaps and FAQ blocks (the letterbase's), busy sections, and the next rival of a comparison series the site already keeps (the Site's next-in-pattern row: get_site_tree.patterns). At most `limit` (default 4), strongest first, dismissed ones and surfaces an open page idea already covers left out. Dismiss one with dismiss_site_observation and its `id`; build one with start_growth_build (a change whose instructions quote the evidence). Each `item` is in get_site_tree's item shape. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The mapped, owned site to read Build's suggestions for."},"limit":{"description":"At most this many, strongest source first (default 3: what Build shows).","type":"integer","minimum":1,"maximum":50}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Its dedupe key (`build.suggestion@/<surface>`); dismiss_site_observation takes it."},"host":{"type":"string"},"surface":{"type":"string","description":"The thing the site lacks as a whole: pricing, integrations, customers, docs, security, about, use-cases, comparison:<rival>, offering:<name>, hub:<path> (a section built with its first pages), changelog, path:<path>, folder:<folder>, proof-embed."},"kind":{"type":"string","enum":["page","family","section","embed"]},"summary":{"type":"string","description":"What to build, named: 'Build an integrations family (/integrations)'."},"sources":{"type":"array","items":{"type":"string","enum":["promise","product","context","rivals","access","answers","proofs","releases","letterpress","map","read"]},"description":"Strongest first: promise (the site's own words), product (the connected repo's code), proofs, context, rivals, access, answers, releases; letterpress: proposed by Build's suggestion pass from these signals and the suggestion recipes, checked in code and judged by Jev; map: the floor's surface the site's map lacks, the weakest (the floor's rows rest on context, rivals or map and come after every obvious yes, each saying in relevance what it rests on)."},"evidence":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["promise","product","context","rivals","access","answers","proofs","releases","letterpress","map","read"]},"label":{"type":"string"},"quote":{"description":"The site's own words, when the evidence is a quote.","type":"string"},"path":{"description":"The page the quote is on, or the path requested.","type":"string"},"count":{"type":"number"}},"required":["source","label"]},"description":"Measured only: quotes, counts, requests, questions."},"relevance":{"type":"string","description":"Why this site: the plain why a person reads under the summary (the same words as item.why)."},"more":{"type":"string","description":"The expand: where the evidence came from and what building it changes, two short sentences."},"pages":{"type":"array","items":{"type":"string"},"description":"The pages it implies, as hints for the agent and the idea pass."},"placement":{"type":"object","properties":{"kind":{"type":"string","enum":["extend","start","prerequisite"]},"folder":{"description":"extend: the folder that already keeps the pattern.","type":"string"},"pages":{"type":"number"},"parent":{"description":"start: the page it is built under (/ for the home page).","type":"string"},"hub":{"description":"prerequisite: the section built first, with the members as its first pages.","type":"string"}},"required":["kind"],"description":"Where it goes on this site: start (one new surface under a parent), or prerequisite (the section the site names but lacks is the build, with the evidenced members as its first pages). A candidate that would extend a pattern the site keeps is the Site's, never listed here."},"action":{"type":"string","enum":["pr"],"description":"A code change, always: an article is the letterbase's and is never listed here."},"path":{"type":"string","description":"Where it sits: the site (/), or the page a section or embed goes on."},"item":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"],"description":"The same shape as get_site_tree's items, for the Build view."}},"required":["id","host","surface","kind","summary","sources","evidence","relevance","more","pages","placement","action","path","item"]}},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Candidates before the cap."},"skipped":{"type":"array","items":{"type":"string"},"description":"Sources that could not be read this time (they add nothing, never a zero)."},"pass":{"anyOf":[{"type":"object","properties":{"ranAt":{"type":"string"},"kept":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Suggestions the pass kept."},"rejected":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Proposals a code check dropped, or ranked past the cap."}},"required":["ranAt","kept","rejected"]},{"type":"null"}],"description":"Build's suggestion pass behind the list (research_builds): when it last ran; null when it never has."}},"required":["host","suggestions","total","skipped","pass"]}},{"name":"research_builds","description":"Run Build's suggestion pass for a mapped, owned site (docs/architecture/build-suggestions-agentic.md): Letterpress reads the same signals list_build_suggestions' rules read (the site's own words, its map, top rivals' sections, questions AI answers give to a rival, requested pages that answer nothing, Proofs, the product repo, the rules' own candidates as leads), the suggestion recipes staff wrote, what was dismissed and why and what is already suggested, and proposes what the site should build. Code finds the evidence in what was handed and checks the facts (the map, a person's dismissals within the window, that it is Build's: never an article, never a fix to a page that exists); then Jev, playing the site owner, reads every kept item (how obvious, how specific to this company, how well the evidence carries it, whether it is one build, how directly it serves the ask) with no bar, and the best are stored in that order; list_build_suggestions returns them after the rules' candidates; reading never runs the pass. Runs on its own after each refresh; skipped (ran:false) when the stored pass is under an hour old. Spends model credit; builds nothing. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The mapped, owned site to run Build's suggestion pass for."},"fresh":{"description":"Run now even when the stored pass is under an hour old (an explicit ask to look again).","type":"boolean"}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"ran":{"type":"boolean","description":"False when the stored pass was fresh enough, or the host is watched."},"reason":{"type":"string"},"pass":{"anyOf":[{"type":"object","properties":{"ranAt":{"type":"string"},"kept":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Suggestions the pass kept."},"rejected":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Proposals a code check dropped, or ranked past the cap."}},"required":["ranAt","kept","rejected"]},{"type":"null"}]},"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Its dedupe key (`build.suggestion@/<surface>`); dismiss_site_observation takes it."},"host":{"type":"string"},"surface":{"type":"string","description":"The thing the site lacks as a whole: pricing, integrations, customers, docs, security, about, use-cases, comparison:<rival>, offering:<name>, hub:<path> (a section built with its first pages), changelog, path:<path>, folder:<folder>, proof-embed."},"kind":{"type":"string","enum":["page","family","section","embed"]},"summary":{"type":"string","description":"What to build, named: 'Build an integrations family (/integrations)'."},"sources":{"type":"array","items":{"type":"string","enum":["promise","product","context","rivals","access","answers","proofs","releases","letterpress","map","read"]},"description":"Strongest first: promise (the site's own words), product (the connected repo's code), proofs, context, rivals, access, answers, releases; letterpress: proposed by Build's suggestion pass from these signals and the suggestion recipes, checked in code and judged by Jev; map: the floor's surface the site's map lacks, the weakest (the floor's rows rest on context, rivals or map and come after every obvious yes, each saying in relevance what it rests on)."},"evidence":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["promise","product","context","rivals","access","answers","proofs","releases","letterpress","map","read"]},"label":{"type":"string"},"quote":{"description":"The site's own words, when the evidence is a quote.","type":"string"},"path":{"description":"The page the quote is on, or the path requested.","type":"string"},"count":{"type":"number"}},"required":["source","label"]},"description":"Measured only: quotes, counts, requests, questions."},"relevance":{"type":"string","description":"Why this site: the plain why a person reads under the summary (the same words as item.why)."},"more":{"type":"string","description":"The expand: where the evidence came from and what building it changes, two short sentences."},"pages":{"type":"array","items":{"type":"string"},"description":"The pages it implies, as hints for the agent and the idea pass."},"placement":{"type":"object","properties":{"kind":{"type":"string","enum":["extend","start","prerequisite"]},"folder":{"description":"extend: the folder that already keeps the pattern.","type":"string"},"pages":{"type":"number"},"parent":{"description":"start: the page it is built under (/ for the home page).","type":"string"},"hub":{"description":"prerequisite: the section built first, with the members as its first pages.","type":"string"}},"required":["kind"],"description":"Where it goes on this site: start (one new surface under a parent), or prerequisite (the section the site names but lacks is the build, with the evidenced members as its first pages). A candidate that would extend a pattern the site keeps is the Site's, never listed here."},"action":{"type":"string","enum":["pr"],"description":"A code change, always: an article is the letterbase's and is never listed here."},"path":{"type":"string","description":"Where it sits: the site (/), or the page a section or embed goes on."},"item":{"type":"object","properties":{"id":{"type":"string"},"tone":{"type":"string","enum":["ok","warn","risk","info"]},"effort":{"type":"string","enum":["none","small","large"],"description":"How much work the fix is."},"priority":{"type":"number","description":"Lower is more pressing and less friction (the attention policy)."},"reach":{"type":"number","description":"The page's reach, or a group's summed reach."},"members":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"For a folder group: the member observation ids."},"summary":{"type":"string"},"why":{"type":"string","description":"The plain why under the summary: one sentence, at most 14 words, what it costs them or what they gain, no term to look up. The same words the UI shows."},"more":{"type":"string","description":"The expand under `why`: what the thing is in plain words and what the fix changes, two short sentences. The evidence facts stay in `evidence` and `sources`."},"observationId":{"type":["string","null"]},"inFlight":{"description":"A change already covering this item, however it started (Instances, an audit fix, @Letter, autopilot): its state, instead of another fix.","anyOf":[{"type":"object","properties":{"runId":{"type":"string"},"label":{"type":"string"},"prUrl":{"type":["string","null"]}},"required":["runId","label","prUrl"]},{"type":"null"}]},"blocked":{"description":"Letter found it cannot make this change here (a sandbox ended 'CANNOT DO', or a check): no fix is offered for it until the facts it rested on move. `say` is why; `code` is one of wrong_repo, not_in_repo, already_done, no_input, not_applicable, needs_info, not_a_page; `next` is the step that makes it doable.","anyOf":[{"type":"object","properties":{"code":{"type":"string"},"say":{"type":"string"},"next":{"description":"What makes it doable, when something does: connect (or correct) the repo that builds the site, map a competitor, edit it in the CMS, set the branch the site deploys from.","type":["string","null"]}},"required":["code","say"]},{"type":"null"}]},"proposalId":{"type":["string","null"]},"familyId":{"type":["string","null"]},"action":{"anyOf":[{"type":"object","properties":{"label":{"type":"string"},"mode":{"type":"string","enum":["call","propose","agent"],"description":"call: run the tool; propose: record a proposal from the draft and accept it; agent: ask the agent to draft one."},"tool":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["label","mode","tool","input"]},{"type":"null"}]},"note":{"description":"An honest status line when the action has been taken but cannot ship yet, or where to edit words a repo change can't reach.","type":["string","null"]},"contentHome":{"description":"Where the page's words live when not in the repo; such an item has no repo fix.","type":["string","null"]},"key":{"description":"The observation key behind it; null for a bare proposal.","type":["string","null"]},"rung":{"description":"The legibility ladder: 1 reach, 2 map, 3 trust, 4 use, 5 fast.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"remedy":{"description":"fix: Fix's; build: what the site lacks; improve: a page that could do more (Build's).","type":"string","enum":["fix","build","improve","status","reading"]},"risk":{"description":"high: a migration (pages move, URLs redirect): ranked like any fix, never pre-ticked.","type":"string","enum":["high"]},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"sources":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"href":{"type":["string","null"]}},"required":["label","href"]}},"path":{"type":"string"}},"required":["id","tone","effort","priority","reach","members","summary","why","more","observationId","proposalId","familyId","action","note","evidence","sources","path"],"description":"The same shape as get_site_tree's items, for the Build view."}},"required":["id","host","surface","kind","summary","sources","evidence","relevance","more","pages","placement","action","path","item"]},"description":"What the pass kept, best first in Jev's rank, each with its evidence."},"rejected":{"type":"array","items":{"type":"object","properties":{"surface":{"type":"string"},"summary":{"type":"string"},"reason":{"type":"string"}},"required":["surface","summary","reason"]},"description":"What was proposed and dropped, and why (a code check, or ranked past the cap)."}},"required":["host","ran","pass","suggestions","rejected"]}},{"name":"research_fixes","description":"Run Fix's suggestion pass for a mapped, owned site (docs/architecture/fix-suggestions-agentic.md): the edits no rule can decide, on pages that exist. Letterpress reads the stored bodies of the most linked pages, the links each has, leads (a page names an offering and doesn't link its page), the Fix recipes staff wrote and what was dismissed and why, and proposes exact edits: link words already in a sentence, add a key page to the navigation, reword or retarget a vague link, correct a statement another page or a company fact contradicts, retitle a page whose title says something it doesn't cover. A change of wording alone is an edit too. Code decides it is Fix's, never Build's (the page is live, the text it changes is quoted verbatim from it; a new page, section or embed is rejected) and that a link's target is live and not linked yet; then Jev, as the owner, reads every kept edit and Fix's bar on its readings decides what is shown. Each kept fix is stored as a Fix item (`fix.suggestion`) on the page it edits, with a draft that makes exactly that edit; the focus holds them until a few fixes were accepted. Open ones whose text left the page are resolved. Runs on its own after each refresh; skipped (ran:false) when the stored pass is under an hour old. Spends model credit; changes no page. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"The mapped, owned site to run Fix's suggestion pass for."}},"required":["host"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"host":{"type":"string"},"ran":{"type":"boolean","description":"False when the stored pass was fresh enough."},"reason":{"type":"string"},"pass":{"anyOf":[{"type":"object","properties":{"ranAt":{"type":"string"},"kept":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"resolved":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Open fixes resolved because the page changed."}},"required":["ranAt","kept","resolved"]},{"type":"null"}]},"suggestions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The fix's observation dedupe key (fix.suggestion@<page>#<kind>:<subject>)."},"kind":{"type":"string","enum":["link","nav","anchor","retarget","correct","rename","retitle","edit"]},"page":{"type":"string","description":"The live page the edit is on."},"current":{"type":"string","description":"The text as it is now, verbatim from the page."},"change":{"type":"string","description":"The same text after the edit."},"target":{"description":"link / anchor / retarget: the live page it links to.","type":["string","null"]},"from":{"description":"retarget: where the link goes now (a URL the crawl found redirecting or broken).","type":["string","null"]},"was":{"description":"rename: the old name.","type":["string","null"]},"now":{"description":"rename: the name the rest of the site uses.","type":["string","null"]},"anchor":{"type":["string","null"]},"summary":{"type":"string"},"why":{"type":"string"},"more":{"type":"string"},"recipe":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"string"},"source":{"type":"string"},"label":{"type":"string"},"quote":{"type":"string"},"path":{"type":"string"}},"required":["ref","source","label"]}}},"required":["id","kind","page","current","change","target","anchor","summary","why","more","recipe","evidence"]},"description":"What the pass kept and stored as Fix items: each an obvious yes to Jev, with its evidence."},"rejected":{"type":"array","items":{"type":"object","properties":{"page":{"type":"string"},"summary":{"type":"string"},"reason":{"type":"string"}},"required":["page","summary","reason"]},"description":"What was proposed and dropped, and why (a code check, the Fix/Build split, or Jev)."}},"required":["host","ran","pass","suggestions","rejected"]}},{"name":"list_page_ideas","description":"The ideas for new pages waiting on the organization (what the Site shows as suggestions): each is one kind of page a site is missing, why, and the pages it means (one, or a family), each a page draft with its path and status. At most 4 wait at a time across the organization's sites. the Site shows the ones with `shownAt` (3 at most); one more waits in reserve (`shownAt` null) for the next visit, and cycle_page_ideas rotates them. Build one by starting a growth build that names it (`idea:<id>` among `subjects`, with its pages as `draft:<id>`): that accepts it. Dismiss with update_page_idea. `status` reads accepted, dismissed or cycled ones instead. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"host":{"description":"Only this site's ideas. Every site of the organization when omitted.","type":"string","minLength":1,"maxLength":253},"status":{"description":"suggested (default): waiting on a person (shown on the Site, or in reserve when shownAt is null). accepted: a build was started with it. dismissed. cycled: a visit to the Site replaced it, unbuilt.","type":"string","enum":["suggested","accepted","dismissed","cycled"]}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ideas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"title":{"type":"string","description":"The kind of page, as a person would say it: \"A page for each industry we serve\"."},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"folder":{"type":"string","description":"The folder its pages go in."},"newFolder":{"type":"boolean","description":"True when the site has no page in `folder` yet and this family starts it."},"status":{"type":"string","enum":["suggested","accepted","dismissed","cycled"],"description":"cycled: shown on the Site on an earlier visit and left unbuilt, so a later visit replaced it."},"statusReason":{"type":["string","null"]},"shownAt":{"description":"When Build first showed it. Null for a suggested idea waiting in reserve for the next visit.","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: research | user | agent | api_key."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"buildId":{"description":"The growth build that accepted it (list_growth_builds).","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"The pages it means, as drafts: one, or a family."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","title","angle","rationale","evidence","folder","newFolder","status","statusReason","shownAt","source","proposedByUserId","decidedBy","decidedAt","buildId","pages","createdAt","updatedAt"]}},"open":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Ideas waiting on a person across the organization's sites."},"max":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"At most this many wait at a time (the shown ones and one in reserve); a research pass only fills the room left."},"shownMax":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The Site shows at most this many at a time."}},"required":["ideas","open","max","shownMax"]}},{"name":"get_page_idea","description":"One page idea with all of its pages (page drafts), whatever its status: who proposed it, who decided and when, and the growth build that accepted it. Requires capability site:read.","capability":"site:read","inputSchema":{"type":"object","properties":{"idea_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The page idea's id (list_page_ideas)."}},"required":["idea_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"idea":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"title":{"type":"string","description":"The kind of page, as a person would say it: \"A page for each industry we serve\"."},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"folder":{"type":"string","description":"The folder its pages go in."},"newFolder":{"type":"boolean","description":"True when the site has no page in `folder` yet and this family starts it."},"status":{"type":"string","enum":["suggested","accepted","dismissed","cycled"],"description":"cycled: shown on the Site on an earlier visit and left unbuilt, so a later visit replaced it."},"statusReason":{"type":["string","null"]},"shownAt":{"description":"When Build first showed it. Null for a suggested idea waiting in reserve for the next visit.","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: research | user | agent | api_key."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"buildId":{"description":"The growth build that accepted it (list_growth_builds).","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"The pages it means, as drafts: one, or a family."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","title","angle","rationale","evidence","folder","newFolder","status","statusReason","shownAt","source","proposedByUserId","decidedBy","decidedAt","buildId","pages","createdAt","updatedAt"]}},"required":["idea"]}},{"name":"cycle_page_ideas","description":"What the Site calls each time it opens: the page ideas to show now, kept fresh. Cycles out the idea shown longest ago when it was left unbuilt since an earlier visit (status cycled, decided by no one; update_page_idea reopens it), never one whose pages someone started and never below 2 shown; shows ideas from the reserve up to 3; and, when there is room, starts a research pass in the background (at most one an hour per site) so the next visit has a fresh one. Opens within 30 minutes of the last cycle are one visit and cycle nothing. `host`: only that site's ideas come back. Spends model credit only when it starts a pass. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"host":{"description":"The site Build is looking at: only its ideas come back. Every site when omitted.","type":"string","minLength":1,"maxLength":253}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ideas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"title":{"type":"string","description":"The kind of page, as a person would say it: \"A page for each industry we serve\"."},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"folder":{"type":"string","description":"The folder its pages go in."},"newFolder":{"type":"boolean","description":"True when the site has no page in `folder` yet and this family starts it."},"status":{"type":"string","enum":["suggested","accepted","dismissed","cycled"],"description":"cycled: shown on the Site on an earlier visit and left unbuilt, so a later visit replaced it."},"statusReason":{"type":["string","null"]},"shownAt":{"description":"When Build first showed it. Null for a suggested idea waiting in reserve for the next visit.","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: research | user | agent | api_key."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"buildId":{"description":"The growth build that accepted it (list_growth_builds).","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"The pages it means, as drafts: one, or a family."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","title","angle","rationale","evidence","folder","newFolder","status","statusReason","shownAt","source","proposedByUserId","decidedBy","decidedAt","buildId","pages","createdAt","updatedAt"]},"description":"The ideas the Site shows now, oldest shown first."},"cycled":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}},"required":["id","title"]},{"type":"null"}],"description":"The idea this visit cycled out (get_page_idea reads it; update_page_idea reopens it)."},"researching":{"type":"boolean","description":"A research pass was started to refill the reserve; new ideas land in a minute or two."},"open":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"max":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"shownMax":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"message":{"type":"string"}},"required":["ideas","cycled","researching","open","max","shownMax","message"]}},{"name":"update_page_idea","description":"Decide a page idea: dismiss it with a reason (its pages nobody has started are dismissed with it, and the research pass reads the reason and won't propose it again), or reopen a dismissed or cycled one (it goes back in reserve; refused with IDEAS_FULL while 4 ideas are waiting). Building one is a growth build that names it. Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"idea_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The page idea's id (list_page_ideas)."},"decision":{"type":"string","enum":["dismiss","reopen"]},"reason":{"description":"Required to dismiss: why the site shouldn't have these pages.","type":"string","minLength":1,"maxLength":1000}},"required":["idea_id","decision"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"idea":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"title":{"type":"string","description":"The kind of page, as a person would say it: \"A page for each industry we serve\"."},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"folder":{"type":"string","description":"The folder its pages go in."},"newFolder":{"type":"boolean","description":"True when the site has no page in `folder` yet and this family starts it."},"status":{"type":"string","enum":["suggested","accepted","dismissed","cycled"],"description":"cycled: shown on the Site on an earlier visit and left unbuilt, so a later visit replaced it."},"statusReason":{"type":["string","null"]},"shownAt":{"description":"When Build first showed it. Null for a suggested idea waiting in reserve for the next visit.","type":["string","null"]},"source":{"type":"string","description":"Who proposed it: research | user | agent | api_key."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"buildId":{"description":"The growth build that accepted it (list_growth_builds).","type":["string","null"]},"pages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]},"description":"The pages it means, as drafts: one, or a family."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","title","angle","rationale","evidence","folder","newFolder","status","statusReason","shownAt","source","proposedByUserId","decidedBy","decidedAt","buildId","pages","createdAt","updatedAt"]}},"required":["idea"]}},{"name":"update_site_page_draft","description":"Decide a suggested page: dismiss it with a reason (the suggestion pass reads dismissals and won't propose the page again) or reopen it. Pages are suggested by the site's own research (research_site_pages); they are not made or edited by hand, and a suggested page is built with its idea (start_growth_build with the idea's subjects, or the Site's Open PR). Requires capability site:write.","capability":"site:write","inputSchema":{"type":"object","properties":{"draft_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The page draft's id (list_site_page_drafts)."},"decision":{"type":"string","enum":["dismiss","reopen"]},"reason":{"description":"Required to dismiss: why the site shouldn't have this page.","type":"string","minLength":1,"maxLength":1000}},"required":["draft_id","decision"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"draft":{"type":"object","properties":{"id":{"type":"string"},"host":{"type":"string"},"path":{"type":"string","description":"Where the page would live."},"parentPath":{"type":"string","description":"The deepest folder the map already has above `path`: where it shows in the tree."},"title":{"type":"string"},"angle":{"type":["string","null"]},"rationale":{"type":["string","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"url":{"type":"string"}},"required":["label"]}},"basedOnPath":{"description":"The existing page it branches from.","type":["string","null"]},"ideaId":{"description":"The page idea it is one page of (list_page_ideas); null for a page drafted on its own.","type":["string","null"]},"markdown":{"description":"Rows the retired copy-first route wrote keep their markdown; null for every page suggested since.","type":["string","null"]},"openQuestions":{"type":"array","items":{"type":"string"},"description":"Retired with the copy-first route; empty for every page suggested since."},"status":{"type":"string","enum":["suggested","drafting","drafted","failed","dismissed","shipped"]},"statusReason":{"type":["string","null"]},"instructions":{"type":["string","null"]},"brand":{"anyOf":[{"type":"object","properties":{"provider":{"anyOf":[{"type":"string","enum":["brief"]},{"type":"null"}],"description":"\"brief\" when guidance was used; null when none was."},"brandId":{"type":["string","null"]},"skillId":{"description":"The version of the learned brand the brief came from.","type":["string","null"]},"briefId":{"type":["string","null"]},"guidance":{"description":"The brief's markdown guidance: voice, typography, palette, layout.","type":["string","null"]},"assets":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"url":{"type":"string"},"mediaType":{"type":["string","null"]}},"required":["kind","url","mediaType"]}},"skipped":{"description":"Why no guidance was used, when none was.","type":["string","null"]},"task":{"description":"What the Brief was asked for.","type":["string","null"]}},"required":["provider","brandId","skillId","briefId","guidance","assets","skipped","task"]},{"type":"null"}]},"model":{"type":["string","null"]},"source":{"type":"string","description":"Who proposed it: user | agent | api_key | research."},"proposedByUserId":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"decidedAt":{"type":["string","null"]},"draftingStartedAt":{"type":["string","null"]},"draftedAt":{"type":["string","null"]},"shipResult":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"liveAt":{"description":"When a refresh first found the built page live in the site map.","type":["string","null"]},"measuredAt":{"description":"When the 28-day `outcome` was recorded, 28 days after liveAt.","type":["string","null"]},"outcome":{"anyOf":[{"type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."},"week":{"description":"The 7-day reading, kept beside the 28-day one.","type":"object","properties":{"impressions":{"type":"number"},"clicks":{"type":"number"},"position":{"type":["number","null"]},"top_query":{"type":["string","null"]},"citations":{"type":"number"},"ai_reads":{"type":"number"},"people":{"description":"People who read it (LetterVisit); null when not collected.","type":["number","null"]},"days":{"type":"number","description":"Days between going live and the reading."}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]}},"required":["impressions","clicks","position","top_query","citations","ai_reads","days"]},{"type":"null"}],"description":"The built page's first live days from its signals: the 7-day reading first, then the 28-day one (with `week` kept); null until the page has been live a week."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","host","path","parentPath","title","angle","rationale","evidence","basedOnPath","ideaId","markdown","openQuestions","status","statusReason","instructions","brand","model","source","proposedByUserId","decidedBy","decidedAt","draftingStartedAt","draftedAt","shipResult","liveAt","measuredAt","outcome","createdAt","updatedAt"]}},"required":["draft"]}}]}