> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.empiriolabs.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.empiriolabs.ai/_mcp/server.

# Generation Templates

Generation Templates includes two related surfaces:

* **Effect templates**: pre-curated creative effects for `/v1/images/generations` and `/v1/videos/generations`. Pass `template: "<slug>"` and EmpirioLabs applies the tuned effect to a normal generation request.
* **Compose recipes**: full-production short-form video workflows. Compose plans scenes, creates or selects visuals, generates voiceover, captions, optional music, and returns a finished MP4.

The same catalog powers the **Templates** button in the [dashboard Playground](https://platform.empiriolabs.ai/dashboard/playground?templates=open), where Effects and Compose appear as top-level tabs. Everything on this page can also be run visually there, with live previews for each effect and recipe; open the [Compose tab](https://platform.empiriolabs.ai/dashboard/playground?templates=open\&compose=open) directly for the full-production recipes.

## List templates

### Request

GET [https://api.empiriolabs.ai/v1/templates](https://api.empiriolabs.ai/v1/templates)

```curl
curl https://api.empiriolabs.ai/v1/templates \
     -H "Authorization: Bearer <token>"
```

```python
import requests

url = "https://api.empiriolabs.ai/v1/templates"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.empiriolabs.ai/v1/templates';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/templates"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/templates")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.empiriolabs.ai/v1/templates")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.empiriolabs.ai/v1/templates', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/templates");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/templates")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

Use the generic endpoint for the full image and video catalog, or the modality-specific endpoints when you already know the generation type.

```bash
curl https://api.empiriolabs.ai/v1/templates?modality=image \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

## List video templates

### Request

GET [https://api.empiriolabs.ai/v1/videos/templates](https://api.empiriolabs.ai/v1/videos/templates)

```curl
curl https://api.empiriolabs.ai/v1/videos/templates \
     -H "Authorization: Bearer <token>"
```

```python
import requests

url = "https://api.empiriolabs.ai/v1/videos/templates"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.empiriolabs.ai/v1/videos/templates';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/videos/templates"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/videos/templates")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.empiriolabs.ai/v1/videos/templates")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.empiriolabs.ai/v1/videos/templates', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/videos/templates");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/videos/templates")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Filters

* `category`: `viral`, `cinematic`, `motion`, `transform`, `social`, `extend`, `product`, `edit`, `portrait`
* `modality`: `video` or `image`
* `model`: only return templates that support a specific model slug
* `featured`: `true` to filter to featured templates only

```bash
curl https://api.empiriolabs.ai/v1/videos/templates?category=viral \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

### Response shape

```json
{
  "object": "list",
  "template_count": 11,
  "data": [
    {
      "slug": "baseball-stadium",
      "display_name": "Stadium",
      "category": "viral",
      "description": "Customer-facing description shown in the playground card.",
      "recommended_model": "kling-o3",
      "supported_models": ["kling-o3"],
      "default_params": { "aspect_ratio": "16:9", "duration": 10 },
      "required_inputs": { "image": true, "min_images": 1, "max_images": 1 },
      "cover_image_url": "https://media.empiriolabs.ai/assets/template-posters/baseball-stadium.jpg",
      "preview_video_url": "https://media.empiriolabs.ai/assets/template-previews/baseball-stadium.mp4",
      "modality": "video",
      "is_featured": true,
      "display_order": 10
    }
  ]
}
```

## List image templates

### Request

GET [https://api.empiriolabs.ai/v1/images/templates](https://api.empiriolabs.ai/v1/images/templates)

```curl
curl https://api.empiriolabs.ai/v1/images/templates \
     -H "Authorization: Bearer <token>"
```

```python
import requests

url = "https://api.empiriolabs.ai/v1/images/templates"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.empiriolabs.ai/v1/images/templates';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/images/templates"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/images/templates")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.empiriolabs.ai/v1/images/templates")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.empiriolabs.ai/v1/images/templates', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/images/templates");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/images/templates")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```bash
curl https://api.empiriolabs.ai/v1/images/templates?model=seedream-5-0-lite \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

## Compose

Compose is the full-production video workflow. Use it when you want a complete short-form video from a brief, script, product idea, or explainable topic. Use effect templates when you want one curated effect applied to a normal image or video generation request.

### List Compose recipes

### Request

GET [https://api.empiriolabs.ai/v1/videos/compose/recipes](https://api.empiriolabs.ai/v1/videos/compose/recipes)

**`default`**

```curl default
curl https://api.empiriolabs.ai/v1/videos/compose/recipes
```

**`default`**

```python default
import requests

url = "https://api.empiriolabs.ai/v1/videos/compose/recipes"

response = requests.get(url)

print(response.json())
```

**`default`**

```javascript default
const url = 'https://api.empiriolabs.ai/v1/videos/compose/recipes';
const options = {method: 'GET'};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**`default`**

```go default
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/videos/compose/recipes"

	req, _ := http.NewRequest("GET", url, nil)

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`default`**

```ruby default
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/videos/compose/recipes")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)

response = http.request(request)
puts response.read_body
```

**`default`**

```java default
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.empiriolabs.ai/v1/videos/compose/recipes")
  .asString();
```

**`default`**

```php default
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.empiriolabs.ai/v1/videos/compose/recipes');

echo $response->getBody();
```

**`default`**

```csharp default
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/videos/compose/recipes");
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
```

**`default`**

```swift default
import Foundation

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/videos/compose/recipes")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```bash
curl https://api.empiriolabs.ai/v1/videos/compose/recipes
```

The response includes public recipe metadata:

* `slug`: pass this as `recipe` on `POST /v1/videos/compose`
* `display_name`, `category`, `visual_mode`, and `audio_mode`
* `scene_count`, `aspect_ratios`, and `default_caption_style`
* `requires_face_image` and `accepts_product_image`
* `models`: recommended and allowed model options by stage

Model recommendations are read from the live Compose recipe catalog. If you override a stage model, use a value listed in that recipe's `models.<stage>.options`.

### Plan a storyboard

Use `mode: "plan"` to turn a topic or script into an editable storyboard. This is the fastest way to preview structure before spending on a full render.

### Request

POST [https://api.empiriolabs.ai/v1/videos/compose](https://api.empiriolabs.ai/v1/videos/compose)

**`default`**

```curl default
curl -X POST https://api.empiriolabs.ai/v1/videos/compose \
     -H "Authorization: Bearer <token>"
```

**`default`**

```python default
import requests

url = "https://api.empiriolabs.ai/v1/videos/compose"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

**`default`**

```javascript default
const url = 'https://api.empiriolabs.ai/v1/videos/compose';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**`default`**

```go default
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/videos/compose"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`default`**

```ruby default
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/videos/compose")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

**`default`**

```java default
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.empiriolabs.ai/v1/videos/compose")
  .header("Authorization", "Bearer <token>")
  .asString();
```

**`default`**

```php default
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.empiriolabs.ai/v1/videos/compose', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

**`default`**

```csharp default
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/videos/compose");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

**`default`**

```swift default
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/videos/compose")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```bash
curl https://api.empiriolabs.ai/v1/videos/compose \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipe": "custom",
    "mode": "plan",
    "brief": "Explain why cold plunge clips went viral.",
    "aspect_ratio": "9:16",
    "scene_count": 5,
    "caption_style": "bold-white"
  }'
```

Compose returns an async job:

```json
{
  "job_id": "job_01HV3KABCDE",
  "status": "processing",
  "poll_url": "/v1/jobs/job_01HV3KABCDE"
}
```

Poll the job:

```bash
curl https://api.empiriolabs.ai/v1/jobs/job_01HV3KABCDE \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

When the plan job completes, `result` contains a storyboard. You can edit that JSON before render.

### Render the video

Send the storyboard back with `mode: "render"`.

```bash
curl https://api.empiriolabs.ai/v1/videos/compose \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipe": "custom",
    "mode": "render",
    "storyboard": {
      "title": "Why cold plunge clips took over",
      "aspect_ratio": "9:16",
      "caption_style": "bold-white",
      "scenes": [
        {
          "index": 1,
          "narration": "Cold plunges look extreme, but the hook is simple.",
          "visual_prompt": "Fast vertical b-roll of ice, water, and a phone recording setup.",
          "duration_seconds": 4
        }
      ]
    }
  }'
```

The final job result includes the output URL and Compose metadata. Generated media URLs expire after 7 days, so save anything you need to keep.

### Edit a render

Use `mode: "edit"` when you already have a prior render manifest and want to change selected scenes.

```json
{
  "recipe": "custom",
  "mode": "edit",
  "manifest": {
    "video_url": "https://media.empiriolabs.ai/worker-outputs/final.mp4",
    "scenes": [
      {
        "index": 1,
        "clip_url": "https://media.empiriolabs.ai/worker-outputs/scene-1.mp4"
      }
    ]
  },
  "edits": [
    {
      "scene_index": 1,
      "instruction": "Make the caption hook shorter and punchier."
    }
  ]
}
```

### Common Compose fields

| Field                  | Use                                                                             |
| ---------------------- | ------------------------------------------------------------------------------- |
| `recipe`               | Required. Recipe slug from `GET /v1/videos/compose/recipes`.                    |
| `mode`                 | `plan`, `render`, or `edit`.                                                    |
| `brief`                | Topic, idea, or direction for plan mode.                                        |
| `script`               | Optional script text for plan mode.                                             |
| `storyboard`           | Required for render mode. Usually the completed plan result.                    |
| `manifest` and `edits` | Required for edit mode.                                                         |
| `aspect_ratio`         | Common values include `9:16`, `16:9`, and `1:1`, depending on recipe support.   |
| `scene_count`          | Requested scene count for plan mode.                                            |
| `caption_style`        | Caption style ID from the recipes endpoint.                                     |
| `music`                | Optional music direction or preset.                                             |
| `voice`                | Optional voice preset or voice ID.                                              |
| `product_image`        | Optional product reference image for recipes that accept one.                   |
| `face_image`           | Optional face or character reference for recipes that require one.              |
| `gameplay_style`       | Optional gameplay background style when the recipe supports gameplay variants.  |
| `model_overrides`      | Optional per-stage model overrides, such as `{ "visual": "seedance-2-0-pro" }`. |

### Compose usage and billing

Compose records a parent usage row for the production, plus internal model legs for script, voice, visuals, transcription, music, and assembly when those stages run. The parent row is the customer-facing row returned by the Account Usage API.

```bash
curl "https://api.empiriolabs.ai/v1/account/usage?source=compose&limit=20" \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

Compose rows use `source: "compose"` and include structured metadata:

```json
{
  "source": "compose",
  "model": "custom",
  "endpoint": "/v1/videos/compose",
  "cost": { "amount": 0.1832, "currency": "USD" },
  "metadata": {
    "category": "compose",
    "compose": {
      "recipe": "custom",
      "mode": "render",
      "phase": "render",
      "leg_count": 5,
      "total_cost_usd": 0.1832,
      "legs": [
        {
          "stage": "voice",
          "model": "inworld-tts-mini",
          "cost_usd": 0.012
        }
      ]
    }
  }
}
```

Read `cost.amount` for the final debited amount. Use `metadata.compose.legs` when you want a breakdown of which model stages contributed.

## Fetch a single template

### Request

GET [https://api.empiriolabs.ai/v1/templates/\{slug}](https://api.empiriolabs.ai/v1/templates/\{slug})

```curl
curl https://api.empiriolabs.ai/v1/templates/studio-product-shot \
     -H "Authorization: Bearer <token>"
```

```python
import requests

url = "https://api.empiriolabs.ai/v1/templates/studio-product-shot"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.empiriolabs.ai/v1/templates/studio-product-shot';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

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

func main() {

	url := "https://api.empiriolabs.ai/v1/templates/studio-product-shot"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.empiriolabs.ai/v1/templates/studio-product-shot")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.empiriolabs.ai/v1/templates/studio-product-shot")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.empiriolabs.ai/v1/templates/studio-product-shot', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.empiriolabs.ai/v1/templates/studio-product-shot");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.empiriolabs.ai/v1/templates/studio-product-shot")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```bash
curl https://api.empiriolabs.ai/v1/templates/studio-product-shot \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY"
```

Returns 404 with `code: "template_not_found"` if the slug doesn't exist.

## Generate a video with a template

Add `template: "<slug>"` to a normal `/v1/videos/generations` call. You must provide whatever the template requires (`required_inputs`), usually a reference image.

```bash
curl https://api.empiriolabs.ai/v1/videos/generations \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "baseball-stadium",
    "image_url": "https://example.com/me.jpg"
  }'
```

Behavior:

* **Model selection**: if you don't pass `model`, the template's `recommended_model` is used. If you do, most templates validate that it is in `supported_models` and return 400 `template_model_unsupported` otherwise. Templates with `metadata.force_recommended_model: true` are pinned to `recommended_model` for effect fidelity.
* **Modality check**: image templates only apply to `/v1/images/generations`; video templates only apply to `/v1/videos/generations`.
* **Prompt blend**: your `prompt` (if any) is combined with the template's built-in styling so the generated output matches both your request and the effect's aesthetic. Send a short directional prompt; EmpirioLabs handles the rest.
* **Default params**: `default_params` from the template merge in only for keys you didn't set explicitly.
* **Required inputs**: `{ "image": true }` means the call returns 400 `template_missing_image` if no `image_url` / `image` / `images` is provided.

## Generate an image with a template

```bash
curl https://api.empiriolabs.ai/v1/images/generations \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "background-swap",
    "prompt": "Place this product on a brushed steel studio plinth",
    "image": ["https://example.com/product.jpg"]
  }'
```

The response is the same async job envelope as a normal video generation:

```json
{
  "job_id": "abc123...",
  "status": "processing",
  "poll_url": "/v1/jobs/abc123..."
}
```

Poll `GET /v1/jobs/{job_id}` until terminal.

## Extend a prior video

`/v1/videos/generations` also accepts `extend_from` to continue a previous generation. EmpirioLabs handles the prior-clip wiring for you and uses a sensible continuity prompt unless you provide your own.

```bash
curl https://api.empiriolabs.ai/v1/videos/generations \
  -H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "extend_from": { "job_id": "<prior_job_id>" }
  }'
```

You can also pass a direct URL:

```json
{ "extend_from": { "video_url": "https://media.empiriolabs.ai/..." } }
```

Extend can compose with templates:

```json
{
  "template": "action-hero",
  "extend_from": { "job_id": "<prior>" }
}
```

### Extend works on every video model

Pass `extend_from` with any supported video model. If you omit `model`, EmpirioLabs picks a sensible default for the extend.

## Error codes

| HTTP | code                         | meaning                                                                       |
| ---- | ---------------------------- | ----------------------------------------------------------------------------- |
| 400  | `template_not_found`         | the slug doesn't match any active template                                    |
| 400  | `template_model_unsupported` | the model you passed isn't in the template's `supported_models`               |
| 400  | `template_modality_mismatch` | the template modality doesn't match the generation endpoint                   |
| 400  | `template_missing_image`     | the template requires an image but the body didn't include one                |
| 400  | `template_missing_video`     | the template requires a reference video but the body didn't include one       |
| 400  | `template_no_model`          | the template has no `recommended_model` and you didn't pass one               |
| 400  | `extend_extraction_failed`   | couldn't process the prior video for extend. Try a different model.           |
| 400  | `extend_invalid_shape`       | `extend_from` was malformed                                                   |
| 400  | `extend_no_prior_video`      | the prior job had no resolvable video URL                                     |
| 404  | `extend_prior_not_found`     | the `job_id` in `extend_from` was unknown                                     |
| 500  | `extend_frame_upload_failed` | couldn't prepare the prior video for extend. Retry, or try a different model. |
| 400  | `compose_unknown_recipe`     | the `recipe` slug is not active                                               |
| 400  | `compose_bad_mode`           | `mode` was not `plan`, `render`, or `edit`                                    |
| 400  | `missing_brief`              | plan mode needs `brief` or script content                                     |
| 400  | `missing_storyboard`         | render mode needs a storyboard                                                |
| 400  | `missing_manifest`           | edit mode needs a prior render manifest                                       |
| 400  | `no_edits`                   | edit mode needs at least one edit instruction                                 |
| 401  | `unauthorized`               | the request was missing a valid API key                                       |
| 402  | `insufficient_credits`       | add credits before starting the job                                           |
| 504  | `compose_render_timeout`     | the render exceeded the production budget. Try fewer or shorter scenes.       |