# Films

`GET | POST | PATCH | DELETE /v1/films` — Film projects — a script, a storyboard and an edit, on the video engines.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: Free

A film is a PROJECT, never media: a title, a shared look (engine, format, film type, era, tempo, camera…) and ordered scenes (prompt, camera move, duration, cast). Up to 50 per account — the studio's Cinema saves them here. Renders always land in `/v1/creations`; a film only points at them.

Cinema writes `data` as its v2 project (`"v": 2`): `notes` — the film's own notes (never sent to an engine); `script` — the scenes' order (one `scene` block per scene; text blocks from older drafts are folded into notes when the studio opens them); `sequences` — the Story's scenes (one place at one time, filmed in several shots), in film order, each with its `heading` (the slugline, prepended to every shot's prompt), `cast` (character ids whose sheets ride each of its shots), `look` (what it changes of the film's look) and `notes`; `scenes` — the SHOTS, each with its `sequence` (the id of the scene it belongs to; a scene's shots sit next to each other; a shot without one starts a scene, or joins the one before when it has no `heading` of its own), `prompt` (the action), `dialogue` (`[{ speaker, manner, line }]` — `speaker` is `char:<id>` or a free name), `shot`, `seconds`, `cast`, its `notes` (never sent), its `link` — how it is made from the scene before: absent = a new shot, `{ "type": "continue" }` = the same shot going on (it opens on the last frame of the scene before), `{ "type": "angle" }` = the same scene from another angle (that frame rides as a reference picture) — and the takes it rendered (`options`: library ids, `pick` = the one in the cut); `edit` — the timeline (`clips` with in/out and transitions, free `audio` clips, `titles`). The API stores `data` as sent (400 KB max); deleting a film never touches the renders it produced.

**`POST /v1/films/{id}/render` shoots the whole storyboard in one call**: every scene with a prompt is queued as an async video job (charged per scene, like POST /v1/videos/generations), the response maps each scene to its job id (poll them) or to the error that kept it from queueing. On a v2 project each scene's slugline and its `dialogue` lines ride its prompt (`@Name (manner) says: "…"`), and every speaking cast member joins its references (their voice too, on engines that take one). Scenes fail independently — a drained wallet stops the remaining queue cleanly, and every charged failure refunds itself. One call queues the first 24 shots with a prompt; a longer film (up to 100 shots) is rendered from the studio.

**Media.** GET /v1/films/{id} returns `media`: every take, clip and sound the project points at, as fresh signed URLs (the Store is private); POST /v1/films/media re-signs a list of keys (`creation:<id>`, `upload:<id>`) during a long session. **The cut.** The studio renders the edit to one MP4 in the browser and saves it with POST /v1/films/{id}/export (raw `video/mp4` body, ≤ 95 MB, free — it counts against your storage); the saved cut is a library clip, and publishing it as the film's single segment puts the edited film on the community shelf.

The draft's `look` is applied to every scene: `model`, `aspect`, `resolution`, `audio`, `seed` and the director's rack (`filmType`, `era`, `tempo`, `cameraType`, `lens`, `aperture`, `palette`, `lighting`) — each scene adds its own prompt, `shot`, `seconds` and `cast`. Every engine takes the whole rack; a recurring cast keeps the same faces from scene to scene only on engines that read reference pictures (`references` in /v1/engines — Seedance, Kling, Veo 3.1, Hailuo 3, Wan). Start/end frames are per-clip controls of POST /v1/videos/generations, not part of a film. The studio's **links** between scenes are the same API used scene by scene: grab the previous clip's last frame, upload it (POST /v1/uploads) and send it as `references[0]` of the next POST /v1/videos/generations — as its first frame to continue the shot, or with `reference_mode: "identity"` for the same scene from another angle. POST /v1/films/{id}/render queues every scene at once, so it renders them as new shots.

