Skip to content

Publish a post

Publishing is asynchronous: you create a publication, Warmup queues it and posts to each selected network, and you poll for the result.

Network text photo story Text limit
Facebook yes yes yes 63,206
Instagram — yes yes 2,200
Threads yes yes — 500

A single publication can target 1–3 networks. Every network in the list must support the chosen kind.

  1. Upload the image (skip for kind: text). Up to 15 MB; Warmup converts it to JPEG.

    Terminal window
    curl -X POST https://api.warmup.social/projects/$PROJECT_ID/media \
    -H "Authorization: Bearer $WARMUP_TOKEN" \
    -F "file=@photo.jpg"
    { "id": "6f1c…", "url": "https://…", "width": 1080, "height": 1350 }

    For the Instagram feed the aspect ratio must be between 4:5 (portrait) and 1.91:1 (landscape). Stories work best at 9:16.

  2. Create the publication.

    Terminal window
    curl -X POST https://api.warmup.social/projects/$PROJECT_ID/publications \
    -H "Authorization: Bearer $WARMUP_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
    "kind": "photo",
    "platforms": ["instagram", "threads"],
    "text": "New collection is live",
    "mediaId": "6f1c…"
    }'

    The API answers 202 Accepted with the publication and a target per network in status queued.

  3. Poll until every target is final.

    Terminal window
    curl https://api.warmup.social/projects/$PROJECT_ID/publications/$PUBLICATION_ID \
    -H "Authorization: Bearer $WARMUP_TOKEN"

    Each target moves queued → publishing → published or failed. Poll every few seconds; most posts finish within a minute.

const api = "https://api.warmup.social";
const headers = { Authorization: `Bearer ${process.env.WARMUP_TOKEN}` };
async function waitForPublication(projectId, publicationId) {
for (let attempt = 0; attempt < 40; attempt++) {
const res = await fetch(`${api}/projects/${projectId}/publications/${publicationId}`, { headers });
const publication = await res.json();
if (publication.targets.every((t) => t.status === "published" || t.status === "failed")) {
return publication;
}
await new Promise((resolve) => setTimeout(resolve, 3000));
}
throw new Error("Publication is still in progress");
}

A published target has permalink (link to the post), externalPostId and publishedAt. A failed target has errorCode:

errorCode What to do
needs_reauth, connection_not_ready Reconnect the network (guide).
media_rejected The network rejected the image — check size and aspect ratio.
media_aspect_ratio The image doesn’t fit the Instagram feed.
rate_limit The network’s own posting limit — try later.
meta_unavailable Meta is temporarily unavailable — try later.
published_unconfirmed Meta published the post but didn’t return its id. The post most likely went out — check the account before retrying.

A publication is a plan: state is scheduled by default, and without scheduledAt it goes out right away. Pass a future scheduledAt (ISO 8601 with offset) and the publication is sent at that moment, within about half a minute. timezone is the IANA zone you entered the time in — Warmup stores it to show the post in the planner at the right local time.

{
"kind": "photo",
"platforms": ["instagram"],
"mediaId": "6f1c…",
"text": "Friday drop",
"scheduledAt": "2026-11-06T18:00:00+02:00",
"timezone": "Europe/Kyiv"
}
  • state: draft keeps the publication in the planner without sending it. Drafts don’t need connected networks and may have no date at all.
  • Until a publication is sent (state is draft or scheduled), PATCH /projects/{id}/publications/{publicationId} changes any field; scheduledAt: null removes the date, state: scheduled without a date sends it right away. After sending, PATCH answers 409 publication_dispatched.
  • POST /projects/{id}/publications/{publicationId}/publish sends a draft or a scheduled publication right away.
  • DELETE /projects/{id}/publications/{publicationId} removes it from the planner; a publication that is still queued is cancelled.
  • GET /projects/{id}/publications?from=…&to=… returns the planner window plus every draft without a date; state summarises the per-network result (publishing, published, failed).
  • text is required for kind: text; mediaId is required for photo and story and not allowed for text.
  • Networks in platforms must be connected and active, otherwise the API answers 409 with connection_missing or connection_not_ready.
  • Upload and publication requests share a limit of 30 per minute per user (429 above it).
  • GET /projects/{id}/publications without from/to lists the 50 most recent publications.