# Posts and scheduling

`POST /posts` schedules a post to one or more connected accounts. It needs the `posts:write` scope, is rate limited to 60 requests per 300 seconds, and supports the `Idempotency-Key` header.

## Three content sources

The body must provide exactly one content source; they are checked in this order:

1. `suggestionId`: publish a generated suggestion from `POST /generations` (the usual path)
2. `deck` + `format`: publish a raw deck object (advanced; `format` is required when `deck` is provided)
3. `videoUrl` or `images`: publish your own media as-is; URLs must be publicly reachable (`thumbnailUrl` is optional for videos)

`caption` and `hashtags` are optional; for suggestion-based posts they default to the suggestion's own values.

## Target accounts

`selectedAccounts` is required: an object of platform keys mapping to arrays of account ids from `GET /accounts`, with at least one non-empty platform array.

```json
{
	"selectedAccounts": {
		"tiktok": ["665f1b2a9c31a2b3c4d5e700"],
		"youtube": ["665f1b2a9c31a2b3c4d5e711"]
	}
}
```

Connecting accounts (OAuth) happens in the web app, not through the API. Other optional fields: `ownerType` (`brand` or `influencer`; `influencerId` is required when `influencer`), `tiktokSendToInbox` to send a TikTok draft instead of publishing, and `workspaceId`.

## asap vs scheduled

- `schedule: "asap"` posts as soon as the render is ready
- `schedule: "scheduled"` requires a `scheduledTime` in the future; pass `timezone` as an IANA name (default UTC)

```bash
viraloop posts create --suggestion <id> --accounts tiktok:<accountId> \
  --at "2026-07-04T15:00:00Z" --json
```

## 202 and polling

Deck-based posts are rendered server-side after the call returns, so the response is `202` with a `statusUrl`:

```json
{
	"success": true,
	"data": {
		"id": "665f1b2a9c31a2b3c4d5e702",
		"status": "scheduled",
		"renderStatus": "pending",
		"statusUrl": "/api/v1/posts/665f1b2a9c31a2b3c4d5e702"
	}
}
```

Poll `GET /posts/{id}` (scope `posts:read`) until a terminal state:

- Terminal `status` values: `posted`, `partial`, `completed`, `failed`
- A `renderStatus` of `failed` is also terminal

The post includes per-platform results: status, `postUrl` and an analytics snapshot (views, likes, comments, shares). With the CLI, `viraloop posts create --wait` does the polling for you; do not use `--wait` for far-future posts, it waits until the post actually publishes.

## Cancelling

`DELETE /posts/{id}` cancels a post that has not been published yet (status `scheduled`). Published or in-flight posts cannot be cancelled; that returns `409` with error type `conflict`.

```bash
viraloop posts cancel <postId> --json
```

## Calendar

`GET /calendar` returns the workspace's posts in a date window; `from` and `to` are required (ISO 8601), `status` is optional. Check what is already queued before scheduling more:

```bash
viraloop calendar --from 2026-07-06 --to 2026-07-13 --json
```

## Idempotency

Send an `Idempotency-Key` header (any unique string) on `POST /posts` to make retries safe: a successful response is stored and replayed if you retry with the same key and body, so a network timeout cannot double-post. Reusing a key with a different body returns `409`. See [errors and rate limits](/developers/docs/errors-and-rate-limits).
