---
title: "Render a composition"
description: "Renders one HTML document you wrote (a Hyperframes composition) and\nspends credits equal to its `data-duration`. Mosio checks it first:\nonly local assets, a sized root element with a duration, and no\nHyperframes lint errors. Google Fonts links and GSAP from a CDN are\nallowed; Mosio stores local copies. Registry components can be loaded\nwith `data-composition-src=\"compositions/components/<name>.html\"`.\nSee the composition guide for the rules. Needs the `videos:create`\npermission.\n"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.mosio.me/llms.txt
> Use this file to discover all available pages before exploring further.

Path: Mosio REST API › Compositions

`POST /v1/compositions`

Renders one HTML document you wrote (a Hyperframes composition) and
spends credits equal to its `data-duration`. Mosio checks it first:
only local assets, a sized root element with a duration, and no
Hyperframes lint errors. Google Fonts links and GSAP from a CDN are
allowed; Mosio stores local copies. Registry components can be loaded
with `data-composition-src="compositions/components/<name>.html"`.
See the composition guide for the rules. Needs the `videos:create`
permission.

## Authentication

- `apiKey`, apiKey, in header, header `x-api-key`

## Request body

_Required._

- `html` (`renderComposition.html`; string, required) — One complete HTML document. Its root element sets the size and length, for example `<div id="root" data-composition-id="main" data-start="0" data-duration="15" data-width="1080" data-height="1920" data-fps="30">`.
  - maxLength 200000
- `title` (`renderComposition.title`; string, optional) — A short title for the dashboard.
  - maxLength 80
- `idempotency_key` (`renderComposition.idempotency_key`; string, optional) — Send the same key again to get the same video instead of a second one.
  - minLength 1; maxLength 200

## Example request

```json
{
  "html": "string",
  "title": "string",
  "idempotency_key": "string"
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url https://app.mosio.me/v1/compositions \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <x-api-key>' \
  --data '
{
  "html": "string",
  "title": "string",
  "idempotency_key": "string"
}
'
```

### TypeScript

```typescript
const url = 'https://app.mosio.me/v1/compositions';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json', 'x-api-key': '<x-api-key>'},
  body: JSON.stringify({html: 'string', title: 'string', idempotency_key: 'string'})
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://app.mosio.me/v1/compositions"

payload = {
    "html": "string",
    "title": "string",
    "idempotency_key": "string"
}
headers = {
    "Content-Type": "application/json",
    "x-api-key": "<x-api-key>"
}

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

print(response.text)
```

## Responses

### 200

A video with this `idempotency_key` and the same body already exists; it is returned unchanged.

#### Example

```json
{
  "id": "vid_6f1c2b9e-0c1a-4b7e-9d0e-2f3a4b5c6d7e",
  "status": "queued",
  "template": "string",
  "created_at": "2019-08-24T14:15:22Z",
  "finished_at": "2019-08-24T14:15:22Z",
  "duration_seconds": 0,
  "output_url": "string",
  "error": {
    "code": "string",
    "message": "string"
  }
}
```

- `id` (`renderComposition.response.200.id`; string, required)
  - example `"vid_6f1c2b9e-0c1a-4b7e-9d0e-2f3a4b5c6d7e"`
- `status` (`renderComposition.response.200.status`; string, required)
  - one of `"queued"`, `"rendering"`, `"done"`, `"failed"`, `"canceled"`
- `template` (`renderComposition.response.200.template`; string, required) — The template id, `composition` for a rendered composition, or `studio` for a Studio project.
- `created_at` (`renderComposition.response.200.created_at`; string, required)
  - format `date-time`
- `finished_at` (`renderComposition.response.200.finished_at`; string | null, required)
  - format `date-time`
- `duration_seconds` (`renderComposition.response.200.duration_seconds`; number | null, required) — Length of the finished video.
- `output_url` (`renderComposition.response.200.output_url`; string | null, required) — Short-lived link to the MP4 once `status` is `done`.
- `error` (`renderComposition.response.200.error`; object | null, required) — Why the render failed, when `status` is `failed`. Failed videos spend no credits.
  - `code` (`renderComposition.response.200.error.code`; string, optional)
  - `message` (`renderComposition.response.200.error.message`; string, optional)

### 201

The video was created and queued.

#### Example

