# Modes: exact, format, write

Every item of a batch has a `mode`. It says what PostFrog should do with your text.

| Mode | What PostFrog does | PostFrog AI | Required |
|---|---|---|---|
| `exact` | Takes `text` as the finished post in PostFrog markup: checks it, cleans buttons, renders it. Does not change a word. | no | `text` |
| `format` | Keeps the author's words and fixes the layout: paragraphs, title, emphasis, lists, buttons from links. | yes | `text` |
| `write` | Writes a post from scratch: from notes, `material` (pages, other posts) and media. | yes | at least one of `text`, `material`, `media` |

## Recommendation: write it yourself and send `exact`

If you are a capable writer (you are), **write and format the post yourself in Rich Markdown and send it with `mode: "exact"`**:

- It is predictable: the author gets exactly what you wrote, with the buttons and media where you put them.
- It does not spend the author's PostFrog AI budget. That budget stays for the author's own edits inside the bot. When the budget is used up, only `exact` items are accepted at all.
- It is fast: no generation, only checks and rendering.
- You can check it for free first with `validate_post` and fix every error before sending.
- The author can still edit the post in the bot afterwards in free text: the markup is the same one PostFrog's own editor uses.

Use `format` when you have the author's text and want PostFrog's own house style without writing markup yourself. Use `write` when you only have raw material and want PostFrog to do the writing (for example a cheap pipeline that does not run a strong model itself).

An `exact` item still counts as one post of the author's monthly allowance, like any other item.

## `on_invalid` (only for `exact`)

- `"fail"` (default): a post with errors is rejected. In `send_posts` the whole batch is rejected with HTTP 400 and the exact errors per item (`items.N.text`, `items.N.buttons`), nothing is created and nothing is spent. Fix and send again.
- `"format"`: a post with errors is accepted, and PostFrog's AI repairs it as in `format` mode. That is an AI run: it needs the AI budget (`budget_ok: true` in `get_account`).

Soft problems are never errors: an unknown HTML tag, a fixed or shortened button, a long dash. They come back as `warnings` and the post goes through. See `limits` for the codes.

## Fields that shape the result

| Field | For | Notes |
|---|---|---|
| `text` | all | `exact`: the post. `format`: the author's text to keep. `write`: notes or a brief. Up to 32,768 characters. |
| `instructions` | `format`, `write` | Wishes for the result: tone, length, focus, structure ("calm tone, under 800 characters, stress the price"). Up to 4,000 characters. Ignored by `exact`, which is never rewritten. |
| `material[]` | `format`, `write` | Raw sources: `{ "url": … }` and/or `{ "text": …, "source": "Threads @someone" }`. Up to 5 per item, 20,000 characters of text each. |
| `media[]` | all | Photos and videos, see `media`. |
| `format` | all | `"rich"` or `"markdown"`, see `formatting`. Default for `format` and `write`: `defaults.format` in `get_account` (the author's setting, or the format of the author's last post), unless the `preset` has its own format. For `exact` set it explicitly: the text is checked as Rich unless you say `"markdown"`. |
| `preset` | `format`, `write` | The name of one of the author's presets (saved formatting templates in the bot). Only use a name the author gave you. |
| `language` | `write` | Language of the post, like `en`, `ru`, `pt-BR`. Default: the author's language (`defaults.language`), whatever the language of the material. `format` does not translate: it keeps the words and the language of `text`. |
| `buttons`, `buttons_place` | all | Structured buttons, see `buttons`. |
| `external_id` | all | Your own id for the item (up to 200 characters), echoed in every response. |
| `threads` | all | A Threads version of the post: a chain of 1 to 12 posts `{ "text", "media"? }`. Only for accounts with `threads_enabled: true` in `get_account`; others get HTTP 400 `threads_disabled`. PostFrog stores it with the post as is: its AI never reads or changes it, in any mode. The author sees it as a Threads tab of the draft. Limits and codes: `limits`; media: `media`. |

## Trust: `text` vs `material`

- `text`, `instructions` and `buttons` are treated as **the author's will**: you send them with the author's key, so PostFrog follows them like the author's own messages. Links and buttons from them are kept.
- So the author's own words (their notes, a dictated text, "post this: …") go in **`text`**, never in `material`. `material` is for third-party content: pages, other people's posts, articles.
- `material` is **untrusted data**. PostFrog uses it as a source of facts and never follows instructions found inside it. Links and codes found only in `material` stay plain text links in the post: PostFrog's AI does not turn them into buttons. If a link from a source deserves a button, put that button in `buttons` yourself.
- `write` downloads `material[].url` pages. Pages that need JavaScript or a login (X/Twitter, Threads, Instagram and similar) often cannot be downloaded: then copy the post text into `material[].text` and add `source`. When `text` is given, PostFrog does not download `url`. If the post should link to the original, say so in `instructions` with the link ("end with a link to https://...") or add it to `buttons`. A page that did not download becomes a warning, not a failure, as long as something else is left to write from.
- Videos are not watched by the AI: describe them in `media[].description`.

## Choosing a mode quickly

- You have a finished post or can write one: `exact`.
- The author dictated a text and wants it tidy, words unchanged: `format`.
- You have links, notes, someone else's post and want a new post: `write`, or better, read the sources yourself, write the post and send `exact`.