Create project
/v1/projectsBuilds a project from a scene list plus optional narration, music, and captions. The schema is strict and every field is validated up front, so a project either comes back ready to export or comes back with the exact field that was wrong. Creating a project costs nothing on its own: you pay when you export it.
- Scenes play in order and may not overlap. The end_ms of the last scene is the length of the finished video.
- Set a scene's volume to 0 when it plays under narration, otherwise both audio tracks are heard.
- Captions are timed from the narration track, so subtitles without narration are rejected.
- One create or export runs per account at a time. Serialize your calls rather than firing them in parallel.
- Credits:
- Free, unless captions are enabled. Transcribing the narration costs 1 voiceover credit per second.
- Rate limit:
- Heavy tier, 2/s and 10/min
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| title | body | string | Yes | Name of the project. Max 200 characters. |
| scenes | body | object[] | Yes | The visual timeline, in order. Each scene shows one asset for a window of the finished video. 1 to 50 scenes. Scenes may not overlap, and the last end_ms sets the video length. |
| scenes.asset_id | body | string | Yes | A video or image asset you own. |
| scenes.start_ms | body | integer | Yes | When the scene starts on the timeline. 0 or greater, and never before the previous scene ends. |
| scenes.end_ms | body | integer | Yes | When the scene ends on the timeline. Greater than start_ms, and at most 10 minutes. |
| scenes.trim_start_ms | body | integer | No | How far into the source video the scene starts playing. Video scenes only. The trimmed window must fit inside the source video.default 0 |
| scenes.volume | body | integer | No | Volume of the scene's own audio. Set 0 to mute the source video under narration. 0 to 100.default 100 |
| scenes.fit | body | string | No | How the asset fills the vertical canvas. cover scales and center-crops it edge to edge; contain shows the whole frame with black bars.default covercovercontain |
| narration | body | object | No | Voice track that plays across the whole video. |
| narration.asset_id | body | string | Yes | An audio asset you own. |
| narration.volume | body | integer | No | Narration volume. 0 to 100.default 100 |
| music | body | object | No | Background music bed. Pass exactly one of track_id or asset_id. |
| music.track_id | body | string | No | A track_id from GET /v1/music. |
| music.asset_id | body | string | No | An audio asset you own, to use your own music. |
| music.volume | body | integer | No | Music volume. 0 to 100.default 50 |
| texts | body | object[] | No | Text overlays such as hooks and calls to action. They use the same title card as the editor, so they stay editable there. Up to 20 entries. Texts may overlap each other and any scene. |
| texts.text | body | string | Yes | The words to show. Max 200 characters. |
| texts.start_ms | body | integer | Yes | When the text appears. 0 or greater. |
| texts.end_ms | body | integer | Yes | When the text disappears. Greater than start_ms, and no later than the end of the last scene. |
| texts.position | body | string | No | Where the text sits on the canvas.default toptopcenterbottom |
| texts.font_size | body | integer | No | Font size in canvas pixels on the 1080 by 1920 frame. 24 to 200.default 75 |
| texts.color | body | string | No | Text color as a hex value.default #000000 |
| texts.background_color | body | string | No | Color of the rounded card behind the text, as a hex value or transparent.default #ffffff |
| subtitles | body | object | No | Burned-in captions, timed from the narration track. Requires narration. |
| subtitles.style_id | body | string | Yes | A style_id from GET /v1/subtitle-styles. |
| subtitles.enabled | body | boolean | No | Set false to keep the style on the project without rendering captions.default true |
| subtitles.position | body | string | No | Where caption lines sit on the canvas.default centertopcenterbottom |
Request
curl -X POST "https://api.crayo.ai/v1/projects" \
-H "Authorization: Bearer $CRAYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Morning habits short",
"scenes": [
{
"asset_id": "cm7x1k2p40001abcd",
"start_ms": 0,
"end_ms": 4000,
"volume": 0
},
{
"asset_id": "cm7x1k2p40002abcd",
"start_ms": 4000,
"end_ms": 11500,
"trim_start_ms": 2000,
"volume": 0,
"fit": "cover"
}
],
"narration": {
"asset_id": "cm7x1j5bb0002abcd",
"volume": 100
},
"music": {
"track_id": "clq0ax9as00d2udfzijarenj2",
"volume": 35
},
"texts": [
{
"text": "3 habits that changed my mornings",
"start_ms": 0,
"end_ms": 3000,
"position": "top"
}
],
"subtitles": {
"style_id": "9",
"enabled": true,
"position": "bottom"
}
}'Response
{
"success": true,
"project": {
"id": "cm7x1k9r70004abcd",
"title": "Morning habits short",
"duration_ms": 11500,
"scene_count": 2,
"created_at": "2026-08-21T14:05:44.000Z",
"edit_url": "https://crayo.ai/edit/cm7x1k9r70004abcd"
}
}Errors
- 400UNKNOWN_FIELDThe body carries a field the schema does not define.
- 400VALIDATION_ERRORA scene overlaps, runs past the source, or breaks a range; a text runs past the last scene or uses a bad color.
- 400INVALID_SUBTITLE_STYLEsubtitles.style_id is not in the style catalog.
- 401MISSING_API_KEYNo Authorization header was sent, or it does not carry a crayo_sk_ key.
- 401UNAUTHORIZEDThe key sent is malformed, expired, or revoked.
- 402INSUFFICIENT_CREDITSCaptions are on and the transcript cannot be paid for.
- 403PROJECT_LIMIT_REACHEDYou already have 200 projects.
- 404NOT_FOUNDA referenced asset or music track does not belong to your account.
- 429CONCURRENT_REQUESTAnother create or export for this account is still in flight.
- 429RATE_LIMITEDOver the rate limit. Wait Retry-After seconds.
- 500SERVER_ERRORSomething failed on our side. Retry with backoff.