**A film can go public.** `POST /v1/films/{id}/cover` (multipart `file`, image ≤ 5MB, free) sets its cover; `POST /v1/films/{id}/publish` puts the whole film on https://eroq.ai/community as one entry that plays the `segments` (your own hosted scene renders, in order) back to back — every URL must belong to a video in your library. `DELETE /v1/films/{id}/publish` makes it private again; likes and comments survive a re-publish. Deleting the draft deletes its community entry too (scene renders stay in the library).

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/films` | List your film drafts. |
| `POST` | `/v1/films` | Create a draft. |
| `GET` | `/v1/films/{id}` | One film, full recipe. |
| `PATCH` | `/v1/films/{id}` | Update title and/or data. |
| `DELETE` | `/v1/films/{id}` | Delete the draft — renders stay in the library. |
| `POST` | `/v1/films/media` | Fresh signed URLs for media keys (`creation:<id>`, `upload:<id>`, up to 300) — unknown or foreign keys are left out. |
| `POST` | `/v1/films/{id}/export` | Save the rendered cut (raw `video/mp4` body, `?seconds=`, ≤ 95 MB) to the library — free, counts against storage; replaces the film's previous unpublished cut. |
| `POST` | `/v1/films/{id}/render` | Shoot the whole storyboard: one async video job per scene, charged per scene, jobs to poll in the response. |
| `POST` | `/v1/films/{id}/cover` | Set the film's cover (multipart `file`, image ≤ 5MB). Free; shown on the community shelf and player. A new cover on an approved film sends it back to review (`review` in the response). |
| `POST` | `/v1/films/{id}/publish` | Submit the film to the community: segments (your own hosted scene renders) play in order. It goes public once our team approves it — `review` starts at `pending` (readable on GET /v1/films/{id}); publishing again keeps the verdict unless the title, scenes or cover change. |
| `DELETE` | `/v1/films/{id}/publish` | Take the film off the community shelf — engagement is kept for a re-publish. |

## GET /v1/films

List your film drafts.

### Example request

```bash
curl https://eroq.ai/v1/films \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/films', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/films", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/films", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "films": [{
    "id": "7f2a…", "title": "Rooftop night",
    "scenes": 3, "updatedAt": "2026-09-11T14:02:11Z"
  }]
}
```

## POST /v1/films

Create a draft.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | no | Up to 80 characters. |
| `data` | object | no | The recipe, stored verbatim: { look: {…}, scenes: [{ prompt, shot, seconds, cast }] }. |

### Example request

```bash
curl https://eroq.ai/v1/films \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Rooftop night",
  "data": {
    "look": {
      "filmType": "noir",
      "aspect": "16:9"
    },
    "scenes": [
      {
        "prompt": "slow pan over a rooftop bar at dusk",
        "seconds": 5
      }
    ]
  }
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/films', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "title": "Rooftop night",
    "data": {
      "look": {
        "filmType": "noir",
        "aspect": "16:9"
      },
      "scenes": [
        {
          "prompt": "slow pan over a rooftop bar at dusk",
          "seconds": 5
        }
      ]
    }
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/films",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "title": "Rooftop night",
        "data": {
            "look": {
                "filmType": "noir",
                "aspect": "16:9"
            },
            "scenes": [
                {
                    "prompt": "slow pan over a rooftop bar at dusk",
                    "seconds": 5
                }
            ]
        }
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "title": "Rooftop night",
  "data": {
    "look": {
      "filmType": "noir",
      "aspect": "16:9"
    },
    "scenes": [
      {
        "prompt": "slow pan over a rooftop bar at dusk",
        "seconds": 5
      }
    ]
  }
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "film": {
    "id": "7f2a…", "title": "Rooftop night",
    "data": { "look": { "filmType": "noir", "aspect": "16:9" }, "scenes": [{ "prompt": "slow pan over a rooftop bar at dusk", "seconds": 5 }] },
    "updatedAt": "2026-09-11T14:02:11Z"
  }
}
```

## GET /v1/films/{id}

One film, full recipe.

### Example request

```bash
curl https://eroq.ai/v1/films/FILM_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/films/FILM_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/films/FILM_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "film": {
    "id": "7f2a…", "title": "Rooftop night",
    "data": { "look": { "filmType": "noir", "aspect": "16:9" }, "scenes": [{ "prompt": "slow pan over a rooftop bar at dusk", "seconds": 5 }] },
    "coverUrl": "", "updatedAt": "2026-09-11T14:02:11Z",
    "published": { "creationId": "3d91…", "publishedAt": "2026-09-11T14:12:40Z", "review": "pending", "reviewNote": null }
  }
}
```

## PATCH /v1/films/{id}

Update title and/or data.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | no | New title, up to 80 characters. |
| `data` | object | no | The whole recipe, replaced verbatim. Omit to keep the stored one. |

### Example request

```bash
curl -X PATCH https://eroq.ai/v1/films/FILM_ID \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Rooftop night — cut 2"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "title": "Rooftop night — cut 2"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.patch(
    "https://eroq.ai/v1/films/FILM_ID",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "title": "Rooftop night — cut 2"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "title": "Rooftop night — cut 2"
}`)

	req, _ := http.NewRequest("PATCH", "https://eroq.ai/v1/films/FILM_ID", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "film": { "id": "7f2a…", "title": "Rooftop night — cut 2", "data": { … }, "updatedAt": "2026-09-11T14:09:02Z" }
}
```

