# Publish a video end to end

> Step by step: publish a video with the Praeko API. Upload the media, create a post with one item per network, pick the queue or a fixed time, and check the result.

Source: https://praeko.app/docs/en/guides/publish-a-video

The full path, from a file on disk to an item published on the network. Every step links to its route in the [reference](https://praeko.app/docs/en/api).

1. **Media**: Create the media and upload the bytes (`/v1/media`).
2. **Post**: One item per network, with the queue, a fixed time or a draft (`/v1/posts`).
3. **Praeko**: Publishes on time and retries if the network fails.
4. **Your code**: Reads the post state or receives a webhook.

### 1. Check your channels

`GET /v1/channels` lists the connected channels and how many time slots each one has. Connect networks in the panel, under Channels; an entry with `connected: false` is not connected yet.

**curl**

```bash
curl https://praeko.app/v1/channels \
  -H "Authorization: Bearer $PRAEKO_KEY" -H "X-Praeko-Contract: en"
```

### 2. Upload the video

`POST /v1/media` creates the media. Up to 50 MiB, with `simple: true`, send the whole file with `PUT /v1/media/:id/content`. Above that, upload in parts: see [Upload media](https://praeko.app/docs/en/guides/media).

**curl**

```bash
curl -X POST https://praeko.app/v1/media \
  -H "Authorization: Bearer $PRAEKO_KEY" -H "X-Praeko-Contract: en" \
  -H "Content-Type: application/json" \
  -d '{ "type": "video", "name": "v.mp4", "mime": "video/mp4", "size": 8123456, "simple": true }'
# response: { "id": "…", "state": "uploading", … }
curl -X PUT https://praeko.app/v1/media/$MEDIA_ID/content \
  -H "Authorization: Bearer $PRAEKO_KEY" -H "Content-Type: video/mp4" \
  --data-binary @v.mp4
```

### 3. Create the post

A post has one item per network. Each item takes the channel's next free slot (`queue: true`), a fixed time (`scheduledFor`, ISO 8601 UTC) or stays a draft (`draft: true`). Each network's text goes in `texts`. `mode` sets the visibility: YouTube `private`, `scheduled` or `public`; TikTok `draft` or `direct`; Instagram `now`.

**curl**

```bash
curl -X POST https://praeko.app/v1/posts \
  -H "Authorization: Bearer $PRAEKO_KEY" -H "X-Praeko-Contract: en" \
  -H "Content-Type: application/json" \
  -d '{ "title": "My video",
        "media": { "video": "'$MEDIA_ID'" },
        "texts": { "youtube": { "text": "Description" }, "tiktok": { "text": "Caption" } },
        "items": [
          { "network": "youtube", "queue": true },
          { "network": "tiktok", "scheduledFor": "2026-11-10T20:30:00Z" }
        ] }'
```

The response is `201` with the post `id` and one item per network, each with `state` and `scheduledFor`.

### 4. Check the result

`GET /v1/posts/:id` returns the post with its items. An item's `state` is `draft`, `scheduled`, `sent`, `private` (on YouTube: uploaded as private, waiting for you), `error` or `canceled`. Instead of polling, [register a webhook](https://praeko.app/docs/en/guides/webhooks): `post.sent` tells you when it went out and `post.failed` when the item gave up.

## After you create it

| I want to | Route |
|---|---|
| Change the title, text or time, or move it to drafts | [`PATCH /v1/posts/:id`](https://praeko.app/docs/en/api/posts/update-post) |
| Publish a queued item now | [`POST /v1/posts/:id/publish-now`](https://praeko.app/docs/en/api/posts/publish-now) |
| Cancel the pending items | [`DELETE /v1/posts/:id`](https://praeko.app/docs/en/api/posts/cancel-post) |
| See what is scheduled and the free slots | [`GET /v1/calendar`](https://praeko.app/docs/en/api/calendar/calendar) |

> **YouTube video already uploaded and still scheduled** When Praeko has already uploaded the video as private with a publish time, `PATCH /v1/posts/:id` can still change the time (at least 5 minutes ahead), the title, the description and the pinned comment, checking the video live; the response has `itemResults` per item. A video that is already public returns `409 video_not_private`; a time that has passed, `409 not_editable`. Canceling with `DELETE` does **not** hold that video back: YouTube publishes it on schedule. Use `?dismiss=true` to remove the publish time (it stays private) or `?dismiss=true&delete_video=true` to delete it.

> **Swapping the media of a scheduled post** Changing the video of a post that is already scheduled returns `409 media_locked`; deleting media used by a pending post returns `409 media_in_use`.

> **Resending the POST creates another post** Send the `Idempotency-Key` header and the retry gets the first response without creating another post: [see how](https://praeko.app/docs/en/guides/idempotency). Without the header, send your own `id` (a repeat returns `409 id_exists`).
