Chirio
Get Started
Introduction
Quickstart
How Chirio works
Authentication
Platforms
Overview
Instagram
Threads
X
LinkedIn
Supa Hub
Guides
Connect accounts
Publish a post
Media requirements
Carousels
First comments
Idempotency and retries
Account lifecycle
Revocation and deletion
The dashboard
Billing
Plans
Metering
Quotas
Api Reference
Overview
Accounts
Posts
Errors
Changelog
Roadmap
TrademarkTrademark
Ctrl k
Search…
Sign up
Chirio
Get Started
Introduction
Quickstart
How Chirio works
Authentication
Platforms
Overview
Instagram
Threads
X
LinkedIn
Supa Hub
Guides
Connect accounts
Publish a post
Media requirements
Carousels
First comments
Idempotency and retries
Account lifecycle
Revocation and deletion
The dashboard
Billing
Plans
Metering
Quotas
Api Reference
Overview
Accounts
Posts
Errors
Changelog
Roadmap
TrademarkTrademark© Dopler. All rights reserved.
Built with Aveiro

Publish a post

One call, many targets — and how to read a result where the targets disagree.
Updated 1mo ago
Connect accounts
Media requirements
POST /api/v1/posts takes one piece of content and up to ten targets, publishes to all of them concurrently, and returns what happened on each.
curl -X POST "https://chirio.dev/api/v1/posts" \
  -H "Authorization: Bearer $CHIRIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: release-2-4-0" \
  -d '{
    "content": "Chirio 2.4 is out.",
    "mediaItems": [{ "type": "image", "url": "https://cdn.yourapp.com/2-4.jpg" }],
    "platforms": [
      { "platform": "instagram", "accountId": "8f2c..." },
      { "platform": "threads",   "accountId": "d31a..." },
      { "platform": "linkedin",  "accountId": "77bb...", "firstComment": "Release notes: https://example.com/2-4" }
    ]
  }'

Request body

Field
Type
Rules
`content`stringUp to 10,000 chars at the API; each platform enforces its own shorter limit. Defaults to `""`.
`mediaItems`array0–10 items of `{ type: "image" \
`platforms`array1–10 targets of `{ platform, accountId, firstComment?, content?, mediaItems?, ... }`.
`publishAt`stringOptional ISO 8601 time to publish at instead of now. At least a minute out, at most a year.
`idempotencyKey`string1–255 chars. Also accepted as the `Idempotency-Key` header; the body field wins.
firstComment is per target, not per post, so the same publish can carry different comment wording for LinkedIn and Instagram. See First comments. So are content and mediaItems: set either on a target and it overrides the post's own for that target only, which is how one announcement carries a square crop for Instagram and a wide one everywhere else without becoming three separate posts. Leave them off and the target follows the post. A few more per-target fields exist for specific platforms — postType on Instagram, spaceSlug on Supa Hub — see the Posts reference.

What is validated up front

Before any network call, Chirio checks in this order:
  • Body shape — schema violations answer 400 invalid_request.
  • Accounts exist and belong to your project — otherwise 404 account_not_found.
  • Platform matches the account — targeting a threads account as instagram answers 400 platform_mismatch.
  • Per-platform content rules — length, media counts, video/image mixing. A violation on any target rejects the whole request with 400 invalid_post, before anything publishes.
  • Quota — on a plan with no overage. See .
429 quota_exceeded
Quotas
That ordering is deliberate: everything you can fix is checked before a single byte reaches a platform, so a rejected request never leaves a half-published post behind. A scheduled post is validated the same way at submission, and has its quota checked again when it actually goes out.

Publishing later

Add publishAt and the response is a 202 instead of a 201: the post is stored, every target reads pending, and a dispatcher sends it when its time comes. Nothing else about the request changes. POST /api/v1/posts/:id/cancel calls one off while it is still waiting, and a scheduled post is immutable — moving one is cancel plus create. Full detail in Scheduling a post.

Reading the response

201 Created — 202 Accepted when scheduled, or 200 with replayed: true for an idempotent retry:

Target fields

Field
Meaning
`status``published` or `failed` — `pending` while a scheduled post waits, `processing` mid-dispatch
`platformPostId`The platform's own id
`url`Public URL of the post, when the platform returns one
`error``code: message` on failure, `null` otherwise
`publishedAt`When the platform accepted it
`commentStatus``none`, `published`, `failed` or `unsupported` — independent of `status`
`commentId` / `commentError`Outcome of the first comment

Mixed results are normal

Do not treat 201 as success
A 201 means the request was accepted and every target was attempted. Targets fail independently — one platform's rate limit, expired connection or media error does not stop the others. Always iterate post.targets and act per target.
Retrying only the failed targets is also the cheap thing to do: usage is recorded per target that actually published, so a retry of the third target bills the third target only.

Timing

An immediate publish is synchronous and waits for every platform. Image posts settle in seconds. Video is different — the platform processes the file server-side and Chirio waits for it, up to roughly four minutes per item, so a video-heavy carousel can take several minutes.
Two consequences worth designing for:
  • Send an Idempotency-Key so a client-side timeout is safe to retry. See Idempotency.
  • Poll GET /api/v1/posts/:id when a replayed response shows targets that have not settled yet.
Scheduling sidesteps both: a 202 returns immediately, and the dispatcher does the waiting.

Listing history

Newest first. limit is capped at 100 and defaults to 25.
{
  "post": {
    "id": "3a1e...",
    "content": "Chirio 2.4 is out.",
    "media": [{ "type": "image", "url": "https://cdn.yourapp.com/2-4.jpg" }],
    "createdAt": "2026-08-04T09:12:44.101Z",
    "targets": [
      {
        "id": "b77d...",
        "accountId": "8f2c...",
        "platform": "instagram",
        "status": "published",
        "platformPostId": "17851...",
        "url": "https://www.instagram.com/p/...",
        "error": null,
        "publishedAt": "2026-08-04T09:12:51.882Z",
        "commentStatus": "none",
        "commentId": null,
        "commentError": null
      },
      {
        "id": "c02f...",
        "accountId": "d31a...",
        "platform": "threads",
        "status": "failed",
        "platformPostId": null,
        "url": null,
        "error": "media_processing_timeout: container did not finish processing",
        "publishedAt": null,
        "commentStatus": "none",
        "commentId": null,
        "commentError": null
      }
    ]
  },
  "replayed": false
}
const { post } = await res.json();

const failed = post.targets.filter((t) => t.status === "failed");
if (failed.length > 0) {
  // Retry only these accountIds, with a NEW idempotency key.
  console.warn(failed.map((t) => `${t.platform}: ${t.error}`));
}
curl "https://chirio.dev/api/v1/posts?limit=25&offset=0" \
  -H "Authorization: Bearer $CHIRIO_API_KEY"