---
title: "Stage a draft reply on a creator conversation"
method: POST
path: "/creator-messages/draft"
tags: ["Creator Messages"]
---

# Stage a draft reply on a creator conversation

`POST /creator-messages/draft`

Persist a reply on the conversation WITHOUT delivering it to the creator. The draft surfaces in the Reacher inbox for a human to review and either send via the existing /reply* endpoints or discard via DELETE /drafts/{id}. Idempotency-Key required. X-Dry-Run: true validates and echoes without persisting.

## Headers

- `Idempotency-Key` string, nullable
- `X-Dry-Run` string, nullable

## Request body

- CreateDraftRequest — Body for ``POST /creator-messages/draft``. Exactly one of `text`, `image_url`, `product_id` must be set — matches the three live-send endpoints. Validated below in `_resolve_draft_content` so a malformed body is rejected at the route layer with a single structured 400 (rather than 5 separate model-level rules whose error messages would be harder for an agent to act on). `creator_handle` is in the BODY (not the path) for the create endpoint — keeps the path stable as ``/creator-messages/draft`` regardless of content type. Mirrors how the existing /reply* paths are creator-scoped via the URL while the create-draft surface is deliberately content-shape agnostic.
  - `creator_handle` string, required — The creator's TikTok handle (with or without leading @). Must already have an open conversation with this shop — drafts can only be staged on existing conversations, the same prerequisite TikTok enforces on the live-send path.
  - `text` string, nullable — Plain-text reply body. Set this OR `image_url` OR `product_id` (exactly one). `message` is accepted as an alias: it is what an assistant reaches for, and with extra=forbid the mismatch was a schema rejection before the clearer route-level validation below could run (CORE-6957).
  - `image_url` string, nullable — HTTPS URL of an image to send. Same SSRF + size + type constraints as the live /reply/image endpoint will apply when the human ships the draft — we do NOT fetch the URL at draft time (no outbound network call from this endpoint). Set this OR `text` OR `product_id` (exactly one).
  - `product_id` string, nullable — A TikTok product_id from THIS shop's catalog. Ownership is verified ONLY when the human clicks Send in the inbox — the draft endpoint does not pre-check the catalog (the catalog can change between draft and send anyway). Set this OR `text` OR `image_url` (exactly one).

## Response `200`

Dry-run — validated and echoed, nothing persisted.

- CreateDraftDryRunResponse — Shape for ``X-Dry-Run: true`` on create. Echoes what would be persisted without actually inserting (and without resolving the conversation either — pure validation echo, same posture as the live-send dry-run paths).
  - `dry_run` boolean
  - `would_create_for_handle` string, required
  - `content_type` string, required
  - `text` string, nullable
  - `image_url` string, nullable
  - `product_id` string, nullable

## Other responses

- `201` — Draft staged on the conversation.
- `400` — Invalid body (multiple content fields set, none set, etc.).
- `403` — API key lacks `read_write` scope.
- `404` — No existing conversation with this creator for the shop.
- `422` — Validation Error
- `429` — Write rate limit exceeded.

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) · [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/reacherapp/reacher-data-api/revisions/d4fe3e380424/schema)
