# Posts

A post holds the text and media you want to publish, together with its target networks. Start with shared Markdown text, then give a network its own text, post type or fields where needed. Approval covers all of these choices; scheduling converts the approved variant to plain text for the channel.

## The shape of a post

| Field | What it is |
|---|---|
| `id` | Post id |
| `text` | The current text, in Markdown |
| `media` | Media ids in display order, shared by all targets |
| `targets` | Target networks and their variants |
| `author` | Identity id of whoever composed the post |
| `status` | `draft`, `pending_approval`, `approved`, `scheduled`, `published` or `failed` |
| `currentVersion` | Version number of the current text; a new post is version 1 |
| `approvedVersion` | The version an approval binds to, or `null` |
| `createdAt`, `updatedAt` | ISO-8601 timestamps |

A post accepts up to 50,000 characters and 20 media attachments. Validation and scheduling also check the chosen channel’s limits; a network may accept less. See [Channels](/acquire/content/channels).

Each entry in `targets` names a `network` and can set `postType`, `text` and `fields`. Omit `postType` to use the network’s default. Omit variant text or set it to `null` to use the post’s base text; supplied variant text must contain 1 to 50,000 characters. Media stays shared across targets.

You can create a draft without targets and add them with `content_posts_update`. A supplied targets list replaces the previous list; omitting it preserves the current targets. Up to 20 targets are allowed, with each network listed once. Unknown networks, duplicate networks, unsupported post types or fields, and fields exceeding `maxLength` receive `400`. Empty fields are stored as absent. Read `postTypes` and `variantFields` in the channel descriptor before filling them in.

## The lifecycle

```text
draft ──submit──▶ pending_approval ──approve──▶ approved ──schedule──▶ scheduled ──▶ published
  ▲                      ▲                          │
  └──────edit────────────┴──────────edit────────────┘
```

- Submit only a `draft` with at least one target and all required variant fields. A missing YouTube `title`, or Reddit `title` or `subreddit`, blocks submission with a conflict naming the missing fields.
- Only a `pending_approval` post can be approved.
- Only an `approved` post can be scheduled. A post already `scheduled` can be scheduled again, so one post fans out to several channels without being re-approved between them.
- Editing a `draft` leaves it a draft.
- Editing a `pending_approval` or `approved` post returns it to `pending_approval` and clears `approvedVersion`.
- Editing a post that is already `scheduled`, `published` or `failed` is refused: the text is either queued or already out in the world. Write a new post instead.

Changing a target, its post type, text or fields counts as an edit. Reordering unchanged targets or their field keys does not. A rejected transition returns a conflict naming the current status.

## Versions

Use `content_posts_versions` to read the edit history, oldest first. Each immutable version records the text, media, targets, authoring identity and time.

Scheduling reads the approved version, including every variant. If an edit revokes that approval, `content/post.approval-revoked` identifies the version that was previously approved.

## Approval is a person's act

You can automate drafting and submission, then wait for a person to approve. `content_posts_approve` is unavailable as a workflow step, and the approving identity comes from the caller’s credentials. The caller cannot supply a different approver as a parameter.

After approval, `content/post.approved` carries the post, approver and approved version. Use it to resume a workflow that schedules the reviewed content.

## Media is an edit

Attach media while preparing the draft so it is included in the version a person reviews. Attaching or detaching creates a version and returns an approved post to `pending_approval`. Only `ready` assets can be attached; attaching an asset already on the post has no effect. See [Media](/acquire/content/media).

## Listing

`content_posts_list` returns posts newest first, with `total` counting every match regardless of paging. Filter by `status`; page with `limit` (1 to 100, default 50) and `offset`.

## Tools

| Tool | What it does |
|---|---|
| `content_posts_create` | Create a draft at version 1, with or without targets |
| `content_posts_update` | Replace text, media and/or targets; record a new version |
| `content_posts_get` | Read one post |
| `content_posts_list` | List posts, newest first |
| `content_posts_versions` | The full version trail, oldest first |
| `content_posts_submit` | Submit a draft with targets and required variant fields |
| `content_posts_approve` | Approve a post awaiting approval |
| `content_posts_attach_media` | Append ready media assets |
| `content_posts_detach_media` | Remove media assets, keeping the order of the rest |

Their inputs, outputs and REST bindings are in [Tools and REST API](/acquire/content/tools-and-api).

## Next steps

- [Publications](/acquire/content/publications): what happens to an approved post once you give it a slot.
- [Media](/acquire/content/media): the two ways bytes arrive, and what a channel will accept.
