Request Format Note When making POST requests, we recommend sending the JSON body directly with the
Content-Type: application/jsonheader. If usingapplication/x-www-form-urlencoded, serialize the parameters as JSON and send them in thepayloadfield. Example:payload={"apiKey":"...","secret":"..."}
An API for automatically generating and exporting videos without the editing interface. To prevent token exposure, call this API from the backend only.
Automates both project creation and export in a single call. The final result is delivered via POST to the webhookUrl or the Export Webhook configured in your app settings. Projects created this way are automatically deleted one week after export.
| Header | Required | Description |
|---|---|---|
| Authorization | ✓ | Token copied from Account Settings > API Token. Format: Bearer Qjky6mFiMkRn... |
| x-nonce | ✓ | A unique value per request (used to distinguish duplicate requests) |
| Parameter | Required | Type | Description |
|---|---|---|---|
| injector | ✓ | object | Data injection/transformation spec. See Injector section below |
| dictionary | object | TTS/STT text substitution rules. See dictionary section below | |
| baseProjectId | string | projectId of the project to use as a template. Must be in the same space or be public | |
| spaceId | int | Target workspace ID. 0 means the default workspace |
|
| type | string | Export file format. mp4 | jpg |
|
| webhookUrl | string | URL to receive the result upon completion | |
| webhookType | string | Webhook delivery method. form | json |
|
| webhookHeader | string | Custom webhook header. Format: Header-Name: value. Example: x-api-key: abc123 |
curl -X POST "https://videostew.com/api/automations" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM" \
-H "Content-Type: application/json" \
-d '{
"baseProjectId": "88282f69vmqbqr9",
"injector": {
"wizard": {
"source": "url",
"sourceContent": "https://doo39oi115k60.cloudfront.net/wizard-url-example/",
}
}
}'
{
"status": "done",
"result": {
"projectId": "abc123..."
}
}
Checking the result: If you visit
https://videostew.com/v/{projectId}and see the message "Exporting…", rendering is in progress. You can check the completed video at the same URL in about 5 minutes. To receive the completion event in code, specify awebhookUrl.
{
"status": "done",
"projectId": "abc123...",
"size": "1080x1920",
"type": "mp4",
"link": "https://cdn.videostew.com/projects/..."
}
Two data injection methods are available: wizard and data. They are mutually exclusive — only one can be used at a time.
Pass text or a URL and AI will analyze it to automatically create a video. Detailed control is limited, but this is the simplest way to generate a video.
| Parameter | Description |
|---|---|
| source | Source type. text | url |
| sourceContent | Value corresponding to source. Plain text or HTML if text; a URL string if url |
| title | Project title |
| language | Project language. Example: ko, en, ja |
| opts.replace | Slide range for AI to replace. body = replace body slides only, keeping intro/outro from the template as-is (suitable for fixed animations and endings used repeatedly). headbody = AI auto-generates subheadings and replaces subheading + body, keeping intro/outro. all = replace everything including intro/outro |
| opts.visual | Visual source method. stock-mixed/stock-video/stock-image = automatically matches contextually relevant resources from the Videostew stock library (mixed/video only/image only respectively). ai-image = generate images with AI. none = no visuals (suitable when the source URL is already image-rich) |
| opts.autoLineBreak | When set to y, text is split line by line so that line-by-line text animations work as intended. Most templates use this animation by default, so y is recommended |
| opts.visualStyle | Visual style. illust = digital art, photo = photorealistic, flat-vector = vector, editorial-cartoon = editorial cartoon, editorial-illust = newspaper illustration, silhouette = silhouette. Or enter a direct prompt such as "Draw in ink wash painting style" to specify the style you want |
| adjust.duration | Target length. keep = preserve original, 30s | 1m | 3m | 10m | 20m |
| adjust.structure | Structure. keep = original, body = body only, headbody = subheading + content format |
| adjust.style | Script style. keep = preserve original, colloquial = conversational like a podcast, news = formal and objective like a news broadcast, info = clear and concise style optimized for information delivery, insight = in-depth style emphasizing insights and analysis, essay = narrative and reflective essay tone, casual = friendly and relaxed everyday conversational style. Or enter a direct prompt such as "Speak to the viewer in a friendly tone with a touch of humor" to specify the style you want |
If all keys in
adjustare set tokeep, the original text is used as-is without any AI refinement.
{
"wizard": {
"source": "url",
"sourceContent": "https://doo39oi115k60.cloudfront.net/wizard-url-example/",
"language": "ko",
"opts": {
"replace": "all",
"visual": "ai-image",
"visualStyle": "illust",
"autoLineBreak": "y"
},
"adjust": {
"duration": "1m",
"structure": "headbody",
"style": "news"
}
}
}
Directly specifies the project/slide/element structure. Use this when you want to fully control how the video is built. If a match with an existing element is found it is replaced; otherwise a new one is created. Specify only the keys that need to change — unspecified values retain their values from the existing project.
data keys
The bgm specified at the top level is applied as the default across the entire slides array. If a value is specified for an individual slide, that value takes precedence.
| Key | Description |
|---|---|
| title | Project title |
| language | Project language. Example: ko, en |
| bgm | Project BGM file URL |
| slides | Array of slides |
data.slides[] keys
| Key | Description |
|---|---|
| label | Slide identification label (matched against the slide labels in the base project). Specifying the same label multiple times duplicates that template slide to increase the slide count. Example: using the main label 3 times creates 3 main slides. Label values can be found in the slide panel at the bottom of the editing screen. |
| narration | Slide narration. Enter text and it will be read in the AI voice configured in the template. To use a self-recorded audio file, enter a public URL |
| sound | Slide sound effect URL |
| bgm | BGM URL applied to this slide only. Takes precedence over the top-level bgm |
| text | Text element content. A plain string is applied to the first text element in the slide. When a slide has multiple text elements and you need to target each precisely, specify an array of { "label": "element-label", "content": "content" } objects (label is based on the element label set in the template). An empty string deletes the element. The elements array approach is not supported. |
| image | Image element libraryId. Multiple values can be specified as an array |
| video | Video element libraryId. Multiple values can be specified as an array |
Note: All fields that accept a URL (
image,video,sound,bgm,narration, etc.) must be publicly accessible addresses in a standard file format supported by Chrome.
Example 1 — Text/image replacement + TTS narration
{
"data": {
"language": "ko",
"bgm": "https://example.com/bgm.mp3", // applied to all slides
"slides": [
{
"label": "title", // matched by slide label in the base project
"text": "오늘의 뉴스 제목", // replaces the template's text element with this content
"image": "https://doo39oi115k60.cloudfront.net/wizard-url-example/01.webp",
"narration": "오늘의 뉴스를 전해드립니다." // a string value generates TTS (follows the narration settings of the base project)
},
{
"label": "body",
"text": "본문 내용입니다.",
"image": "https://doo39oi115k60.cloudfront.net/wizard-url-example/02.webp"
},
{
"label": "outro",
"text": "" // an empty string deletes that text element
}
]
}
}
Example 2 — Targeting elements by label
When a template has multiple text elements, you can use the { label, content } form to precisely specify which element receives what content. However, this approach requires that each text element in the Videostew editing screen has been pre-labeled. Elements without a label cannot be targeted. For example, if a template has the top element labeled title and the bottom AI voice element labeled voicetext, you can control each independently as shown below.
{
"data": {
"slides": [
{
"label": "main",
"text": [
{ "label": "title", "content": "이거 진짜 될까?" }, // top title element on screen
{ "label": "voicetext", "content": "이거 진짜 될까" } // subtitle element read by AI voice
]
}
]
}
}
Example 3 — Recorded narration + per-slide video
{
"data": {
"slides": [
{
"label": "intro",
"narration": "https://doo39oi115k60.cloudfront.net/wizard-url-example/narration1.mp3", // self-recorded/synthesized audio file
"video": "https://doo39oi115k60.cloudfront.net/wizard-url-example/video1.mp4"
},
{
"label": "body",
"narration": "https://doo39oi115k60.cloudfront.net/wizard-url-example/narration2.mp3",
"video": "https://doo39oi115k60.cloudfront.net/wizard-url-example/video2.mp4",
"bgm": "https://example.com/this-slide-only.mp3" // different BGM for this slide only
}
]
}
}
Use this when you need fine-grained control that cannot be expressed with simple key mapping (e.g., position or multiple items). The following is a complete list of available keys — include only what you need in your actual request.
color format:
[[R, G, B, A], degree]— R/G/B are 0–255, A is 0–1 (opacity), degree is the gradient angle (0for solid color).
{
"data": {
"title": "프로젝트 제목",
"language": "ko",
"slides": [
{
"trans": {
"type": "fade", // auto | fade | flip-ltr | flip-ttb | cube-ltr | cube-rtl | reveal-ltr | reveal-rtl
"durationTime": 0.3
},
"narration": {
"type": "voice", // voice
"libraryId": "https://doo39oi115k60.cloudfront.net/wizard-url-example/narration1.mp3",
"content": "", // TTS 텍스트 (type이 tts일 때),
"volume": 1,
"speed": 1.1,
"trimStart": 0,
"trimEnd": 0
},
"glb": {
"bgmLibraryId": "https://example.com/bgm.mp3",
"bgmVolume": 0.9,
"bgmSpeed": 1,
"soundLibraryId": "https://example.com/sfx.mp3",
"soundVolume": 1,
"soundEvery": true,
"soundFit": false,
"backgroundColor": [[0, 0, 0, 1], 0],
"bgBlr": false,
"bgBlrB": 0.4
},
"elements": [
{
"type": "text",
"content": "텍스트 내용",
"rect": {
"top": 800, "left": 40, "width": 1000, "height": 200,
"opacity": 1
},
"custom": {
"fontSize": 48,
"color": [[255, 255, 255, 1], 0],
"align": "center", // left | center | right
"valign": "v_bottom", // top | middle | bottom | v_top | v_bottom
"lineHeight": 1.2,
"raType": "block", // notset | block | inline | outline | textShadow
"raColor": [[0, 0, 0, 1], 0],
"enabledTts": true,
"splitLine": "1l", // "" | 1l | 2l | p
"splitLineType": "replace" // replace | add
}
},
{
"type": "image",
"libraryId": "https://doo39oi115k60.cloudfront.net/wizard-url-example/01.webp",
"rect": {
"top": 0, "left": 0, "width": 1080, "height": 1920,
"opacity": 1,
"innerType": "cover", // cover | contain | fill
"innerPosition": "50% 30%" // CSS position format, image focus position
}
},
{
"type": "video",
"libraryId": "https://doo39oi115k60.cloudfront.net/wizard-url-example/video1.mp4",
"rect": {
"top": 0, "left": 0, "width": 1080, "height": 1080,
"innerType": "cover",
"innerPosition": "50% 50%"
},
"custom": {
"trimStart": 0,
"trimEnd": 10,
"speed": 1,
"volume": 1,
"loop": 9999 // 0 = no loop, 9999 = infinite loop
}
},
{
"type": "shape",
"libraryId": "shape-library-id",
"rect": {
"top": 0, "left": 0, "width": 200, "height": 200
},
"custom": {
"color": [[255, 100, 0, 1], 0]
}
}
]
}
]
}
}
Text substitution rules applied during TTS/STT. Passed at the top level of the body alongside injector. Merged with the space dictionary, with values specified here taking precedence. Maximum 100 entries per section.
| Key | Description |
|---|---|
tts |
Applied when AI voice reads text. Force-sets the correct pronunciation for words that are mispronounced |
stt |
Applied when converting speech to text (subtitle generation). Force-corrects words that STT misrecognizes |
trans |
Applied during translation post-processing |
{
"injector": {
"wizard": { "source": "url", "sourceContent": "https://doo39oi115k60.cloudfront.net/wizard-url-example/" }
},
"dictionary": {
"tts": [
{ "i": "3D", "o": "쓰리디" }, // prevents it from being read as "삼디"
{ "i": "AI", "o": "에이아이" } // prevents it from being read as "아이"
],
"stt": [
{ "i": "video studio", "o": "videostew" }, // corrects STT misrecognition
{ "i": "비디오 스튜", "o": "비디오스튜" } // corrects spacing misrecognition
]
}
}
curl -X GET "https://videostew.com/api/spaces" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM"
| Parameter | Type | Description |
|---|---|---|
| spaceId | int | Target workspace ID. 0 means the default workspace |
| projectIds | string | Comma-separated list of project IDs |
| folderId | int | Folder ID. Default (root) is 1000000 |
| status | string | public | enabled | secret. trash means deleted |
| limit | int | Items per page |
| page | int | Page number |
| title | string | Search by title |
| tags | string | Search by tags |
curl -X GET "https://videostew.com/api/projects?spaceId=0&limit=20&page=1" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM"
curl -X GET "https://videostew.com/api/projects/PROJECT_ID" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM"
Query parameter: includeSlides=true (includes slide details; response may be large)
| Parameter | Type | Description |
|---|---|---|
| spaceId | int | Target workspace ID. 0 means the default workspace |
| projectIds | string | Comma-separated list of project IDs |
| projectId | string | Retrieve exports for a specific project only |
| type | string | mp4 | jpg |
| limit | int | Items per page |
| page | int | Page number |
curl -X GET "https://videostew.com/api/exports?projectId=PROJECT_ID&limit=20" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM"
curl -X GET "https://videostew.com/api/exports/PROJECT_ID/EXPORT_ID" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM"
| Parameter | Type | Description |
|---|---|---|
| type | string | mp4 | jpg |
| webhookUrl | string | URL to receive the result upon completion |
| webhookType | string | form | json |
| webhookHeader | string | Custom webhook header. Format: Header-Name: value. Example: x-api-key: abc123 |
curl -X POST "https://videostew.com/api/exports/PROJECT_ID" \
-H "Authorization: Bearer {{bearerToken}}" \
-H "x-nonce: $RANDOM" \
-H "Content-Type: application/json" \
-d '{"type":"mp4","webhookUrl":"https://your-server.com/webhook"}'
Response:
{
"status": "done",
"result": {
"id": "e-19",
"projectId": "...",
"status": "generate",
"type": "mp4"
}
}
Webhook response (on export completion):
{
"status": "done",
"projectId": "...",
"size": "1280x720",
"type": "mp4",
"link": "https://cdn.videostew.com/projects/..."
}
Use this when you want to provide only the Videostew editing interface to your customers on your own website.
When using the SDK, you must issue a separate access token for each user. A token is valid for 1 month after its last use and is automatically renewed on reuse. If you receive a 401 Token is invalid response, issue a new token and handle the fallback.
!! To prevent
secretkey exposure, make this request from the backend only.
| Parameter | Required | Type | Description |
|---|---|---|---|
| apiKey | ✓ | string | The ApiKey of the issued app |
| secret | ✓ | string | The secret key of the issued app |
| uniqueId | ✓ | any | The unique identifier of the user in your platform |
| language | string | Force-set language when a new uniqueId is created. Example: ko, en |
curl -X POST "https://videostew.com/auth/access-token" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "{{apiKey}}",
"secret": "{{secret}}",
"uniqueId": "user_123"
}'
Response:
{
"status": "done",
"apiKey": "abc1de2fg3...",
"token": "Qjky6mFiMkRnOmExYj..."
}
Force-expires a user's token when all activity ends (e.g., logout). Since tokens are not single-use, force-deleting one may cause issues in other open tabs. Call this when you are certain the user has stopped using the service (e.g., account deletion).
curl -X POST "https://videostew.com/auth/revoke-access-token" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "{{apiKey}}",
"secret": "{{secret}}",
"uniqueId": "user_123"
}'
Opens the editor as a modal, typically from a button click or similar event.
<script src="https://cdn.videostew.com/web/sdk/sdk-latest.js"></script>
<script>
videostewSDK.open({
token: "Qjky6mFiMkRnOmEx...", // token value from the /auth/access-token response
projectId: "...", // ID of the project to edit (creates a new one if omitted)
// baseProjectId: "...", // use a specific project as a template (on new creation)
// language: "ko", // project and UI language setting
// injector: { ... }, // data injection/transformation spec
// dictionary: { ... }, // TTS/STT text substitution rules
callbackFunc: function(result) {
// called when the user clicks [Done]
console.log(result.projectId);
// result: { projectId, thumbnailUrl, spaceId, width, height, title, status, slideCount, savedAt }
},
eventFunc: function(projectId, eventName) {
// called when an event occurs in the editing screen
// eventName: openPreview, saveProject, openDownload,
// requestExportPng, requestExportMp4, exportedPng, exportedMp4,
// downloadPng, downloadMp4, openProjectInfo, updateProjectInfo
},
errorFunc: function(errorMessage) {
// called on token expiry → issue a new token and retry
// errorMessage: "Token is invalid"
}
});
</script>
| Option | Required | Description |
|---|---|---|
| token | ✓ | The token value from the /auth/access-token response. Example: Qjky6mFiMkRn... |
| apiKey | Required only when using the legacy method (raw token). Omit for the new method | |
| projectId | ID of the project to edit. Creates a new project if omitted | |
| baseProjectId | Template project ID. Must be in the same space or be public | |
| language | Project and UI language. Defaults to en if not specified |
|
| injector | Data injection/transformation spec. See Injector section | |
| dictionary | TTS/STT text substitution rules. See dictionary section | |
| callbackFunc | function(result) — called when the [Done] button is clicked. Ignored if a Done Webhook is configured in the app settings. |
|
| eventFunc | function(projectId, eventName) — called when an event occurs in the editing screen |
|
| errorFunc | function(errorMessage) — called on token expiry |
Saves the current state of the editing screen. This is equivalent to the user pressing the [Save] button, so there is generally no need to call it explicitly.
videostewSDK.save();
Saves the edited content and delivers the result to callbackFunc or the Done Webhook.
videostewSDK.done();
Closes the editor. A confirmation popup is shown if there are unsaved changes.
videostewSDK.cancel();
// videostewSDK.cancel(true); // force close