# createPost — Postify API

`POST /v1/posts`

Create a post. Creates a post with one variant per target channel. Exactly one mode is required: `draft: true` (save only), `scheduled_at` (future publish), or `publish_now: true` (immediate — requires the workspace's dangerous-operations toggle for API callers). Send an `Idempotency-Key` header to make retries safe.

- Required scope: `posts:write`
- Auth: `Authorization: Bearer postify_live_…` or `x-api-key` header
- Idempotency: send an `Idempotency-Key` header (1–255 printable ASCII chars, e.g. a UUID). Replays of the same key + body return the recorded response with `Idempotency-Replayed: true`; the same key with a different body is rejected with 422 `idempotency_key_reused`. Ledger window: 24h.

## Request body

- `variants` (array of object, required) — One variant per target channel.
  - `channel_id` (string, required) — A connected channel's `id` (see /v1/channels).
  - `body` (string, required) — The text to publish to this channel.
  - `media` (array of PostMediaItem) — Media attachments for this channel.
- `title` (string | null) — Internal title (not published).
- `body` (string | null) — Optional channel-agnostic base text.
- `draft` (boolean) — Save as a draft (no scheduling, no publishing).
- `scheduled_at` (string (ISO-8601)) — Future publish time (ISO-8601). Mutually exclusive with `draft` and `publish_now`.
- `publish_now` (boolean) — Publish immediately. Requires the workspace's dangerous-operations toggle for API callers.

## Responses

- `201` — The created post. (schema: `Post`)
- `400` — [`invalid_request`](https://usepostify.com/docs/api/problems/invalid-request.md), [`validation_failed`](https://usepostify.com/docs/api/problems/validation-failed.md)
- `401` — [`authentication_required`](https://usepostify.com/docs/api/problems/authentication-required.md), [`invalid_api_key`](https://usepostify.com/docs/api/problems/invalid-api-key.md)
- `403` — [`insufficient_scope`](https://usepostify.com/docs/api/problems/insufficient-scope.md), [`feature_not_enabled`](https://usepostify.com/docs/api/problems/feature-not-enabled.md), [`dangerous_ops_disabled`](https://usepostify.com/docs/api/problems/dangerous-ops-disabled.md)
- `409` — [`idempotency_in_progress`](https://usepostify.com/docs/api/problems/idempotency-in-progress.md)
- `422` — [`idempotency_key_reused`](https://usepostify.com/docs/api/problems/idempotency-key-reused.md)
- `429` — [`rate_limited`](https://usepostify.com/docs/api/problems/rate-limited.md), [`quota_exhausted`](https://usepostify.com/docs/api/problems/quota-exhausted.md)
- `500` — [`internal_error`](https://usepostify.com/docs/api/problems/internal-error.md)

Errors are RFC 9457 `application/problem+json` with a stable `code` and a `request_id`. Full schemas: [interactive reference](https://usepostify.com/docs/api-reference) · [OpenAPI spec](https://app.usepostify.com/v1/openapi.json).

## Example (curl)

```bash
curl -X POST https://app.usepostify.com/v1/posts \
  -H "Authorization: Bearer postify_live_XXXXXXXXXXXXXXXX" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "variants": [
    {
      "channel_id": "ch_9x2y3z0001",
      "body": "Shipping day 🚀 Our new analytics dashboard is live — see what actually lands with your audience."
    }
  ],
  "scheduled_at": "2026-08-01T09:00:00Z"
}'
```

## Example (TypeScript)

```ts
const res = await fetch("https://app.usepostify.com/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POSTIFY_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "variants": [
        {
          "channel_id": "ch_9x2y3z0001",
          "body": "Shipping day 🚀 Our new analytics dashboard is live — see what actually lands with your audience."
        }
      ],
      "scheduled_at": "2026-08-01T09:00:00Z"
    }),
});

if (!res.ok) {
  // RFC 9457 problem+json: branch on the stable `code`.
  const problem = await res.json();
  throw new Error(`${problem.code}: ${problem.detail ?? problem.title}`);
}
const data = await res.json();
```
