---
title: "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."
method: POST
path: "/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 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.

`POST /api/v1/content/calendar`

## Request

- Base URL: `https://afterlaunch.io`
- URL: `https://afterlaunch.io/api/v1/content/calendar`
- Auth: HTTP bearer

## Request body

- union
  - object
    - `operation` 'idea', required
    - `run_key` string, required
    - `source_idea` object, required
      - `title` string, required: Non-empty prose after trimming. Em dashes are rejected.
      - `angle` string: Non-empty prose after trimming. Em dashes are rejected.
      - `link` string, uri: An HTTP or HTTPS address without embedded credentials.
      - `needs_data` boolean, required: True when this idea needs evidence before publication copy can be prepared.
      - `channels` string[]: Where the piece should go out. Omitted means x.
      - `kind` 'opinion' | 'educational' | 'amplify': Omitted means the calendar default, educational.
      - `score` number: Priority from 0 to 10, at most two decimals. Omitted means unscored.
      - `final_text` object: 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.
        - `channel` 'x' | 'linkedin', required: One of this idea's channels.
        - `text` string, required: The finished post, kept byte for byte (not trimmed). Em dashes are rejected.
      - `desk` object: 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.
        - `kind` 'post' | 'reply' | 'task' | 'decision' | 'read', required: 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` string, date-time: The slot to file it into. Present means the item is filed queued; omitted means the backlog.
        - `image` string: One line: which image to attach, or its address.
        - `first_reply` string: X: the reply that carries the link, posted under the original.
        - `target_url` string, uri: A reply: the thread it answers. A decision or a read: the link it opens.
        - `due_on` string, date: A decision or a read only: the local date it is due on. Omitted means undated.
        - `why` string: A decision or a read only: one line on why it matters.
        - `step` string: A decision or a read only: the step that is the owner's.
        - `source` string: A decision or a read only: who raised it.
        - `note` string: Any kind: one line the owner reads before posting, a "Your call: ..." included. Shown under the row on the desk.
        - `venue` 'hn' | 'producthunt' | 'reddit' | 'email': 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.
      - `series` object: 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.
        - `name` string, required: Matched among running series, case and spacing aside.
        - `kind` 'major_drop' | 'minor_drop' | 'posts' | 'free_tool' | 'launch' | 'announcement' | 'experiment_writeup': Used only when the series is created. Omitted means posts.
        - `starts_on` string, date: Used only when created. Omitted means the desk slot's date, else today.
        - `ends_on` string, date: Used only when created. Omitted means open-ended.
        - `why` string: One line on why the series exists. Required to create one.
  - object
    - `operation` 'research', required
    - `run_key` string, required
    - `idea_id` string, uuid, required
    - `expected_updated_at` string, date-time, required: Pass the calendar value unchanged for a new result; exact replays do not need a fresh timestamp.
    - `research` object, required
      - `outcome` 'supported' | 'not_supported' | 'inconclusive', required
      - `summary` string, required: Non-empty prose after trimming. Em dashes are rejected.
      - `method` string, required: Non-empty prose after trimming. Em dashes are rejected.
      - `limitations` string, required: Non-empty prose after trimming. Em dashes are rejected.
      - `sources` object[], required
        - `title` string, required: Non-empty prose after trimming. Em dashes are rejected.
        - `url` string, uri, required: An HTTP or HTTPS address without embedded credentials.
        - `observed_at` string, date-time, required

## Response `200`

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.

- object
  - `data` union, required
    - object
      - `output_id` string, uuid, required
      - `idea_id` string, uuid, required
      - `run_key` string, required
      - `channel` 'x', required
      - `title` string, required
      - `source_url` string, nullable, required: Source evidence; also the exact destination for a classified legacy reply.
      - `why` string, required
      - `claim_ids` string[], required
      - `claims` object[], required
        - `id` string, uuid, required
        - `text` string, required
        - `number` string, required
        - `caveat` string, required
      - `output_status` string, required: Raw source output status. posted alone proves neither founder confirmation nor published words.
      - `status` string, required: Current canonical move status when linked, otherwise raw output status. Prepared attempt content never implies shipped.
      - `source_draft` object, nullable, required: Current source candidate, or output fallback. Null when drafts are withheld. Not published words.
        - `storage` 'candidate' | 'output'
        - `candidate_id` string, nullable
        - `text` string
      - `move` object, nullable, required: Ownership-bound Feed read, carrying the actual move id. A collection app_url does not open the move in focus.
        - `id` string, uuid
        - `identity_key` string, nullable
        - `source_key` string
        - `source_output_id` string, uuid
        - `action_kind` string, nullable
        - `target` string, nullable
        - `status` string
        - `updated_at` string, date-time
        - `draft_content` string, nullable
        - `draft_kind` 'full_draft' | 'brief' | 'null', nullable
      - `prepared_result` object, nullable, required: 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` object, nullable, required: Only the old pilot explicit human_reported confirmation and its stored words/link. Never reconstructed from output status or a draft.
        - `basis` 'human_reported'
        - `text` string
        - `url` string
      - `drafts_withheld` boolean, required
      - `drafts_match` boolean, nullable, required
      - `linkage` 'linked' | 'incomplete' | 'legacy_terminal', required
      - `linkage_reason` string, nullable, required
      - `calendar_sync` 'linked' | 'missing' | 'different_output' | 'lifecycle_differs', required: Comparison with stored channel_state. The move lifecycle is authoritative; this reader does not silently repair completion projections.
      - `updated_at` string, date-time, required: Source output update time. The move and attempt carry their own timestamps.
    - object
      - `idea_id` string, uuid, required
      - `title` string, required
      - `angle` string, nullable, required
      - `link` string, nullable, required
      - `needs_data` boolean, required
      - `channels` string[], required
      - `kind` 'opinion' | 'educational' | 'amplify', required
      - `score` number, nullable, required
      - `status` 'pool' | 'queued' | 'done' | 'dismissed', required: A new idea is pool. An idempotent replay returns its current state without reopening it.
    - object
      - `idea_id` string, uuid, required
      - `research` object, required
        - `outcome` 'supported' | 'not_supported' | 'inconclusive', required
        - `summary` string, required: Non-empty prose after trimming. Em dashes are rejected.
        - `method` string, required: Non-empty prose after trimming. Em dashes are rejected.
        - `limitations` string, required: Non-empty prose after trimming. Em dashes are rejected.
        - `sources` object[], required
          - `title` string, required: Non-empty prose after trimming. Em dashes are rejected.
          - `url` string, uri, required: An HTTP or HTTPS address without embedded credentials.
          - `observed_at` string, date-time, required
        - `run_key` string, required
        - `request_hash` string, required
        - `recorded_at` string, date-time, required
      - `needs_data` boolean, required
      - `updated_at` string, date-time, required
      - `app_url` string, required
  - `meta` object, required
    - `enrollment_id` string, uuid, required
    - `generated_at` string, date-time, required

## Other responses

- `400`: Invalid operation, unknown field, value outside its bounds, wrong idea selection, or an absent, held or malformed canonical claim.
- `401`: Missing, invalid or revoked API key.
- `403`: The key does not carry the scope this endpoint requires.
- `404`: The key is not bound to the current product run, or the requested idea/output does not belong to it.
- `409`: 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.
- `429`: Per-key rate limit exceeded. Honour Retry-After.
- `503`: 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.

---

[API](https://skmtc.dev/afterlaunch/apis/afterlaunch-public-api.md) · [All operations](https://skmtc.dev/afterlaunch/apis/afterlaunch-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/afterlaunch/apis/afterlaunch-public-api/revisions/d67a599194a4?raw)