```json
{
  "id": "vid_6f1c2b9e-0c1a-4b7e-9d0e-2f3a4b5c6d7e",
  "status": "queued",
  "template": "string",
  "created_at": "2019-08-24T14:15:22Z",
  "finished_at": "2019-08-24T14:15:22Z",
  "duration_seconds": 0,
  "output_url": "string",
  "error": {
    "code": "string",
    "message": "string"
  }
}
```

- `id` (`renderComposition.response.201.id`; string, required)
  - example `"vid_6f1c2b9e-0c1a-4b7e-9d0e-2f3a4b5c6d7e"`
- `status` (`renderComposition.response.201.status`; string, required)
  - one of `"queued"`, `"rendering"`, `"done"`, `"failed"`, `"canceled"`
- `template` (`renderComposition.response.201.template`; string, required) — The template id, `composition` for a rendered composition, or `studio` for a Studio project.
- `created_at` (`renderComposition.response.201.created_at`; string, required)
  - format `date-time`
- `finished_at` (`renderComposition.response.201.finished_at`; string | null, required)
  - format `date-time`
- `duration_seconds` (`renderComposition.response.201.duration_seconds`; number | null, required) — Length of the finished video.
- `output_url` (`renderComposition.response.201.output_url`; string | null, required) — Short-lived link to the MP4 once `status` is `done`.
- `error` (`renderComposition.response.201.error`; object | null, required) — Why the render failed, when `status` is `failed`. Failed videos spend no credits.
  - `code` (`renderComposition.response.201.error.code`; string, optional)
  - `message` (`renderComposition.response.201.error.message`; string, optional)

### 400

The request body is not valid JSON or does not match the schema.

#### Example

```json
{
  "error": "unauthorized",
  "message": "string",
  "details": null
}
```

- `error` (`renderComposition.response.400.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.400.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.400.details`; unknown, optional) — More about the error, such as the fields to fix.

### 401

The API key is missing or not valid.

#### Example

```json
{
  "error": "unauthorized",
  "message": "Kirim API key di header x-api-key."
}
```

- `error` (`renderComposition.response.401.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.401.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.401.details`; unknown, optional) — More about the error, such as the fields to fix.

### 402

The workspace does not have enough credits for this video.

#### Example

```json
{
  "error": "unauthorized",
  "message": "string",
  "details": null
}
```

- `error` (`renderComposition.response.402.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.402.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.402.details`; unknown, optional) — More about the error, such as the fields to fix.

### 409

This `idempotency_key` was used before with a different body.

#### Example

```json
{
  "error": "unauthorized",
  "message": "string",
  "details": null
}
```

- `error` (`renderComposition.response.409.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.409.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.409.details`; unknown, optional) — More about the error, such as the fields to fix.

### 422

The composition cannot be rendered yet. `details.problems` lists what to fix.

#### Example

```json
{
  "error": "invalid_composition",
  "message": "Komposisi belum bisa dirender. Perbaiki bagian yang disebut lalu kirim lagi.",
  "details": {
    "problems": [
      {
        "code": "remote_asset",
        "message": "Aset di luar file ini tidak bisa dimuat saat render: https://example.com/hero.jpg.",
        "hint": "Pakai vendor/ (GSAP, plugin, font bawaan), Google Fonts, komponen registry, SVG/CSS inline, atau gambar data:."
      }
    ]
  }
}
```

- `error` (`renderComposition.response.422.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.422.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.422.details`; unknown, optional) — More about the error, such as the fields to fix.

### 429

The workspace already has the maximum number of videos queued or rendering (2). Wait for one to finish.

#### Example

```json
{
  "error": "unauthorized",
  "message": "string",
  "details": null
}
```

- `error` (`renderComposition.response.429.error`; string, required) — A stable error code.
  - one of `"unauthorized"`, `"invalid_api_key"`, `"insufficient_scope"`, `"forbidden"`, `"invalid_input"`, `"unknown_template"`, `"invalid_variables"`, `"invalid_composition"`, `"idempotency_conflict"`, `"quota_exceeded"`, `"insufficient_credits"`, `"too_many_active_renders"`, `"duration_too_long"`, `"not_found"`, `"internal_error"`
- `message` (`renderComposition.response.429.message`; string, required) — A message for people, in Indonesian.
- `details` (`renderComposition.response.429.details`; unknown, optional) — More about the error, such as the fields to fix.


Source: https://docs.mosio.me/api/compositions/create/index.md
