# Post lifecycle (/docs/concepts/post-lifecycle)



A post goes to one or more accounts. Each account gets a **target** with its own status, and the post's status sums up its targets. This page shows how both move and which calls are allowed when.

Post status [#post-status]

<PostLifecycleDiagram />

| Status             | Meaning                                                                                                                    |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `draft`            | Saved without a time. Nothing happens until you schedule or publish it                                                     |
| `scheduled`        | Waiting for its time. `publishNow` and `POST /posts/{id}/publish` answer with `scheduled` too and start sending right away |
| `publishing`       | At least one account is being sent, or waits for an automatic retry                                                        |
| `published`        | Every account published                                                                                                    |
| `partially_failed` | Every account is done; some published, some failed                                                                         |
| `failed`           | Every account is done and none published                                                                                   |

Scheduled posts are picked up once a minute, so a post goes out within about a minute of its time.

Target status [#target-status]

Each entry in `targets[]` has its own status: `draft`, `scheduled`, `publishing`, `published` or `failed`. A failed target carries an `error` with a `code`, a `category` and a `source` that says who can fix it, see [Publishing errors](/docs/api/errors#publishing-errors). A published target carries `platformPostId` and `platformUrl`.

When a platform is rate limiting or down, the target shows `failed` with its error while FeedHorizon waits to try again (3 attempts in total, after 2 and 10 minutes). The post stays `publishing` during that time. Only when the last attempt fails, or the error is one that is not retried, does the target count as failed for good and `post.target.failed` is sent.

What you can do in each state [#what-you-can-do-in-each-state]

| Call                         | `draft` | `scheduled` | `publishing`        | `published` | `partially_failed` | `failed` |
| ---------------------------- | ------- | ----------- | ------------------- | ----------- | ------------------ | -------- |
| `PATCH /posts/{id}` (edit)   | Yes     | Yes         | No                  | No          | No                 | Yes      |
| `POST /posts/{id}/publish`   | Yes     | Yes         | No                  | No          | No                 | Yes      |
| `POST /posts/{id}/cancel`    | No      | Yes         | No                  | No          | No                 | No       |
| `POST /posts/{id}/retry`     | No      | No          | Only failed targets | No          | Yes                | Yes      |
| `POST /posts/{id}/duplicate` | Yes     | Yes         | Yes                 | Yes         | Yes                | Yes      |
| `DELETE /posts/{id}`         | Yes     | Yes         | No                  | Yes         | Yes                | Yes      |

* **Edit** is refused with `409 post_not_editable` as soon as any account is publishing or published. To change the text of a post that is partly live, [duplicate](#duplicate-to-change-a-live-post) it.
* **Retry** sends only the failed accounts again; published ones stay as they are. It answers `409 post_not_failed` when no account has failed.
* **Cancel** takes a scheduled post off the schedule and keeps it as a draft. On any other status it answers `409 post_not_scheduled`.
* **Delete** removes the post from FeedHorizon. Posts that are already live stay on the platforms. While an account is being sent, delete answers `409 post_publishing`; try again a few seconds later.

Duplicate to change a live post [#duplicate-to-change-a-live-post]

A post that is partly or fully live cannot be edited, because the live part could no longer match it. To post a corrected version, for example after `platform_rejected` on one account:

1. `POST /posts/{id}/duplicate` creates a draft with the same text, media, accounts and own texts.
2. `PATCH` the copy: keep only the accounts that failed in `targets`, and fix the text.
3. Schedule or publish the copy.

Webhooks along the way [#webhooks-along-the-way]

| When                                                            | Event                                             |
| --------------------------------------------------------------- | ------------------------------------------------- |
| A post gets a time (created scheduled, or a draft is scheduled) | `post.scheduled`                                  |
| Text, time, accounts or status change                           | `post.updated`                                    |
| One account published                                           | `post.target.published`                           |
| One account failed for good                                     | `post.target.failed`                              |
| All accounts done                                               | `post.published`, `post.partial` or `post.failed` |
| The post was deleted                                            | `post.deleted`                                    |

The payload of each event is in [Webhook events](/docs/api/webhook-events/post-published).
