Home > Dev > Docs
Home / Pricing / Docs / Apps

Videostew Developer Documentation

Request Format Note When making POST requests, we recommend sending the JSON body directly with the Content-Type: application/json header. If using application/x-www-form-urlencoded, serialize the parameters as JSON and send them in the payload field. Example: payload={"apiKey":"...","secret":"..."}

The // comments in the JSON examples in this document are for explanation only. Standard JSON does not allow comments, so do not include comments in your actual request body. (Blocks containing comments are marked as jsonc.)

Automation API

An API for automatically generating and exporting videos without the editing interface. To prevent token exposure, call this API from the backend only.

POST https://videostew.com/api/automations

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.

Authentication Headers

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)

Body Parameters

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

Request Example

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/"
      }
    }
  }'

Response 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 a webhookUrl.

Webhook Response (on export completion)

{
  "status": "done",
  "projectId": "abc123...",
  "size": "1080x1920",
  "type": "mp4",
  "link": "https://cdn.videostew.com/projects/..."
}

Injector Detailed Options

Two data injection methods are available: wizard and data. They are mutually exclusive — only one can be used at a time.

injector.wizard

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
opts.narrationVoices Assigns a per-speaker AI voice for conversational content (e.g., podcasts). An object of the form { "neutral": "libraryId", "SpeakerName": "libraryId", ... }. neutral is the default voice applied to slides with no speaker pattern. Speaker names must exactly match the SpeakerName: patterns in the content. For single-voice TTS, the narration settings of the base project are used instead of this field.
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 adjust are set to keep, 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"
    }
  }
}

Conversational (podcast) example - per-speaker AI voice assignment

When content includes speaker patterns such as 진행자: or 게스트:, you can use narrationVoices to assign a different voice to each speaker. Speaker names must exactly match the patterns in the content.

{
  "wizard": {
    "source": "text",
    "sourceContent": "<p>진행자: 안녕하세요, 오늘의 주제는...</p><p>게스트: 네, 반갑습니다...</p>",
    "language": "ko",
    "opts": {
      "replace": "all",
      "narrationVoices": {
        "neutral": "voice-library-id-default",
        "진행자": "voice-library-id-host",
        "게스트": "voice-library-id-guest"
      }
    }
  }
}

neutral: The default voice applied to slides that have no speaker pattern (e.g., intro/outro slides).

injector.data

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.

Label ≠ API key: Even if the label you gave an element is img, the image injection key is always image. The label (label) is for choosing "which slide/element," while the injection keys (text image video) and the coordinate/style keys are fixed.

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. In this simple mode, only the slide's top-level text/image/video keys are used. If you need element-level control (position, animation, color, etc.), use the elements array in Advanced Settings below.
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
      }
    ]
  }
}

injector.data Advanced Settings (elements)

This is the extended mode of injector.data. Use the elements array when you need fine-grained control that cannot be expressed with the simple mode above (text/image/video strings) — such as element positioning, multiple elements of the same type, entrance animations, idle effects, and so on. The following is a complete list of available keys — include only what you need in your actual request.

baseProjectId required: Injection is based on a project/template owned by the caller. If you omit baseProjectId, elements will not be applied, so specify your own project ID. You cannot create a new slide layout that does not exist in the base using elements alone.

color format: [[R, G, B, A], degree] (R/G/B are 0–255, A is 0–1 opacity, degree is the gradient angle, 0 for solid color).

Media replacement uses a public URL in libraryId: If you put a public URL in an image/video element's libraryId, it is ingested and replaced. It cannot be replaced via custom.source or an element's image key.

Element entrance animation trans (a top-level field of the element, separate from the slide transition trans. Format: group__direction):

Group Direction
slide flip_push flip_pull cube left right up down
stretch left right up down horizontal vertical
flip horizontal_left horizontal_right vertical_up vertical_down
reveal left right up down horizontal_open vertical_open
fade normal left right up down
zoom zoomspin in out
circle open
clock asc desc
blink (no direction)

Element idle effect idle: pendulum pulse twitch updown leftright shake blink clockwise counterclockwise. Intensity is set with idleConf: { "speed": number, "distance": number }.

Slide transitions and element animations use different value systems. Slide transitions (slides[].trans.type) use hyphenated directions (fade, cube-ltr, reveal-rtl, etc.), while element entrance animations (elements[].trans.type) use the underscore directions from the table above (reveal__right, etc.).

Field tables

Slide level:

Key Type Description
label string Matched by base slide label (same as simple mode)
trans object Slide transition effect { type, durationTime }
narration object Narration (below)
glb object Slide-global (BGM/sound effect/background/blur)
elements array Array of elements. Replaced on label match, created if absent, deleted when content:""

Element elements[] (common):

