# Media

Create a video asset, upload the file to its one-time upload URL, and play it over HLS when it is ready. Live channels get an RTMPS ingest URL and a stream key; point your encoder at it and go live.

Videos and live channels are on Pro and up. On a plan without them, creating one answers 402 `plan_limit` with `required_plan`, and nothing is created. A workspace that still holds assets from a plan that had them gets the same answer when it asks for an upload URL or takes a channel live; videos already uploaded keep playing, and a stream can still be ended.

## Details

- Video only, up to 1 hour per upload. Kinds "image" and "audio" answer 501 — keep those files in Storage.
- Status runs pending_upload → processing → ready. Size and duration appear once the file is processed.
- An expired upload URL is replaced with a fresh one (`POST /api/media/:id/upload`); once a file has arrived, no new URL is issued.
- Stream keys and upload URLs are visible to developers and above, never to viewers.
- Live channels do not record.
- Assets per plan: 100 pro, 1,000 enterprise. Each uploaded video and each live channel counts as one.

## Reference

**SDK**

```js
const { asset, uploadUrl } = await hk.media.create(project.id, { name: 'promo', kind: 'video' });
// POST multipart/form-data with field "file" to uploadUrl, then poll:
const { asset: ready } = await hk.media.get(asset.id);   // pending_upload → processing → ready
const live = await hk.media.create(project.id, { name: 'launch', kind: 'live' });   // ingest URL + stream key
```

**REST**

```http
POST  /api/projects/:id/media    { "name": "promo", "kind": "video" | "live" }
POST  /api/media/:id/upload      # a fresh upload URL while the asset waits for its file
GET   /api/media/:id             # status, size, duration, playback URL
PATCH /api/media/:id             { "action": "go-live" | "end-stream" }
```
