<!-- https://cutedyno.com/docs/publishing -->

# Publishing

Drafts, schedules, per-platform options, and what to do when one account fails.

One post can target several accounts on several platforms. CuteDyno accepts it as a single object, then fans it out — each account is published independently, with that platform's rules applied.

That independence is the thing to design around. A post is not one operation that succeeds or fails; it is several, and they can disagree.

## The three modes

There is no `mode` parameter. What you send decides:

```typescript
// Draft — saved, nothing goes out
await cutedyno.posts.create({ content: 'Rough idea', accountIds });

// Scheduled
await cutedyno.posts.create({
  content: 'Autumn range is live.',
  accountIds,
  scheduledAt: '2026-09-01T09:00:00Z',
});

// Now
await cutedyno.posts.create({ content: 'We are live.', accountIds, publishNow: true });
```

Sending both `scheduledAt` and `publishNow` is rejected rather than guessed at. A `scheduledAt` in the past publishes immediately; more than 180 days out is refused.

A draft may omit `accountIds` entirely — useful when a human picks the accounts later. Scheduling or publishing without them is an error, since there is nowhere to send it.

## Status, and what each one means

```text
draft ──────────┐
                ├─→ scheduled ─→ queued ─→ processing ─→ published
pending_approval ┘                                    ├─→ partially_published
                                                      └─→ failed
```

`queued` means a worker has it. `processing` means bytes are moving. The gap between `scheduled` and `published` depends on the platform and the size of the media, so treat publishing as asynchronous even when you asked for it now.

The full table is in the [API overview](/docs/api#status-values).

## Per-platform options

The same text rarely suits every network, and some platforms require fields the others do not have. `platforms` carries options that apply only to accounts on that platform:

```typescript
await cutedyno.posts.create({
  content: 'Behind the scenes of the shoot.',
  media: [{ type: 'video', url: videoUrl }],
  accountIds: [tiktokId, youtubeId, linkedinId],
  platforms: {
    tiktok: {
      title: 'Behind the scenes',
      privacy_level: 'PUBLIC_TO_EVERYONE',
      disable_duet: true,
    },
    youtube: {
      title: 'Behind the scenes of the autumn shoot',
      privacyStatus: 'public',
      tags: ['fashion', 'bts'],
    },
    linkedin: {
      visibility: 'PUBLIC',
    },
  },
});
```

Options for a platform you are not posting to are ignored, so a single template can carry all of them. YouTube is the one platform with a required option: it will not accept a video without `platforms.youtube.title`.

See the [platform guides](/docs/platforms) for the full option list and media specs per network.

## Validate first

[`POST /v1/posts/validate`](/docs/api/validate-post) runs the same checks as create and writes nothing:

```typescript
const result = await cutedyno.posts.validate({
  content: 'Autumn range is live.',
  accountIds,
  scheduledAt: '2026-09-01T09:00:00Z',
});

result.valid;    // false if it would be rejected
result.errors;   // same codes and messages create would return
result.warnings; // e.g. "This profile requires approval…"
```

Use it to drive a form in your own UI, and read `warnings` — that is where you learn that a post will go to review instead of publishing, before your user wonders why nothing happened.

## Editing and cancelling

Drafts, scheduled, queued, and failed posts can be edited. Published, processing, and partially published posts cannot — the content is already on a platform, and CuteDyno will not pretend otherwise.

```typescript
await cutedyno.posts.update(postId, { scheduledAt: '2026-09-02T09:00:00Z' });
await cutedyno.posts.cancel(postId); // scheduled or queued only
```

Editing the content of a post that required approval sends it back for review. Approval attaches to what was approved, not to the post ID.

## When one account fails

```typescript
const { post } = await cutedyno.posts.retrieve(postId);

if (post.status === 'partially_published') {
  for (const result of post.results) {
    if (result.status === 'failed') {
      console.error(result.platform, result.pageName, result.error);
    }
  }
}
```

Then requeue only what failed:

```typescript
await cutedyno.posts.retry(postId);
```

Retry skips accounts that already succeeded, so it cannot double-post. It accepts `failed` and `partially_published` posts only.

Not every failure is worth retrying. A revoked token needs the user to [reconnect](/docs/guides/connecting-accounts); a video too long for TikTok needs a different video. Read `result.error` before retrying in a loop — the same request will fail the same way.

## Don't poll

Polling every post until it settles works, and wastes most of your [rate limit](/docs/guides/rate-limits) discovering that nothing has changed. Subscribe once instead:

```typescript
await cutedyno.webhooks.create({
  url: 'https://yourapp.com/webhooks/cutedyno',
  events: [
    'post.published',
    'post.partially_published',
    'post.failed',
  ],
});
```

The payload carries the per-account `results`, so you usually do not need to fetch the post at all. See [Webhooks](/docs/webhooks).

## Listing history

```typescript
const { posts, total, hasMore } = await cutedyno.posts.list({
  status: 'failed',
  platform: 'tiktok',
  page: 1,
  limit: 50,
});
```

Or let the SDK walk the pages:

```typescript
for await (const post of cutedyno.posts.iterate({ status: 'failed' })) {
  const { history } = await cutedyno.posts.history(post.id);
  console.log(post.id, history.at(-1)?.reason);
}
```

`history` is the per-post audit trail: every transition, who caused it, and the reason recorded at the time. It is the first place to look when someone asks why a post did not go out.

## Next

- [Media uploads](/docs/guides/media-uploads) — images and video
- [Platform guides](/docs/platforms) — per-network rules
- [Idempotency](/docs/guides/idempotency) — safe retries
- [Agent governance](/docs/guides/agent-governance) — approvals and scoped keys