## DELETE /v1/films/{id}

Delete the draft — renders stay in the library.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/films/FILM_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/films/FILM_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/films/FILM_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true }
```

## POST /v1/films/media

Fresh signed URLs for media keys (`creation:<id>`, `upload:<id>`, up to 300) — unknown or foreign keys are left out.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `keys` | array | yes | Media keys to sign. |

### Example request

```bash
curl https://eroq.ai/v1/films/media \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "keys": [
    "creation:5c1d…",
    "upload:9f2c41ab77d0e1c3"
  ]
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/media', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "keys": [
      "creation:5c1d…",
      "upload:9f2c41ab77d0e1c3"
    ]
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/films/media",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "keys": [
            "creation:5c1d…",
            "upload:9f2c41ab77d0e1c3"
        ]
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "keys": [
    "creation:5c1d…",
    "upload:9f2c41ab77d0e1c3"
  ]
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films/media", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "media": {
    "creation:5c1d…": { "url": "https://cdn.eroq.ai/…?token=…&expires=…", "contentType": "video/mp4", "kind": "video", "seconds": 5, "name": "Scene 1" }
  }
}
```

## POST /v1/films/{id}/export

Save the rendered cut (raw `video/mp4` body, `?seconds=`, ≤ 95 MB) to the library — free, counts against storage; replaces the film's previous unpublished cut.

### Example request

```bash
curl -X POST https://eroq.ai/v1/films/FILM_ID/export \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID/export', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post("https://eroq.ai/v1/films/FILM_ID/export", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films/FILM_ID/export", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "export": { "creationId": "a17b…", "seconds": 42.5, "at": "2026-09-28T16:02:11Z", "signature": "9c1f02ab" },
  "creation": { "id": "a17b…", "url": "https://cdn.eroq.ai/…/film.mp4?token=…", "bytes": 18230411, "seconds": 42.5, "rating": "sfw" }
}
```

## POST /v1/films/{id}/render

Shoot the whole storyboard: one async video job per scene, charged per scene, jobs to poll in the response.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `store` | boolean | no | Persist every clip to the eroq Store (charged at the Store's rate). Default `false`. |
| `folder_id` | string | no | Library folder every scene's clip lands in — one of yours (GET /v1/folders). Omitted = the Library root; an unknown id answers 404 `folder_not_found` before anything is billed. |

### Example request

```bash
curl https://eroq.ai/v1/films/FILM_ID/render \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "store": true
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID/render', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "store": true
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/films/FILM_ID/render",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "store": True
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "store": true
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films/FILM_ID/render", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "id": "7f2a…",
  "object": "film.render",
  "title": "Rooftop night",
  "scenes": [
    { "scene": 0, "id": "b7e6c2d4-…", "status": "processing", "model": "eroq-motion-one", "duration": "5s", "poll": "/v1/videos/generations/b7e6c2d4-…" },
    { "scene": 1, "status": "failed", "error": { "message": "Not enough credits", "type": "insufficient_credits", "code": "insufficient_credits" } }
  ],
  "queued": 1,
  "usage": { "credits_spent": 100, "credits_remaining": 787 }
}
```

## POST /v1/films/{id}/cover

Set the film's cover (multipart `file`, image ≤ 5MB). Free; shown on the community shelf and player. A new cover on an approved film sends it back to review (`review` in the response).

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | file | yes | Cover image, up to 5MB. |

### Example request

```bash
curl https://eroq.ai/v1/films/FILM_ID/cover \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -F file=@cover.webp
```

```javascript
import { readFile } from 'node:fs/promises'

