> ## Documentation Index
> Fetch the complete documentation index at: https://docs.madiad.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Posts

> Publish text, photos, or video to many platforms in a single request.

A **post** publishes content to one or more platforms connected to a profile. You send one request; MADIAD Hub fans it out to every platform you list and returns a per-platform result.

<Note>
  **Publishing is unlimited on every paid plan** — there is no monthly cap on how many posts you send. Usage is still metered, and **one request counts as one upload no matter how many platforms it targets**: publishing to TikTok, Instagram, and Facebook in a single call records **1** upload, not 3. See [Usage](/concepts/usage) to read your current figures.
</Note>

## Choose an endpoint by media type

| Endpoint                | Use for                                  |
| ----------------------- | ---------------------------------------- |
| `POST /v1/posts/text`   | Text-only updates                        |
| `POST /v1/posts/photos` | One or more images (and photo carousels) |
| `POST /v1/posts/video`  | A single video                           |

The photo endpoint uses `multipart/form-data` for file uploads, or plain JSON when the images come from URLs (`photo_urls[]` — see [Send photos by URL](#send-photos-by-url)). Text and video use JSON.

## Images per post

MADIAD Hub doesn't impose its own cap on how many photos you attach to `photos[]` — it passes them straight through. Each platform enforces its own maximum, so the effective limit for a fan-out post is the **smallest** limit among the platforms you targeted:

| Platform                           | Max images                                       | Behavior over the limit                                                                                                               |
| ---------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| Pinterest                          | 5                                                | Hard cap — the platform rejects the post                                                                                              |
| Instagram                          | 10                                               | Hard cap — the platform rejects the post                                                                                              |
| Telegram                           | 10                                               | Hard cap — the platform rejects the post                                                                                              |
| Discord                            | 10                                               | Hard cap — the platform rejects the post                                                                                              |
| X                                  | 4 per tweet                                      | Extra images are auto-distributed across a thread — see `x_thread_image_layout` in [Platform options](/concepts/platform-options)     |
| Threads                            | 10 per post                                      | Extra media is auto-distributed across a thread — see `threads_thread_media_layout` in [Platform options](/concepts/platform-options) |
| Bluesky                            | 4                                                | Hard cap — the platform rejects the post                                                                                              |
| TikTok, LinkedIn, Facebook, Reddit | No fixed limit imposed by the publishing service | —                                                                                                                                     |

<Note>
  These are the platforms' own published limits, not something MADIAD Hub enforces — they can change on the platform's side without notice. When in doubt, test with a small batch first.
</Note>

## Fan out with `platforms[]`

List every target platform. The same content goes to each, unless you override per platform.

```bash theme={null}
curl -X POST https://api.madiad.com/v1/posts/photos \
  -H "Authorization: Bearer $MADIAD_API_KEY" \
  -H "Idempotency-Key: 9f1c2e7a-4b6d-4e2a-9c10-7b3f8d5a1e22" \
  -F "profile_id=prof_01HZX9F2K4M7N6QR8T0V2W4Y6Z" \
  -F "platforms[]=instagram" \
  -F "platforms[]=facebook" \
  -F "platforms[]=tiktok" \
  -F "caption=New blend dropping today" \
  -F "photos[]=@./launch.jpg"
```

<Note>
  **One request shares its media across every listed platform.** You can override the text per
  platform (`caption` → `<platform>_caption`, below) and pass platform-specific options, but the
  `photos[]` / `photo_urls[]` / `video` you attach go to **all** the platforms in that request — there is no
  per-platform media field. To publish **different images or video per platform**, send a **separate
  request per platform** (each with its own `photos[]`/`video`).
</Note>

## Send photos by URL

Instead of uploading the bytes, pass public links with `photo_urls[]` — MADIAD Hub downloads each image server-side and publishes it exactly as if you had uploaded the file. Prefer this for **large images or big batches**: the heavy transfer happens on our side, so your request stays tiny and can never trip a request-size error.

```bash theme={null}
curl -X POST https://api.madiad.com/v1/posts/photos \
  -H "Authorization: Bearer $MADIAD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3ab77c14-08e5-4d69-b2f1-6c5904ad7e83" \
  -d '{
    "profile_id": "prof_01HZX9F2K4M7N6QR8T0V2W4Y6Z",
    "platforms": ["instagram", "facebook"],
    "caption": "New blend dropping today",
    "photo_urls": [
      "https://cdn.example.com/launch-1.jpg",
      "https://cdn.example.com/launch-2.jpg"
    ]
  }'
```

* Each URL must be a **public `https://` direct image link** (a hostname, not an IP address). A share-page link that opens a web page (e.g. a drive viewer) is rejected with a clear error — use the direct file URL. Redirects are followed (up to 5 hops), but every hop must stay `https://`.
* Up to **40 URLs per request**, **30 MB per image**, and **120 MB combined**.
* Also works in multipart: send `photo_urls[]` as text fields, and mix them freely with `photos[]` file uploads in one request — on carousels, uploaded files come first, then URL photos, each in send order.

## Per-platform overrides

`caption` is the shared default. Override it for a single platform with a `<platform>_caption` field — handy for hashtags, mentions, or length limits.

```bash theme={null}
  -F "caption=New blend dropping today" \
  -F "instagram_caption=New blend dropping today ☕ #specialtycoffee" \
  -F "x_caption=New blend out now →"
```

The same prefix pattern applies to other per-platform options, such as `instagram_first_comment`.

<Note>
  **Instagram overrides are published as their own job.** When `instagram_caption` differs from
  the shared `caption` and the request also targets other platforms, MADIAD Hub sends Instagram
  separately so it gets exactly the text you wrote. This is invisible for a normal post — still
  **one upload** against your quota, one `request_id`, one status to poll. For a **scheduled**
  post it is visible in one place: the schedule list shows Instagram as its own entry, so
  cancelling or editing it is a separate action from the other platforms.

  One combination is refused: `add_to_queue` together with an `instagram_caption` that differs
  from the shared `caption`. Instagram would take its own queue slot and go out at a different
  time from the rest, so the request is rejected instead — queue Instagram as its own request, or
  use the shared `caption`.
</Note>

## Character limits

`caption` maps to each platform's main text field. The limit differs per platform — and on a few platforms that field is a short **title**, with the long text going in a separate `description`:

| Platform        | `caption` limit                      | Notes                                                                                |
| --------------- | ------------------------------------ | ------------------------------------------------------------------------------------ |
| Facebook        | 63,206                               |                                                                                      |
| Instagram       | 2,200                                |                                                                                      |
| LinkedIn        | 3,000                                |                                                                                      |
| TikTok (video)  | 2,200                                |                                                                                      |
| TikTok (photo)  | 90                                   | It's a **title** — put long text in `tiktok_description` (≤ 4,000)                   |
| YouTube         | 100                                  | `caption` becomes the video **title**; use `description` for the long text (≤ 5,000) |
| Pinterest       | 500                                  | Pin title is capped at 100 (`pinterest_title`)                                       |
| Reddit          | 300                                  | `caption` becomes the post **title**; body via `description` (≤ 5,000)               |
| X               | 280                                  | Longer text is auto-split into a thread                                              |
| Threads         | 500                                  | Longer text is auto-split into a thread                                              |
| Bluesky         | 300                                  | Longer text is auto-split into a thread                                              |
| Google Business | 1,500                                |                                                                                      |
| Telegram        | 4,096 (text) · 1,024 (media caption) | Longer text is truncated                                                             |
| Discord         | 2,000                                | Longer text is truncated                                                             |

A few rules follow from the table:

* **Posting to several platforms at once?** A `caption` is rejected up front only when it's longer than *every* selected platform's limit. If it fits at least one, the request proceeds and each platform reports its own result — so set a `<platform>_caption` for any target with a tighter limit.
* **Per-platform overrides are validated too**, for the platforms with a hard cap: Instagram, LinkedIn, TikTok, YouTube, Reddit, Pinterest and Google Business. A `<platform>_title` (or `<platform>_caption`) over the cap — e.g. `tiktok_title` > 90 on a photo post, `reddit_title` > 300, or `instagram_caption` > 2,200 — is rejected with a clear error before publishing. X, Threads and Bluesky are not pre-checked because they auto-thread rather than reject, Telegram and Discord truncate, and a Facebook override is only capped on Reels — those go to the platform and report their own result.
* **Links on X.** Any URL X would turn into a clickable link is removed from X posts before publishing — put links in your profile or inside the image/video instead.

## Advanced platform options

Any option a platform supports can be passed through — MADIAD Hub forwards it untouched, so you're never limited to the fields listed here. Prefix the field with the platform name where the option is platform-specific. Common examples:

| Field                                               | Platform  | Effect                                                                 |
| --------------------------------------------------- | --------- | ---------------------------------------------------------------------- |
| `instagram_first_comment`                           | Instagram | Auto-post a first comment after publishing                             |
| `media_type`                                        | Instagram | `REELS`, `STORIES`, …                                                  |
| `privacy_level`                                     | TikTok    | `PUBLIC_TO_EVERYONE`, `SELF_ONLY`, …                                   |
| `disable_comment`, `disable_duet`, `disable_stitch` | TikTok    | Interaction toggles                                                    |
| `auto_add_music`                                    | TikTok    | Add background music to a photo post (`true`/`false`, default `false`) |
| `tags[]`                                            | YouTube   | Video tags                                                             |
| `privacyStatus`                                     | YouTube   | `public`, `unlisted`, `private`                                        |
| `visibility`                                        | LinkedIn  | Post visibility                                                        |
| `poll_options[]` + `poll_duration`                  | X         | Attach a poll                                                          |

Array fields use the `field[]` convention — repeat the key once per value.

For a full reference of every per-platform option — privacy levels, video types, polls, CTAs, subtitle upload, and more — see [Platform options](/concepts/platform-options).

## First comment

Auto-post a follow-up comment the instant a post publishes — handy for a link, hashtags, or a call to action you'd rather keep out of the main caption. Set `first_comment` as the shared default and override it per platform with `<platform>_first_comment` (the per-platform value wins where both are present).

```bash theme={null}
  -F "caption=New blend dropping today" \
  -F "first_comment=Grab it here 👉 link in our bio" \
  -F "instagram_first_comment=Tap the link in bio ☕ #specialtycoffee"
```

Supported on Instagram, Facebook, Threads, Bluesky, Reddit, X, YouTube, and LinkedIn. A few platform-specific behaviors:

* **X and Threads** — posted as a reply to the post. For an auto-threaded X post, it replies to the last tweet in the thread.
* **YouTube** — posted as a top-level comment on the video.
* **Instagram** — applies to photo and video posts only (Instagram has no text-only posts).

To attach images to the comment, send `first_comment_media[]` as binary file uploads, exactly like `photos[]`. Currently supported on **Reddit only**, and not available for scheduled or queued posts.

```bash theme={null}
  -F "first_comment=Full detail gallery below 👇" \
  -F "first_comment_media[]=@./detail-1.jpg" \
  -F "first_comment_media[]=@./detail-2.jpg"
```

## AI-generated captions

Let MADIAD Hub write the caption for you instead of supplying one. Most useful on photo and video posts:

```bash theme={null}
  -F "autogenerate=true" \
  -F "autogenerate_language=en"
```

| Field                      | Effect                                      |
| -------------------------- | ------------------------------------------- |
| `autogenerate`             | Generate both title and description         |
| `autogenerate_title`       | Generate the title only                     |
| `autogenerate_description` | Generate the description only               |
| `autogenerate_language`    | Force the output language (e.g. `en`, `vi`) |

## Platform requirements

<AccordionGroup>
  <Accordion title="Facebook publishes to a Page">
    Facebook posts go to a **Page**, not a personal profile. If the profile has exactly one Page it is used automatically; if it has several, pass `facebook_page_id` to choose one. To list a profile's Pages and find their IDs, use `GET /v1/connections/facebook/pages` — see [Discover Page & board IDs](/concepts/profiles#discover-page-board-ids).
  </Accordion>

  <Accordion title="Photos: upload files or pass URLs; video by URL">
    Send photos as binary file uploads (`photos[]=@file`) or as public links (`photo_urls[]`) — see [Send photos by URL](#send-photos-by-url). For video, pass a public `video_url`.
  </Accordion>

  <Accordion title="Reddit needs a title">
    Reddit posts always carry a title. By default the caption is used as the title; pass `reddit_title` to set a separate one.
  </Accordion>
</AccordionGroup>

## Read the response

A publish call returns the post `status`, the platforms it targeted, and the identifiers you poll with:

```json theme={null}
{
  "status": "processing",
  "profile_id": "prof_01HZX9F2K4M7N6QR8T0V2W4Y6Z",
  "platforms": ["instagram", "facebook", "tiktok"],
  "request_id": "req_01HZX9H6S8T0V2W4X6Y8Z0A2B4",
  "job_id": null,
  "results": null
}
```

* `completed` — published synchronously; `results` holds the per-platform outcome.
* `processing` — still uploading (common for video). Poll with `request_id`, or subscribe to [webhooks](/concepts/webhooks).
* `scheduled` — queued for a future time; poll with `job_id`.
* `partial` — some platforms published and at least one was rejected; check each entry in `results`.
* `failed` — every targeted platform rejected the post. The HTTP status is still `200`; the per-platform reasons are in `results`.
* `unknown` — the outcome could not be confirmed in time (returned with HTTP `502` and code `upstream_timeout`). The post may already be live — do **not** retry blindly; see [Errors](/concepts/errors) and [Idempotency](/concepts/idempotency#when-the-outcome-is-unknown).

Status polls (`GET /v1/posts/status`) use the same vocabulary.

When `results` is present it is keyed by platform. Each entry has `success`, plus `publish_id` and `url` when the post went out, or `error` and `error_code` when the platform refused it:

```json theme={null}
{
  "results": {
    "instagram": { "success": true, "publish_id": "17912345678901234", "url": "https://instagram.com/p/abc123" },
    "tiktok":    { "success": false, "error": "TikTok đã nhận 15/15 bài trong 24 giờ qua…", "error_code": "daily_cap_reached" }
  }
}
```

`error_code` is a short machine-readable reason (for example `account_restricted`, `daily_cap_reached`, `media_invalid_format`) — branch on it rather than on the wording of `error`. It is present only when the platform gave one.

The publish response and a status poll return this identical shape.

When some — but not all — platforms fail, the top-level `status` is `partial` and a `failed_platforms` array lists the ones that failed.

<Tip>
  A post can partially succeed — one platform publishes while another fails. Inspect `results` per platform rather than the top-level `status` alone.
</Tip>

## Check status later

Poll an asynchronous or scheduled post with the `request_id` or `job_id` from the publish response:

```bash theme={null}
curl "https://api.madiad.com/v1/posts/status?request_id=req_01HZX9H6S8T0V2W4X6Y8Z0A2B4" \
  -H "Authorization: Bearer $MADIAD_API_KEY"
```

For push updates instead of polling, see [Webhooks](/concepts/webhooks).
