---
title: "Create a draft"
method: POST
path: "/api/v1/create_post_from_text"
tags: ["write"]
---

# Create a draft

`POST /api/v1/create_post_from_text`

## Request

- Base URL: `https://awraiter.ai`
- URL: `https://awraiter.ai/api/v1/create_post_from_text`
- Auth: HTTP bearer

Save an EXACT, ready-to-publish post as a draft from text you (the assistant) wrote yourself. This is the ONLY way to create a post: the server never generates copy and spends no AI credits — the body is stored verbatim, in whatever language you wrote it (only deterministic typography cleanup is applied unless deslop:false). It guarantees the language and wording you intended. The result is a DRAFT only — nothing is published until schedule_post is confirmed, so the operator stays in full control. Call it once, for text the operator has already approved: iterate on wording with preview_post(text) first. Each call creates a NEW draft that shows up in the team's review queue, and saved drafts cannot be edited here, so saving every revision floods the queue with near-identical drafts. To attach media, pass public direct-download links in imageUrls / videoUrls / audioUrls: the server fetches them and publishes the post with the media attached. If any link fails to download, NO draft is created — fix the link and retry. Note: a post with media is a caption (1024-char limit, checked by preview_post). Tracks (audioUrls) go out as music and cannot share a post with photos or videos; they need rightsConfirmed:true, which you may set only after the operator confirmed they hold the rights to publish those tracks.

## Headers

- `Idempotency-Key` string

## Request body

- CreatePostFromTextInput
  - `channelId` number, required
  - `body` string, required: Post text in Markdown as Telegram renders it: **bold**, *italic*, __underline__, ~~strikethrough~~, ||spoiler||, [text](https://link), `code`. A quote is one or more lines starting with "> "; it ends at the first line without ">", and goes out as a plain quote. Separate paragraphs with a blank line. Premium emoji: <tg-emoji id="ID">🔥</tg-emoji>, with an ID from the channel's emoji sets (get_channel → customEmoji); the emoji inside the tag must be the base emoji of that ID and is shown where premium emoji cannot render.
  - `title` string
  - `deslop` boolean: Strip AI-tell typography (em dashes -> hyphen, smart quotes). Default true. No AI is used either way.
  - `imageUrls` string[]: Public http(s) direct links to images to attach (jpg/png/webp; svg/gif rejected).
  - `videoUrls` string[]: Public http(s) direct links to videos to attach (mp4 preferred).
  - `audioUrls` string[]: Public http(s) direct links to audio files (mp3/m4a/ogg/wav/flac, up to 49 MB each), sent as music tracks with the post text as the caption. Not a YouTube/SoundCloud page: the link must return the file itself. Cannot be combined with imageUrls/videoUrls. Requires rightsConfirmed:true.
  - `rightsConfirmed` boolean: Required with audioUrls. Set true only after the operator has confirmed they hold the rights to use these tracks and publish them in the channel. Ask them; never assume.
  - `planItemId` number: Optional content-plan slot id this draft fills. The slot must belong to this channel's plan and have no post yet; on success the draft is linked to it and the slot is marked completed. The channel's autopilot policy then decides: the response's `autopilot.action` is 'auto' (a plan was created at the slot time, `planId`/`publishAt`) or 'approve' (the draft waits in the review queue; `reason` says why — policy_manual, streak_below_target, recommendation_revise, banlist_hit, quiet hours, daily limit…). Do not call schedule_post for a draft the policy already scheduled.
  - `publishOptions` object: Optional Telegram publish options, applied when the post goes out. Set only what the operator asked for.
    - `silent` boolean: Deliver without a notification sound.
    - `pin` boolean: Pin the post in the channel once it is published (an album pins its first item).
    - `protect` boolean: Protected content: subscribers cannot forward or save the post.
    - `spoiler` boolean: Blur the photos and videos until tapped. No effect on text or audio.

## Response `200`

The tool's result in `data`.

- CreatePostFromTextOutput
  - `ok` unknown, required
  - `data` object, required: Not declared by the tool yet; treat the shape as informational.
  - `usage` Usage: Remaining paid AI actions for the team. Present only on tools that cost AI quota.
    - `aiActionsRemaining` integer, required: -1 when the team is unlimited.
    - `aiActionsLimit` integer, required: -1 when the team is unlimited.
    - `unlimited` boolean, required

## Other responses

- `400`: Invalid arguments (`invalid_input`, with per-field `details`) or a missing `channelId` (`channel_id_required`).
- `401`: No or unknown token (`unauthorized`). The `WWW-Authenticate` header points at the OAuth resource metadata.
- `402`: The team has no active subscription (`subscription_required`).
- `403`: Read-only token on a write tool (`read_only_token`), channel outside the team or the member's ACL (`channel_not_accessible`), channel over the plan limit (`channel_over_plan_limit`), or the user left the team (`not_team_member`).
- `404`: Unknown tool (`unknown_tool`) or an entity the tool could not find (`*_not_found`).
- `409`: The same call is already running (`duplicate_in_flight`); wait instead of retrying.
- `422`: The request was understood but cannot be executed — any other domain error code.
- `428`: The tool needs confirmation before it acts; repeat the call with `confirmationToken`.
- `429`: Rate limit (`rate_limited`, see `Retry-After`), AI quota (`ai_quota_exhausted`) or the daily agent-job budget (`agent_budget_exhausted`).
- `500`: The tool failed unexpectedly (`tool_error`). Nothing internal is disclosed; the `X-Request-Id` header identifies the call for support.

---

[API](https://skmtc.dev/awraiter/apis/awraiter-rest-v1.md) · [All operations](https://skmtc.dev/awraiter/apis/awraiter-rest-v1/llms.txt) · [OpenAPI document](https://skmtc.dev/awraiter/apis/awraiter-rest-v1/revisions/c16dace566cf?raw)