const form = new FormData()
form.append('file', new Blob([await readFile('cover.webp')]), 'cover.webp')

const res = await fetch('https://eroq.ai/v1/films/FILM_ID/cover', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
  body: form,
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/films/FILM_ID/cover",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    files={"file": open("cover.webp", "rb")},
)
print(res.json())
```

```go
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)
	part0, _ := form.CreateFormFile("file", "cover.webp")
	file0, _ := os.Open("cover.webp")
	io.Copy(part0, file0)
	file0.Close()
	form.Close()

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films/FILM_ID/cover", &buf)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", form.FormDataContentType())

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true, "coverUrl": "https://store.eroq.ai/…/cover.webp", "review": "pending" }
```

## POST /v1/films/{id}/publish

Submit the film to the community: segments (your own hosted scene renders) play in order. It goes public once our team approves it — `review` starts at `pending` (readable on GET /v1/films/{id}); publishing again keeps the verdict unless the title, scenes or cover change.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `segments` | array | yes | Ordered clip URLs (1–24). Each must be a hosted render from your own library — no foreign media. |
| `title` | string | no | Public title, up to 120 characters. Defaults to the draft's title. |
| `durationSeconds` | number | no | Total runtime shown as the duration badge. |

### Example request

```bash
curl https://eroq.ai/v1/films/FILM_ID/publish \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Rooftop night",
  "segments": [
    "https://cdn.eroq.ai/library/…/scene-1.mp4",
    "https://cdn.eroq.ai/library/…/scene-2.mp4"
  ],
  "durationSeconds": 12
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID/publish', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "title": "Rooftop night",
    "segments": [
      "https://cdn.eroq.ai/library/…/scene-1.mp4",
      "https://cdn.eroq.ai/library/…/scene-2.mp4"
    ],
    "durationSeconds": 12
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/films/FILM_ID/publish",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "title": "Rooftop night",
        "segments": [
            "https://cdn.eroq.ai/library/…/scene-1.mp4",
            "https://cdn.eroq.ai/library/…/scene-2.mp4"
        ],
        "durationSeconds": 12
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "title": "Rooftop night",
  "segments": [
    "https://cdn.eroq.ai/library/…/scene-1.mp4",
    "https://cdn.eroq.ai/library/…/scene-2.mp4"
  ],
  "durationSeconds": 12
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/films/FILM_ID/publish", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "ok": true, "id": "3d91…", "title": "Rooftop night",
  "publishedAt": "2026-09-11T14:12:40Z", "handle": "nova", "review": "pending"
}
```

## DELETE /v1/films/{id}/publish

Take the film off the community shelf — engagement is kept for a re-publish.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/films/FILM_ID/publish \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/films/FILM_ID/publish', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/films/FILM_ID/publish", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/films/FILM_ID/publish", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true }
```

---
Canonical: https://eroq.ai/docs/films · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