Key Type Description
type string text | image | video | shape
label string Element label (selects the replacement target)
content string Text content (type:text). "" deletes the element
libraryId string Media source. A public URL is ingested and replaced
rect object Position/size (below)
custom object Style/entrance effects (below)
trans object Element entrance animation { type, txtRange } (table above)
idle / idleConf string / object Idle effect (list above)

rect:

Key Description
top left width height Position/size (px)
opacity Element opacity 0–1
innerType Media display cover (fill, default) | contain (fit) | fill (original ratio)
innerPosition Focus position, CSS position format (e.g., 50% 30%)

custom (text):

Key Description
fontSize Font size (px)
color / raColor Text color / readability-background color (color format)
align Horizontal alignment left | center | right
valign Vertical alignment top | middle | bottom
lineHeight Line height
raType Readability background "" | block | inline (default) | outline | shadow
enabledTts If true, this text is read by the AI voice
splitLine Text entrance effect "" | 1l | 2l | p
splitLineType replace (replace) | add (accumulate)

custom (video):

Key Description
trimStart trimEnd Trim (seconds)
speed volume Speed / volume
loop Loop (0 = none, 9999 = infinite)

narration:

Key Description
type voice (audio file) | tts (text synthesis)
libraryId Audio URL when type:voice
content Text to read when type:tts
volume speed trimStart trimEnd Volume / speed / trim

glb:

Key Description
bgmLibraryId bgmVolume bgmSpeed Slide BGM URL / volume / speed
soundLibraryId soundVolume Sound effect URL / volume
soundEvery If true, replays across consecutive slides
soundFit If true, fades to fit the slide length
backgroundColor Background color (color format)
bgBlr bgBlrB Background blur on/off / intensity 0–1
{
  "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 (audio file) | tts (text synthesis)
          "libraryId": "https://doo39oi115k60.cloudfront.net/wizard-url-example/narration1.mp3",  // when type is voice
          "content": "",        // text to read when type is 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": "middle",         // top | middle | bottom
              "lineHeight": 1.2,
              "raType": "shadow",         // "" | block | inline | outline | shadow  (default inline)
              "raColor": [[0, 0, 0, 1], 0],
              "enabledTts": true,
              "splitLine": "1l",          // "" all | 1l one line at a time | 2l two lines at a time | p per paragraph
              "splitLineType": "replace"  // replace | add (accumulate)
            },
            "trans": { "type": "reveal__right", "txtRange": "line" }  // element entrance animation (table above)
          },
          {
            "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
            },
            "idle": "updown",             // idle effect (list above)
            "idleConf": { "speed": 1.0, "distance": 1.0 }
          },
          {
            "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]
            }
          }
        ]
      }
    ]
  }
}

dictionary

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
    ]
  }
}

Other APIs

GET https://videostew.com/api/spaces - List Workspaces

curl -X GET "https://videostew.com/api/spaces" \
  -H "Authorization: Bearer {{bearerToken}}" \
  -H "x-nonce: $RANDOM"

GET https://videostew.com/api/projects - List Projects

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"

GET https://videostew.com/api/projects/:projectId - Get Single Project

curl -X GET "https://videostew.com/api/projects/PROJECT_ID" \
  -H "Authorization: Bearer {{bearerToken}}" \
  -H "x-nonce: $RANDOM"

Query parameter: includeSlides=true — the response result includes a slides array (each slide: slideId, glb, duration, narration, trans, elements, note, etc., symmetric with the injection structure). Useful for verifying that injected values were actually applied. It is large, so use it only when needed.

GET https://videostew.com/api/exports - List Exports

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"

GET https://videostew.com/api/exports/:projectId/:exportId - Get Single Export

curl -X GET "https://videostew.com/api/exports/PROJECT_ID/EXPORT_ID" \
  -H "Authorization: Bearer {{bearerToken}}" \
  -H "x-nonce: $RANDOM"

POST https://videostew.com/api/exports/:projectId - Request Export

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/..."
}

SDK

SDK curl 예시에 인증정보를 자동으로 채우려면 앱 관리 페이지에서 앱을 먼저 발급하세요.

Use this when you want to provide only the Videostew editing interface to your customers on your own website.

Authentication (Issuing / Expiring Access Tokens)

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 secret key exposure, make this request from the backend only.

POST https://videostew.com/auth/access-token - Issue Access Token

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..."
}

POST https://videostew.com/auth/revoke-access-token - Expire Access Token (Optional)

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"
  }'

Launching the Editor

videostewSDK.open(options)

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

videostewSDK.save()

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();

videostewSDK.done()

Saves the edited content and delivers the result to callbackFunc or the Done Webhook.

videostewSDK.done();

videostewSDK.cancel(force?)

Closes the editor. A confirmation popup is shown if there are unsaved changes.

videostewSDK.cancel();
// videostewSDK.cancel(true); // force close
[Stop]