Skip to content
SocialHelper
Get API key

Publishing

Posting

Create, list, check and cancel posts.

A post is some text (and optionally media) sent to one or more connected accounts. Each account gets its own target, with its own status, so one slow or failing platform never holds up the others.

Create a post

POST /api/v1/posts

accounts array of strings · required
The IDs of the accounts to post to, from GET /api/v1/accounts. Between 1 and 50, no duplicates.
text string
The post text, up to 100,000 characters here; each platform’s own limit is checked too.
media array of strings
Up to 20 media IDs from POST /api/v1/media.
scheduled_at string
An ISO 8601 time to publish at, up to 1 year ahead. Leave it out to publish now.
overrides object
Per platform: a different text, plus that platform’s options. Keyed by platform, e.g. {"bluesky": {"text": "…"}}.
curl https://socialhelper.app/api/v1/posts \
  -H "Authorization: Bearer $SOCIALHELPER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
      "text": "We just shipped dark mode. Try it tonight 🌙",
      "accounts": [
          "ACCOUNT_ID_1",
          "ACCOUNT_ID_2"
      ]
  }'

A new post returns 201. Repeating the request with the same Idempotency-Key returns the original post with 200.

Every target is checked first

Before anything is created, the content is checked against every target’s platform: text length counted the platform’s way, number and type of images and video, file sizes, required options. If anything doesn’t fit, nothing is posted and you get a 422 listing every problem, keyed by the account’s position:

{
    "message": "Jane Doe (Bluesky): …",
    "errors": {
        "accounts.0": [
            "Jane Doe (Bluesky): …"
        ]
    }
}

Accounts that can’t receive posts (data accounts) and accounts that need reconnecting are rejected the same way. Each platform’s limits are on its platform page and in GET /api/v1/platforms.

A different text for one platform

Give a platform its own text, for example a shorter version for a platform with a small limit:

{
    "text": "We just shipped dark mode. Try it tonight 🌙",
    "accounts": [
        "ACCOUNT_ID_1",
        "ACCOUNT_ID_2"
    ],
    "overrides": {
        "bluesky": {
            "text": "Dark mode just landed 🌙"
        }
    }
}

Platforms with extra options (such as a YouTube video’s title) take them in the same place, for example overrides.youtube.title. Options for each platform are listed on its platform page.

Statuses

Each target moves through these statuses:

scheduled target
Waiting for its scheduled time, or for a retry (next_attempt_at says when).
queued target
Due and waiting for a worker.
publishing target
Being sent to the platform.
confirming target
Sent; waiting for the platform to confirm it went live.
published target
Live. url links to it and external_id is the platform’s ID.
failed target
Didn’t go out; error says why.
cancelled target
Cancelled before it started.

The post’s own status sums up its targets: scheduled, publishing, published, partially_published, failed or cancelled.

Retries never post twice

If a platform is busy or unreachable before anything was sent, the target goes back to scheduled and is retried later. If a platform’s answer is unclear, SocialHelper checks the platform for the post before trying again where the platform allows it, and otherwise marks the target failed rather than risk a duplicate.

List and fetch posts

GET /api/v1/posts lists posts, newest first, 25 per page (?page=2 for more). Filter with ?status= and a post status. GET /api/v1/posts/{id} returns one post with its targets.

curl https://socialhelper.app/api/v1/posts?status=scheduled \
  -H "Authorization: Bearer $SOCIALHELPER_API_KEY"

Cancel a post

DELETE /api/v1/posts/{id} cancels every target that hasn’t started publishing and returns the post. Targets already publishing or live aren’t touched, and posts already on a platform aren’t removed from it. If nothing could be cancelled, you get a 422.