AfterLaunch public API API

AfterLaunch is the agentic growth engine: continuous measurement of how a product gets found, kept as a record and ranked into briefed growth moves. This API is access to one AfterLaunch product run: read the ranked Feed of prepared growth moves, the automated loops and their autonomy, and drafted o

Operations 70

GET /api/v1/content/calendar Read this product run's calendar, up to 100 saved source drafts and their current canonical Feed state. Requires read. Honour queue_position and needs_data. channel_state is a stor #
POST /api/v1/content/calendar File a research idea, original X post or append-only research outcome. Research records preserve the original question, evidence, hold and lifecycle. The caller supplies the conclu #
GET /api/v1/content/experiments Read the growth content experiments already run for this product run, newest first by ran_at. Requires read. Each result is one observation with its stated scope, not a rate. Read- #
GET /api/v1/whoami Confirm the key works and see what it is bound to, in one call: enrollment_id, key_name, key_prefix and scopes, the product_url this run is about, tier and access_state, trial_ends #
GET /api/v1/feed The ranked backlog of prepared growth moves. Live (pending) moves only by default; resolved moves (shipped, skipped, archived, expired) keep stale ranks, so they are excluded unles #
GET /api/v1/loops Every loop PROVISIONED on the enrollment with its autonomy, plus runnable_loop_types: the loop types POST /loops/{loopType}/run accepts. The sets differ: a runnable type must also #
GET /api/v1/loops/{loopType} One loop by type. #
GET /api/v1/enrollment The product-run summary and delivery settings, including the product identity: product_url (the site this run is about), tier (free, founder, growth or scale) and trial_ends_at. Al #
GET /api/v1/outputs The enrollment's drafted outputs (summary; newest first). #
GET /api/v1/outputs/{id} A single drafted output. #
GET /api/v1/visibility The measured AI visibility (GEO) results: share of voice, per-engine coverage, tracked questions with per-engine presence, cited sources with their outreach play, competitor gaps, #
GET /api/v1/seo The site-wide SEO audit: issue groups with severity, affected pages and recommendations (recommendations blank on the free tier, as in the app), the weekly score trend, page perfor #
GET /api/v1/competitors The competitor roster with the measured stat battery for you and each rival (performance, SEO, Core Web Vitals, domain age and authority, G2 and Product Hunt, content freshness), i #
GET /api/v1/outcomes What the shipped growth moves earned: tracked-link clicks per move, shipped-move counters, the weekly movement scoreboard, and the GA4 / Search Console connection state. The per-mo #
GET /api/v1/snapshot The scan this product run started from: positioning, the prioritised actions with impact and effort, the competitor set, and the day-0 SEO and AI visibility baselines. The stable b #
GET /api/v1/activity The product timeline: what the agent and the founder already did and why (drafts, ships, skips, scans, observations, decisions with rationale), newest first. Read before acting so #
GET /api/v1/kb/pages The Memory pages (the shared record the growth engine drafts from): summaries with a content-size hint, no bodies. Fetch one body via /api/v1/kb/pages/{slug}. Read-only exposure; u #
GET /api/v1/kb/pages/{slug} One Memory page by slug, with its full markdown body (capped at 20000 characters; truncated flags a cut tail). Slugs come from /api/v1/kb/pages. #
GET /api/v1/kb/voice-profile The structured voice the engine drafts in: tone, characteristic phrases, vocabulary, sentence style and what it avoids, capped exactly as the drafting context caps them. `register` #
GET /api/v1/kb/business-context The structured positioning facts the engine drafts from: strategic_positioning (one-liner, primary value prop, target audience, notable differentiators), category, positioning_lang #
GET /api/v1/kb/claims The claim library on this product run: the measured numbers the founder will stand behind in public, newest first. Each carries text (the claim in one sentence), number (the measur #
POST /api/v1/kb/claims/record Put one measured number the founder will stand behind in public, with the caveat that travels with it, into this product run's claim library. Scope 'act'; free, unmetered, and avai #
POST /api/v1/kb/voice-axes Write the calibrated leans from a voice round into the FOUNDER'S OWN voice profile, the personal register. It can never touch the brand register: the brand voice came from the webs #
POST /api/v1/kb/voice-rules/record Save one writing rule the person approved into their OWN voice profile, the personal register, never the brand's. The morning posts drafted for them follow it, get_move and get_voi #
POST /api/v1/profiles/record File one public profile or listing the person already has on a directory site (Settings, Your profiles), so AfterLaunch counts it as a verified presence and a weekly check confirms #
POST /api/v1/competitors/add Add one company the person confirmed they compete with to their rival roster (Memory, Who you're up against), which the next AI visibility scan measures them against and every comp #
GET /api/v1/standup The daily growth standup in one call: receipts (recently shipped moves with what was verified live and the clicks they earned), ready (the top ranked moves that can be shipped now, #
GET /api/v1/moves/{id} One growth move in full. Source move material remains distinct from prepared_result, the durable worker result for this exact move and attempt. A ready result awaits founder review #
POST /api/v1/moves/{id}/accept Accept a move after the founder says yes. Atomically records or reuses one durable preparation attempt. A configured worker starts only when launch_won is true; an unchanged respon #
POST /api/v1/moves/{id}/ship Record explicit founder-confirmed completion. prepared_result confirms the exact reviewed attempt; independent_work records work the founder performed separately, including manual #
POST /api/v1/moves/{id}/review Record an explicit founder review decision: revise, withdraw, adopt an inspected legacy result, or correct a publication record. Revision creates a new attempt only on a won transa #
POST /api/v1/moves/{id}/skip Dismiss a move (the founder's Skip action). Free; scope 'act'. #
POST /api/v1/moves/{id}/draft Replace a pending move's draft body before shipping. Free; scope 'act'. #
POST /api/v1/moves/{id}/archive Archive a move (a lighter dismissal that hides it from the moves list). Free; scope 'act'. #
POST /api/v1/moves/{id}/undo Restore a just-shipped or just-skipped move to pending (the founder's Undo): the self-correction verb for a wrong ship or skip. Free; scope 'act'. Idempotent on a move that is alre #
POST /api/v1/outputs/{id}/status Resolve one drafted output in the review queue: ship (mark done / posted-status WITHOUT publishing to a channel) or skip. Never posts anywhere; real channel egress is approve_outpu #
POST /api/v1/outputs/{id}/redraft Replace a drafted output's body with an edited version before it is resolved. Manual replacement only: the instruction-driven AI redraft stays in the app. The previous draft is kep #
POST /api/v1/scans/{scan}/refresh METERED: run an on-demand AI Visibility scan. Scope 'write'; Founder tier; ONE scan per scan type per product per local day (any repeat that day replays the original result, whatev #
POST /api/v1/feed/replenish METERED: generate a fresh batch of growth moves on demand (the in-app 'Find more moves'). Scope 'write'; Founder tier; once a day per product; per-tenant daily and monthly spend ca #
POST /api/v1/loops/{loopType}/run METERED: administrator-only loop generation once, on demand. Scope 'write'; costs 1 credit per loop per cycle (a repeat run in the same cycle replays at no cost, whatever the key); #
POST /api/v1/outreach/preenrol Administrator only: pre-enrol outreach products. Each row gets an account under the alias outreach-@guest.afterlaunch.io, a snapshot and a paused enrollment, one provisioning #
POST /api/v1/outreach/drafts Administrator only: file the day's outreach email drafts into the review queue the owner sends from. Keyed (host, campaign, kind): a new key is inserted as queued, a queued row is #
GET /api/v1/outreach/drafts Administrator only: one batch date's outreach drafts with their status, sent time, Resend id and any error, oldest first, at most 500. Scope 'read'. #
GET /api/v1/outreach/loops Administrator only: the outreach send loop's and draft loop's switches (the cold-mail transport, the per-mailbox cap and the hand-raisers source's four among them) as the loops rea #
POST /api/v1/outreach/loops Administrator only: set any of the outreach loops' fifteen switches, at least one, within the log strip's own bounds (the hand-raiser four within their own). A value out of bounds #
POST /api/v1/outreach/loops/fire Administrator only: run the outreach send loop or draft loop now. Emits the event the loop's own daily tick emits; the loop's live switch and its day claim gate every spend, so wit #
POST /api/v1/loops/{loopType}/autonomy Administrator-only loop autonomy configuration (review, one_click, autonomous). Scope 'config'. Social channels never accept 'autonomous'. #
POST /api/v1/loops/{loopType}/cadence Slow or pause a loop's cadence for this product. Scope 'config'; administrator access. The bounded enum can only SLOW or pause a loop, never speed it up. 'default' clears the overr #
POST /api/v1/scans/ai_visibility/config Set how AI visibility scans run: which engines to ask and how often. Every paid scan asks the included 50 questions, so the question count is fixed: only 50 is accepted. Scope 'con #
POST /api/v1/scans/ai_visibility/intensity RETIRED, kept live for one release: use POST /api/v1/scans/ai_visibility/config instead. Forwards to the same verb, so an existing integration keeps working. The dial widened from #
POST /api/v1/billing/checkout Mint a Founder-tier checkout link for this product run. Returns a checkout_url the founder opens to pay; once payment succeeds the Dodo webhook upgrades THIS enrollment to Founder #
POST /api/v1/billing/topup Mint a credit top-up checkout link for this product run. Pass pack_id ('small', 'medium' or 'large'). Returns a checkout_url the founder opens to pay; once payment succeeds the Dod #
POST /api/v1/outputs/{id}/approve METERED: approve one drafted output for dispatch (the in-app Post now). Scope 'egress'; Founder tier; credits metered with the output id as the idempotency ref; at-most-once egress #
GET /api/v1/connections Which growth data sources and distribution channels are connected to this product run: Google Analytics 4, Search Console, Google Business Profile, and the LinkedIn / Reddit postin #
POST /api/v1/connections/start Mint a one-time link the FOUNDER opens in their own browser to connect a source. A machine credential never completes third-party OAuth consent, so this returns url (hand it over), #
POST /api/v1/connections/poll Did the founder finish the connection you handed them a link for? POST rather than GET because the poll token is a credential and a credential in a query string lands in logs. Answ #
POST /api/v1/connections/disconnect Disconnect a source and delete the credential AfterLaunch stored for it (for Google it is also revoked at Google). Scope 'config', not act: destroying a credential is an account se #
GET /api/v1/visibility/prompts The customer questions this product run is measured on across the AI answer engines. Returns prompts (in tracking order), cap (the hard limit of 15), used, and curated (false means #
POST /api/v1/visibility/prompts/track Add one customer question to the tracked AI visibility set. Scope 'config'; Founder tier; free and unmetered (no LLM call, no paid API, no email). Two consequences worth stating: t #
POST /api/v1/visibility/prompts/untrack Remove one customer question from the tracked AI visibility set, freeing a slot against the included cap of 50. POST rather than DELETE because the question is identified by its te #
GET /api/v1/scoreboard The period proof: AI visibility measured at the start and the end of the window (before / after), the moves shipped between them, the scans that ran, prepared_count (site moves pre #
POST /api/v1/visibility/gaps/dismiss Set aside one AI visibility gap the founder is not going to act on. Resolves it in ONE place: it stops being re-proposed on the AI Visibility surface AND on the Feed, and the reaso #
POST /api/v1/memory/insights/record Write one durable insight the founder just revealed (a standing preference, a rule about how they write, a fact about the product, or what a shipped move earned) into this product #
POST /api/v1/moves/propose Put a thread the caller found, or a move the owner's plan calls for, onto this product run's moves list as a pending move. Scope 'act'; free, unmetered, every plan. Nothing is gene #
GET /api/v1/queue Unclaimed preparation jobs, oldest first. Each summary carries job_id and its nullable attempt_id but never prepared_content. Scope 'act'; free. #
POST /api/v1/queue/{id}/pick-up Try to claim one exact queued attempt. Start work only when the response carries claim_won:true. A changed:false result is a non-winner even when another session uses the same cred #
POST /api/v1/queue/{id}/report Report one claimed attempt as running, needs_input, ready_for_review, shipped or failed, with its exact attempt_id. needs_input requires question (one question for the user, at mos #
POST /api/v1/onboard/start Agent-initiated onboarding, step 1: submit the founder's site URL + email. The founder gets ONE approval email; nothing runs (and nothing is spent) until they click it. No authenti #
POST /api/v1/onboard/poll Agent-initiated onboarding, step 3: exchange the device code for the minted API key, which carries the read, act and write scopes. Delivered exactly once; store it immediately. Pol #
POST /api/v1/starter/redeem Redeem a one-time starter token for an API key carrying the read and act scopes. The founder copied the starter from /app/connect and pasted it into this session; the key is MINTED #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/afterlaunch-io:afterlaunch-public-api-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

afterlaunch-io-api-openapi.json Raw ↑
{"openapi":"3.1.0","info":{"title":"AfterLaunch public API","version":"1.0.0","description":"AfterLaunch is the agentic growth engine: continuous measurement of how a product gets found, kept as a record and ranked into briefed growth moves. This API is access to one AfterLaunch product run: read the ranked Feed of prepared growth moves, the automated loops and their autonomy, and drafted outputs; act on moves (ship, skip, edit the draft) and approve a drafted output for dispatch. Administrators may run loops on demand and configure loop autonomy, cadence and scans. Authenticate every request with \"Authorization: Bearer al_...\" (an API key bound to one enrollment). Reads need the read scope; acting on a move and minting a checkout need act; the metered pulls need write; administrator configuration changes need config; tracked questions retain their existing config scope and customer entitlement. A key carrying write satisfies act. Metered actions (run, approve) honour per-tenant daily and monthly spend caps and, for runs, a REQUIRED Idempotency-Key header: reusing a key replays the original result instead of spending again."},"servers":[{"url":"https://afterlaunch.io"}],"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"AfterLaunch API key, format al_<48 hex chars>."}}},"security":[{"apiKey":[]}],"paths":{"/api/v1/content/calendar":{"get":{"operationId":"getContentCalendar","summary":"Read this product run's calendar, up to 100 saved source drafts and their current canonical Feed state. Requires read. Honour queue_position and needs_data. channel_state is a stored projection; check calendar_sync and linkage before acting. x_production_enabled is the existing twitter_thread switch. Read coverage before planning: unknown or incomplete history is not an empty review pile. No generation or spend.","responses":{"200":{"description":"Up to 200 calendar pieces, ordered by queue position then recency, and up to 100 saved records ordered by source output update time.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","required":["enrollment_id","timezone","calendar","drafts","x_production_enabled","can_record_drafts","coverage"],"properties":{"enrollment_id":{"type":"string","format":"uuid"},"timezone":{"type":"string"},"calendar":{"type":"array","maxItems":200,"items":{"type":"object","required":["id","title","angle","kind","channels","channel_state","evidence","status","queue_position","link","notes","needs_data","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"angle":{"type":["string","null"]},"kind":{"type":"string","enum":["opinion","educational","amplify"]},"channels":{"type":"array","items":{"type":"string"}},"channel_state":{"type":"object","description":"Stored per-channel projection, including status and output_id when present. For linked content, the canonical move and calendar_sync expose lifecycle drift. Other channels are preserved."},"evidence":{"description":"The existing JSON evidence for this piece, preserved without rewriting."},"status":{"type":"string","enum":["pool","queued","done","dismissed"]},"queue_position":{"type":["integer","null"]},"link":{"type":["string","null"]},"notes":{"type":["string","null"]},"needs_data":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"}}}},"coverage":{"type":"object","required":["ideas","drafts"],"properties":{"ideas":{"type":"object","required":["returned","total","complete"],"properties":{"returned":{"type":"integer","minimum":0},"total":{"type":["integer","null"],"minimum":0},"complete":{"type":"boolean","description":"Exact response count equals all returned, decoded records. False for unknown counts or truncation. This is a point-in-time read, not a reservation."}}},"drafts":{"type":"object","required":["returned","total","complete"],"properties":{"returned":{"type":"integer","minimum":0},"total":{"type":["integer","null"],"minimum":0},"complete":{"type":"boolean","description":"Exact response count equals all returned, decoded records. False for unknown counts or truncation. This is a point-in-time read, not a reservation."}}}}},"drafts":{"type":"array","maxItems":100,"items":{"type":"object","required":["output_id","idea_id","run_key","channel","title","source_url","why","claim_ids","claims","output_status","status","source_draft","move","prepared_result","legacy_publication_report","drafts_withheld","drafts_match","linkage","linkage_reason","calendar_sync","updated_at"],"properties":{"output_id":{"type":"string","format":"uuid"},"idea_id":{"type":"string","format":"uuid"},"run_key":{"type":"string"},"channel":{"const":"x"},"title":{"type":"string"},"source_url":{"type":["string","null"],"description":"Source evidence; also the exact destination for a classified legacy reply."},"why":{"type":"string"},"claim_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"claims":{"type":"array","items":{"type":"object","required":["id","text","number","caveat"],"properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"number":{"type":"string"},"caveat":{"type":"string"}}}},"output_status":{"type":"string","description":"Raw source output status. posted alone proves neither founder confirmation nor published words."},"status":{"type":"string","description":"Current canonical move status when linked, otherwise raw output status. Prepared attempt content never implies shipped."},"source_draft":{"type":["object","null"],"properties":{"storage":{"enum":["candidate","output"]},"candidate_id":{"type":["string","null"]},"text":{"type":"string"}},"description":"Current source candidate, or output fallback. Null when drafts are withheld. Not published words."},"move":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"identity_key":{"type":["string","null"]},"source_key":{"type":"string"},"source_output_id":{"type":"string","format":"uuid"},"action_kind":{"type":["string","null"]},"target":{"type":["string","null"]},"status":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"draft_content":{"type":["string","null"]},"draft_kind":{"type":["string","null"],"enum":["full_draft","brief",null]}},"description":"Ownership-bound Feed read, carrying the actual move id. A collection app_url does not open the move in focus."},"prepared_result":{"type":["object","null"],"description":"Current attempt from readPreparedMoveResult, including job/attempt identity, state, prepared_content and recorded published_url. Preparation is not publication; prepared_content is not reported final copy. Private material is withheld at the shared tier boundary."},"legacy_publication_report":{"type":["object","null"],"properties":{"basis":{"const":"human_reported"},"text":{"type":"string"},"url":{"type":"string"}},"description":"Only the old pilot explicit human_reported confirmation and its stored words/link. Never reconstructed from output status or a draft."},"drafts_withheld":{"type":"boolean"},"drafts_match":{"type":["boolean","null"]},"linkage":{"enum":["linked","incomplete","legacy_terminal"]},"linkage_reason":{"type":["string","null"]},"calendar_sync":{"enum":["linked","missing","different_output","lifecycle_differs"],"description":"Comparison with stored channel_state. The move lifecycle is authoritative; this reader does not silently repair completion projections."},"updated_at":{"type":"string","format":"date-time","description":"Source output update time. The move and attempt carry their own timestamps."}}}},"x_production_enabled":{"type":"boolean"},"can_record_drafts":{"type":"boolean","description":"True when access permits filing and X production is disabled or has not been configured. The first draft write can create disabled storage; this read creates nothing. This is not an OAuth connection check."}}},"meta":{"type":"object","properties":{"enrollment_id":{"type":"string","format":"uuid"},"generated_at":{"type":"string","format":"date-time"}},"required":["enrollment_id","generated_at"]}},"required":["data","meta"]}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the scope this endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The key is not bound to the current product run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"recordContent","summary":"File a research idea, original X post or append-only research outcome. Research records preserve the original question, evidence, hold and lifecycle. The caller supplies the conclusion; it is not automatic verification or permission to draft. Same-key replays preserve receipts; changed results or stale new writes conflict. Research is visible in Memory at /app/knowledge-base?open=research (requires act and current draft access). No generation, spend, posting or lifecycle decision. Stable run_key fingerprints preserve the original request. New drafts require action_kind social_post, one existing idea_id or source_idea, and disabled product X production. If no X loop exists, the first authorised draft write creates a disabled storage row without activating generation or posting. Canonical claim caveats travel with the draft. Filing creates an output, materialises a candidate only if absent, and claims a pending Feed move under the output UUID. Replays preserve edits and terminal history. New drafts are original posts. Explicit adopt_legacy recovers exact saved requests as original posts or replies with their saved X status destination. It refuses changed or unclassified actioned work and retains candidate edits. Review and completion are not part of this endpoint.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["operation","run_key","source_idea"],"properties":{"operation":{"const":"idea"},"run_key":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-zA-Z0-9:_./-]+$"},"source_idea":{"type":"object","additionalProperties":false,"required":["title","needs_data"],"properties":{"title":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"angle":{"type":"string","minLength":1,"maxLength":3000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"link":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"An HTTP or HTTPS address without embedded credentials."},"needs_data":{"type":"boolean","description":"True when this idea needs evidence before publication copy can be prepared."},"channels":{"type":"array","minItems":1,"maxItems":4,"uniqueItems":true,"items":{"enum":["x","linkedin","article","seo"]},"description":"Where the piece should go out. Omitted means x."},"kind":{"enum":["opinion","educational","amplify"],"description":"Omitted means the calendar default, educational."},"score":{"type":"number","minimum":0,"maximum":10,"description":"Priority from 0 to 10, at most two decimals. Omitted means unscored."},"final_text":{"type":"object","additionalProperties":false,"required":["channel","text"],"properties":{"channel":{"enum":["x","linkedin"],"description":"One of this idea's channels."},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"^[^\\u2014]*\\S[^\\u2014]*$","description":"The finished post, kept byte for byte (not trimmed). Em dashes are rejected."}},"description":"A post the owner has already finished. The morning slot delivers it exactly as written, with no rewrite. Text AfterLaunch would alter or block is refused."},"desk":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"enum":["post","reply","task","decision","read"],"description":"A post or reply needs final_text and takes its channel from it. A task has no channel; its words are the title. A decision or a read is a thing that needs the owner and is not a post: its words are the title, it takes no slot, and it stays open until ticked."},"slot_at":{"type":"string","format":"date-time","description":"The slot to file it into. Present means the item is filed queued; omitted means the backlog."},"image":{"type":"string","minLength":1,"maxLength":2000,"description":"One line: which image to attach, or its address."},"first_reply":{"type":"string","minLength":1,"maxLength":2000,"description":"X: the reply that carries the link, posted under the original."},"target_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"A reply: the thread it answers. A decision or a read: the link it opens."},"due_on":{"type":"string","format":"date","description":"A decision or a read only: the local date it is due on. Omitted means undated."},"why":{"type":"string","minLength":1,"maxLength":200,"description":"A decision or a read only: one line on why it matters."},"step":{"type":"string","minLength":1,"maxLength":200,"description":"A decision or a read only: the step that is the owner's."},"source":{"type":"string","minLength":1,"maxLength":80,"description":"A decision or a read only: who raised it."},"note":{"type":"string","minLength":1,"maxLength":200,"description":"Any kind: one line the owner reads before posting, a \"Your call: ...\" included. Shown under the row on the desk."},"venue":{"enum":["hn","producthunt","reddit","email"],"description":"A task or a decision only: where it goes out when it is not X or LinkedIn. It picks the row's mark; a task or a decision without one is not drawn on the desk."}},"description":"An item for the Posting Desk, which the owner posts by hand. A desk item is kept out of the morning slot, so it goes out once."},"series":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Matched among running series, case and spacing aside."},"kind":{"enum":["major_drop","minor_drop","posts","free_tool","launch","announcement","experiment_writeup"],"description":"Used only when the series is created. Omitted means posts."},"starts_on":{"type":"string","format":"date","description":"Used only when created. Omitted means the desk slot's date, else today."},"ends_on":{"type":"string","format":"date","description":"Used only when created. Omitted means open-ended."},"why":{"type":"string","minLength":1,"maxLength":200,"description":"One line on why the series exists. Required to create one."}},"description":"The run of items this one belongs to (a drop, a run of posts). Matched by name, else created; the idea is filed into it."}}}}},{"type":"object","additionalProperties":false,"required":["operation","run_key","title","text","channel","why"],"properties":{"operation":{"const":"draft"},"run_key":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-zA-Z0-9:_./-]+$"},"idea_id":{"type":"string","format":"uuid"},"source_idea":{"type":"object","additionalProperties":false,"required":["title","needs_data"],"properties":{"title":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"angle":{"type":"string","minLength":1,"maxLength":3000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"link":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"An HTTP or HTTPS address without embedded credentials."},"needs_data":{"type":"boolean","description":"True when this idea needs evidence before publication copy can be prepared."}}},"title":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"text":{"type":"string","minLength":1,"maxLength":20000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"channel":{"const":"x"},"action_kind":{"const":"social_post","description":"Required for new drafts. Omission is accepted only to recognise an unchanged old-pilot replay; unclassified legacy pending records are not adopted as posts."},"source_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"Source evidence for new original posts. An explicit legacy reply adoption retains this exact X status URL as its destination."},"adopt_legacy":{"type":"object","additionalProperties":false,"required":["output_id","action_kind"],"properties":{"output_id":{"type":"string","format":"uuid"},"action_kind":{"enum":["social_post","community_reply"]}},"description":"Explicit recovery of an existing exact replay, never new content. Omit top-level action_kind and restate the original request. Classification preserves output, idea, original words and any existing move."},"why":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"claim_ids":{"type":"array","maxItems":20,"items":{"type":"string","format":"uuid"},"default":[],"description":"Canonical usable claim IDs from get_claims. Missing, held or malformed claims are refused. Their stored caveats travel with the draft."}},"oneOf":[{"required":["idea_id"],"not":{"required":["source_idea"]}},{"required":["source_idea"],"not":{"required":["idea_id"]}}]},{"type":"object","additionalProperties":false,"required":["operation","run_key","idea_id","expected_updated_at","research"],"properties":{"operation":{"const":"research"},"run_key":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-zA-Z0-9:_./-]+$"},"idea_id":{"type":"string","format":"uuid"},"expected_updated_at":{"type":"string","format":"date-time","description":"Pass the calendar value unchanged for a new result; exact replays do not need a fresh timestamp."},"research":{"type":"object","additionalProperties":false,"required":["outcome","summary","method","limitations","sources"],"properties":{"outcome":{"type":"string","enum":["supported","not_supported","inconclusive"]},"summary":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"method":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"limitations":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"sources":{"type":"array","maxItems":50,"items":{"type":"object","additionalProperties":false,"required":["title","url","observed_at"],"properties":{"title":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"An HTTP or HTTPS address without embedded credentials."},"observed_at":{"type":"string","format":"date-time"}}}}}}}}]}}}},"responses":{"200":{"description":"The current linked record or idea receipt. A legacy terminal replay returns its history without inventing a pending move. No receipt implies posting or focused browser visibility.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"oneOf":[{"type":"object","required":["output_id","idea_id","run_key","channel","title","source_url","why","claim_ids","claims","output_status","status","source_draft","move","prepared_result","legacy_publication_report","drafts_withheld","drafts_match","linkage","linkage_reason","calendar_sync","updated_at"],"properties":{"output_id":{"type":"string","format":"uuid"},"idea_id":{"type":"string","format":"uuid"},"run_key":{"type":"string"},"channel":{"const":"x"},"title":{"type":"string"},"source_url":{"type":["string","null"],"description":"Source evidence; also the exact destination for a classified legacy reply."},"why":{"type":"string"},"claim_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"claims":{"type":"array","items":{"type":"object","required":["id","text","number","caveat"],"properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"number":{"type":"string"},"caveat":{"type":"string"}}}},"output_status":{"type":"string","description":"Raw source output status. posted alone proves neither founder confirmation nor published words."},"status":{"type":"string","description":"Current canonical move status when linked, otherwise raw output status. Prepared attempt content never implies shipped."},"source_draft":{"type":["object","null"],"properties":{"storage":{"enum":["candidate","output"]},"candidate_id":{"type":["string","null"]},"text":{"type":"string"}},"description":"Current source candidate, or output fallback. Null when drafts are withheld. Not published words."},"move":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"identity_key":{"type":["string","null"]},"source_key":{"type":"string"},"source_output_id":{"type":"string","format":"uuid"},"action_kind":{"type":["string","null"]},"target":{"type":["string","null"]},"status":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"draft_content":{"type":["string","null"]},"draft_kind":{"type":["string","null"],"enum":["full_draft","brief",null]}},"description":"Ownership-bound Feed read, carrying the actual move id. A collection app_url does not open the move in focus."},"prepared_result":{"type":["object","null"],"description":"Current attempt from readPreparedMoveResult, including job/attempt identity, state, prepared_content and recorded published_url. Preparation is not publication; prepared_content is not reported final copy. Private material is withheld at the shared tier boundary."},"legacy_publication_report":{"type":["object","null"],"properties":{"basis":{"const":"human_reported"},"text":{"type":"string"},"url":{"type":"string"}},"description":"Only the old pilot explicit human_reported confirmation and its stored words/link. Never reconstructed from output status or a draft."},"drafts_withheld":{"type":"boolean"},"drafts_match":{"type":["boolean","null"]},"linkage":{"enum":["linked","incomplete","legacy_terminal"]},"linkage_reason":{"type":["string","null"]},"calendar_sync":{"enum":["linked","missing","different_output","lifecycle_differs"],"description":"Comparison with stored channel_state. The move lifecycle is authoritative; this reader does not silently repair completion projections."},"updated_at":{"type":"string","format":"date-time","description":"Source output update time. The move and attempt carry their own timestamps."}}},{"type":"object","required":["idea_id","title","angle","link","needs_data","channels","kind","score","status"],"properties":{"idea_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"angle":{"type":["string","null"]},"link":{"type":["string","null"]},"needs_data":{"type":"boolean"},"channels":{"type":"array","items":{"type":"string"}},"kind":{"type":"string","enum":["opinion","educational","amplify"]},"score":{"type":["number","null"]},"status":{"type":"string","enum":["pool","queued","done","dismissed"],"description":"A new idea is pool. An idempotent replay returns its current state without reopening it."}}},{"type":"object","required":["idea_id","research","needs_data","updated_at","app_url"],"properties":{"idea_id":{"type":"string","format":"uuid"},"research":{"type":"object","additionalProperties":false,"required":["outcome","summary","method","limitations","sources","run_key","request_hash","recorded_at"],"properties":{"outcome":{"type":"string","enum":["supported","not_supported","inconclusive"]},"summary":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"method":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"limitations":{"type":"string","minLength":1,"maxLength":5000,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"sources":{"type":"array","maxItems":50,"items":{"type":"object","additionalProperties":false,"required":["title","url","observed_at"],"properties":{"title":{"type":"string","minLength":1,"maxLength":300,"pattern":"^(?!\\s*$)[^\\u2014]*$","description":"Non-empty prose after trimming. Em dashes are rejected."},"url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^https?://","description":"An HTTP or HTTPS address without embedded credentials."},"observed_at":{"type":"string","format":"date-time"}}}},"run_key":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-zA-Z0-9:_./-]+$"},"request_hash":{"type":"string","pattern":"^[a-f0-9]{64}$"},"recorded_at":{"type":"string","format":"date-time"}}},"needs_data":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"},"app_url":{"type":"string"}}}]},"meta":{"type":"object","properties":{"enrollment_id":{"type":"string","format":"uuid"},"generated_at":{"type":"string","format":"date-time"}},"required":["enrollment_id","generated_at"]}},"required":["data","meta"]}}}},"400":{"description":"Invalid operation, unknown field, value outside its bounds, wrong idea selection, or an absent, held or malformed canonical claim.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the scope this endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The key is not bound to the current product run, or the requested idea/output does not belong to it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Changed replay, ineligible idea, enabled product X production, or incomplete candidate/move/calendar linkage. When already saved, the error includes retained_output_id. Read the calendar before retrying the same run_key; no retained output is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The X production state could not be confirmed. Missing storage is created disabled only by an authorised draft write. Idea and research recording do not require that storage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/content/experiments":{"get":{"operationId":"getExperiments","summary":"Read the growth content experiments already run for this product run, newest first by ran_at. Requires read. Each result is one observation with its stated scope, not a rate. Read-only: this never runs, claims or pays for an experiment. available is false while the experiment records cannot be read yet, and the empty list then means unread, not none.","parameters":[{"name":"idea_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only experiments run for this calendar idea."},{"name":"experiment_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only this experiment."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"Up to limit experiments for the bound product run.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","required":["available","experiments"],"properties":{"available":{"type":"boolean"},"experiments":{"type":"array","maxItems":50,"items":{"type":"object","required":["id","idea_id","run_id","kind","question","method","prediction","evidence","claim_ids","figure","run_date","spend_usd","ran_at"],"properties":{"id":{"type":"string","format":"uuid"},"idea_id":{"type":["string","null"],"format":"uuid"},"run_id":{"type":["string","null"],"format":"uuid"},"kind":{"type":"string","enum":["claim_lookup","engine_probe","corpus_query"]},"question":{"type":"string"},"method":{"type":["string","null"]},"prediction":{"type":["string","null"]},"evidence":{"type":["string","null"],"description":"The stored evidence prose; every line carries its scope."},"claim_ids":{"type":"array","items":{"type":"string"}},"figure":{"description":"The stored figure specification, or null when the result was not numeric."},"run_date":{"type":["string","null"],"description":"The run local date the result was stored under."},"spend_usd":{"type":"number","minimum":0},"ran_at":{"type":"string","format":"date-time"}}}}}},"meta":{"type":"object","properties":{"enrollment_id":{"type":"string","format":"uuid"},"generated_at":{"type":"string","format":"date-time"}},"required":["enrollment_id","generated_at"]}},"required":["data","meta"]}}}},"400":{"description":"An idea_id or experiment_id that is not a UUID, or a limit outside 1 to 50.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the scope this endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The key is not bound to the current product run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/whoami":{"get":{"operationId":"getWhoami","summary":"Confirm the key works and see what it is bound to, in one call: enrollment_id, key_name, key_prefix and scopes, the product_url this run is about, tier and access_state, trial_ends_at, and onboarding { snapshot_status, current_phase, feed_ready, scanned, message } (feed_ready is false while the Growth Snapshot deep scan is still building). credits is present only when metering is live. The same payload the whoami MCP tool returns, without its text screen. Read-only, zero spend.","responses":{"200":{"description":"What this key is bound to.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"onboarding":{"type":"object","properties":{"snapshot_status":{"type":"string","enum":["building","ready"],"description":"'building' while the deep scan is still running, 'ready' once it has finished."},"current_phase":{"type":["string","null"],"description":"A short honest label of what the deep scan is doing right now while building; null when ready."},"feed_ready":{"type":"boolean","description":"True once the deep scan (Stage B) has finished and the moves list holds the stronger stage-B moves."}}}}},"meta":{"type":"object","properties":{"enrollment_id":{"type":"string","format":"uuid"},"generated_at":{"type":"string","format":"date-time"}},"required":["enrollment_id","generated_at"]}},"required":["data","meta"]}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the scope this endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/feed":{"get":{"operationId":"listFeed","summary":"The ranked backlog of prepared growth moves. Live (pending) moves only by default; resolved moves (shipped, skipped, archived, expir

# --- truncated at 32 KB (197 KB total) ---
# Full source: https://artifacts.apis.io/afterlaunch-io/openapi/_original/afterlaunch-io-api-openapi.json