Publishing

Create a post, target accounts, publish it, and read the per-account result.

Publishing in FoPost is two steps: create a post, then publish it. They are separate because a post is a durable object with per-account deliveries, not a fire-and-forget request. Creating and reading posts needs the posts scope. Publishing, retrying and cancelling also need publish, so a key can manage drafts without being able to send anything to a network.

The model

A post holds your content and the list of accounts it targets. An account is one connected profile, for example your LinkedIn company page. You target account ids, never platform names: a workspace can hold three X accounts, and the API will not guess which one you meant.

Publishing produces one delivery per account. Deliveries succeed and fail independently, which is why a post can end up partially_failed.

Create a post

POST /v1/posts
curl -X POST https://api.fopost.com/v1/posts \
  -H "X-API-Key: $FOPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90",
    "accounts": ["3a9f1d20-6c77-4b2e-9a01-8f5d2c3b4e10"],
    "content": [
      { "text": "Shipping something new today." }
    ],
    "status": "draft"
  }'
FieldTypeRequiredDescription
workspace_iduuidYesWorkspace the post belongs to
accountsuuid[]Yes, unless account_group_id is setConnected accounts to target
account_group_iduuidNoTarget every account in an account group. Merged with accounts; each account publishes once
contentblock[]YesOne block per item. More than one block makes a thread
content[].textstringNoThe text of the block
content[].mediamedia[]NoAttachments for the block
content_typeenumNopost, thread, or reel. Default post
artifact_typeenumNotext_post, thread, article, carousel, short_video, link_share
statusenumNodraft or scheduled. Default draft
schedule_atdatetimeNoWhen to publish. Required when status is scheduled
scheduleenumNoqueue takes the next free posting slot instead of a schedule_at
labelsuuid[]NoLabels to attach
titlestringNoTitle, for platforms that take one
summarystringNoSummary, for platforms that take one
auto_plugbooleanNoPost a follow-up comment after publishing

Media items take type (image, video, gif), name, and url, plus optional alt and thumbnail. Upload files first through the media endpoints and pass what they return.

The response is the created post, with id and status.

Publish it

POST /v1/posts/{id}/publish
curl -X POST https://api.fopost.com/v1/posts/$POST_ID/publish \
  -H "X-API-Key: $FOPOST_API_KEY"

This answers 202 Accepted and queues delivery. It does not wait for the platforms, so a 202 means accepted, not live. Poll deliveries or subscribe to webhooks for the outcome.

Two options are worth knowing:

  • accountIds publishes to a subset of the post's accounts instead of all of them
  • options.dryRun validates and returns the accounts it would target without publishing, answering 200 instead of 202

Accounts whose credentials have expired or been revoked fail immediately. Accounts flagged degraded still publish, with a warning attached.

Check before you publish

POST /v1/posts/{id}/preflight

Preflight returns two different things, and the difference matters:

  • issues are hard blockers, such as content over a platform's character limit or a media type the platform will not take
  • content quality signals are advisory. They never block publishing

Run it when you are publishing on behalf of a user who cannot see the composer.

Read the result

GET /v1/posts/{id}/deliveries

One record per account, newest first:

{
  "data": [
    {
      "id": "3a9f1d20-6c77-4b2e-9a01-8f5d2c3b4e10",
      "platform": "linkedin",
      "username": "yourbrand",
      "publish_status": "published",
      "posted_at": "2026-08-29T14:30:12.000Z",
      "external_url": "https://www.linkedin.com/feed/update/urn:li:share:6789",
      "attempts": 1,
      "max_attempts": 3
    },
    {
      "id": "b81e4f02-91a3-4c55-8de6-77a0c1f2d3e4",
      "platform": "twitter",
      "username": "yourbrand",
      "publish_status": "failed",
      "error_code": "media_too_large",
      "error_message": "The attached video exceeds the size this platform accepts.",
      "attempts": 3,
      "max_attempts": 3
    }
  ]
}

publish_status is one of pending, queued, delayed, publishing, published, failed, cancelled, unpublished. The post's own status summarises them: published when every delivery landed, partially_failed when some did.

Retry a failure

POST /v1/posts/{id}/retry

Re-queues the failed deliveries of a failed, partially failed, or published post. Deliveries that have already used their attempts come back in exceeded and are skipped rather than silently dropped. Resending to an account that already published requires includePublished: true, so you cannot double-post by accident.

Transient failures are already retried automatically with exponential backoff. Reach for this endpoint when the cause was on your side, for example a media URL that was not reachable yet.

Cancel

POST /v1/posts/{id}/cancel

Cancels a scheduled post and its queued deliveries. Anything already published stays published.

Edit after publishing

PUT /v1/posts/{id}

A post that has gone out can still be edited. Send a content change only, content, title, settings, labels, internal_title or summary, and the new text is pushed to every network the post is live on. The accounts and the schedule stay as they are; sending accounts, status or schedule_at on a published post answers 409.

Every network the post is live on has to support editing in place. Each post account carries remote_edit, and when one of them is false the whole update answers 409 with unsupported_platforms and changes nothing, so a caption never drifts between networks. X is the usual reason: its API deletes and reposts, it does not edit.

The response is the post with a remote_edits array, one entry per network with edited and, when a network refused, its error. The pass is also written to the publish log as its own run. Reaching a network needs the publish scope as well as posts.

Unpublish

POST /v1/posts/{id}/unpublish

Takes the post down on every network it is live on. The post stays in FoPost as a record: each delivery is marked unpublished, the pass is written to the publish log, and the post status becomes unpublished once nothing is live any more. Requires the publish scope.

Every network has to support deleting a live post (remote_delete on each post account). When one does not, the request answers 409 with unsupported_platforms and removes nothing. A network that refuses at the time comes back with removed: false and keeps its delivery published.

{
  "data": {
    "post_status": "unpublished",
    "deliveries": [
      { "account_id": "…", "platform": "linkedin", "removed": true }
    ]
  }
}

To remove the post from FoPost as well, use DELETE /v1/posts/{id}?remove_from_platform=1 instead.

Duplicate

POST /v1/posts/{id}/duplicate

Copies a post, its content, and its account targeting into a new draft. Useful for a recurring format you do not want to rebuild each time.

Many at once

POST /v1/posts/bulk applies one action to up to 200 posts in a single transaction: shift moves each schedule_at by offset_minutes, label adds, removes, or replaces labels, and delete soft-deletes them and cancels their queued deliveries.

The whole batch is validated first. An id outside the workspace answers 404 and a post that is no longer a draft or scheduled answers 409, in both cases without changing anything.

For importing a spreadsheet of posts, see Scheduling.

Next

Related documentation
  • API Overview

    Base URL, envelopes, pagination, and errors for the FoPost REST API.

  • Authentication

    API keys, scopes, and workspace binding.

  • Scheduling

    Schedule a post, repeat it, and import a batch from a spreadsheet.

  • Media

    Upload files, list the media library, and attach media to a post.

  • Validation

    Check content, text length, and media against platform rules before a post exists.

  • Accounts

    List connected social accounts, check their health, and refresh credentials.

  • Workspaces

    Workspaces, labels, and how isolation works across them.

  • Analytics

    Overview totals, time series, top posts, demographics, and label roll-ups.

Was this helpful?

On this page