API Documentation
The JEMSU REST API lets you generate videos, run Preset Styles and your saved My Styles, and read your credit balance from your own code. Every video you create through the API spends your account credits, exactly like the app. Manage your API keys →
Base URL
All endpoints are relative to your JEMSU app origin:
Production https://app.jemsu.ai/api/v1
Staging https://app-staging.jemsu.ai/api/v1
Authentication
Pass your API key as a Bearer token in the Authorization header. Keys look like jem_live_… and are created on the API page. An active paid subscription is required to create a key and to generate videos.
Authorization: Bearer jem_live_xxxxxxxxxxxxxxxxxxxxxxxx
Treat your key like a password. Use it only from server-side code — never embed it in a browser, mobile app, or any client a user can inspect. If a key leaks, revoke it on the API page.
Get credit balance
Returns the current credit balance for the key’s owner.
curl https://app.jemsu.ai/api/v1/credits \
-H "Authorization: Bearer $JEMSU_API_KEY"
Response
{
"creditsAvailable": 8500,
"creditsUsed": 1500
}
Create a video
Starts a video-generation job and reserves the credit cost up front. Generation runs asynchronously — poll the status endpoint for the result. If the job fails, the reserved credits are automatically refunded.
The only required field in URL mode is url. Everything else is optional. The most common options are videogenerationmodel, videolength / veovideolength, aspectratio, and resolution — see the Complete field reference below for every field, its allowed values (which models/lengths are available, etc.), and any conditions.
Models & lengths: videogenerationmodel is veo (shown in the app as “Omni”; omni is accepted as an alias), grok, or seedance. Veo/Omni renders 10-second segments and uses veovideolength (numeric 10/20/30/40= 1–4 segments). Grok renders 15-second segments and uses videolength (15/30/45/60). Seedance also uses videolength but accepts ANY integer from 10 to 120 — the total is auto-split into up to 4 segments of at most 30 seconds each (the last segment is shorter, e.g. videolength: 100renders 30 + 30 + 30 + 10). Send one length field, matched to the model — e.g. a 3-segment Veo/Omni video is veovideolength: 30.
Resolution: resolution accepts 720p (default), 1080p (Omni and Seedance), and 4k (Seedance only — premium rate of 540 credits per second; captions are unavailable at 4K). Keyframe image model: imagegenerationmodel selects the still-image generator — nano-banana-pro (default), grok-imagine, or seedream-5-pro.
Image sources (URL videos): people_image_source (default ai_generated) and products_image_source (default scraped) each take scraped | ai_generated | uploaded and are exclusive per category — e.g. people_image_source: "uploaded" makes your userphoto2the presenter’s identity anchor. Selecting uploaded without attaching the matching image returns 422 image_source_missing_upload. Memory videos: saved_video_urls (array, up to 4 clips) anchors segments with frames from your own uploaded footage. Music: music takes none | background | music_only (background mixes an instrumental under the video at 20% volume; music_only replaces all audio with a generated song), with optional music_instructions free text and attach_music_reference (Seedance) to sync motion to the beat. Languages: languagesets the video’s overall language (script, captions, metadata — en es fr de pt it zh ja ko). voiceover_language overrides the SPOKEN content for TTS audio sources (voiceover / voice_clone / voice_design): the script is written in that language and the voice speaks it — any Gemini-TTS language code (see the field table). captions_language sets the language captions render in — a code different from the spoken audio translates the subtitles (any ZapCap language). Both accept auto (or omit) to inherit. Native audio: generate_native_audio (Seedance, yes | no, default yes) toggles the model’s own generated soundtrack; it is switched off automatically when a TTS voiceover or music-only is used, and other video models ignore it.
curl -X POST https://app.jemsu.ai/api/v1/videos \
-H "Authorization: Bearer $JEMSU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/your-page",
"videogenerationmodel": "seedance",
"videolength": 30,
"aspectratio": "9:16",
"resolution": "1080p"
}'
Response
{ "ok": true, "jobId": "b3f1c2a4-..." }
JavaScript
const res = await fetch("https://app.jemsu.ai/api/v1/videos", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JEMSU_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/your-page",
videolength: 30,
}),
});
const { jobId } = await res.json();
Preset Styles
Preset Styles are the app’s Styles Library — curated and community-published looks whose settingscarry every field and prompt the style uses. The list endpoint returns metadata for discovery; the detail endpoint adds the style’s complete settings payload. Style ids differ between staging and production, so always discover them through this endpoint.
Query params: sort is featured (default) or popular; industry and subcategory filter exact matches; limit defaults to 50 (max 100).
curl "https://app.jemsu.ai/api/v1/styles?sort=popular&industry=dealership" \
-H "Authorization: Bearer $JEMSU_API_KEY"
Response (list — metadata only)
{
"styles": [
{
"styleId": "kf83nWq1...",
"title": "Night Lot Walkaround",
"description": "Moody nighttime dealership walkaround.",
"industries": ["dealership"],
"subcategories": [],
"requiredPageType": "vdp",
"videoUrl": "https://.../preview.mp4",
"thumbnailUrl": "https://.../thumb.jpg",
"lockedFields": ["videogenerationmodel", "visualinstructions_1"],
"customizableLockedFields": ["visualinstructions_1"],
"creatorType": "community",
"appliedCount": 42,
"createdAt": "2026-05-01T12:00:00.000Z",
"updatedAt": "2026-06-10T09:30:00.000Z"
}
]
}
Response (detail — adds the full settings payload)
{
"styleId": "kf83nWq1...",
"title": "Night Lot Walkaround",
...same fields as the list...,
"settings": {
"videogenerationmodel": "veo",
"veovideolength": 20,
"aspectratio": "9:16",
"audio_source": "native",
"visualinstructions_1": "…the style's prompt for segment 1…",
"videoinstructions_1": "…camera / motion direction…"
},
"protectedFields": []
}
settings— the style’s complete field payload: model, length, audio, and the per-segment creative prompts (visualinstructions_N/videoinstructions_N). These are the values the create endpoint loads when you generate with the style.lockedFields— fields the style pins. When you generate with the style, your overrides for these are ignored — except the keys incustomizableLockedFields(the creative prompt slots), which you may always override.protectedFields— oncreatorType: "community"styles, the creator’s prompt fields (and thesegmentsplan that embeds them) are hidden from reads to protect their work. The prompts are still appliedwhen you generate with the style — they’re just not exposed here.requiredPageType—vdp(vehicle detail page) orpdp(product detail page) styles only work with aurlof that page type; a missing or mismatched url fails with422 wrong_page_typebefore any credits are charged.
My Styles
Your saved styles from the app’s My Stylessection (the “Save to My Styles” button on a finished video). Each item’s settingsis the snapshot of the source video’s form data. The list returns metadata (plus sourceJobId / sourcePresetId); the detail endpoint adds settings and a protectedFieldsarray. Only styles saved by the key’s owner are visible.
If the source video used a community creator’s style, that creator’s prompt fields (and the segments plan embedding them) are withheld from the read and listed in protectedFields — same protection as in the app. They are still applied when you generate, so re-run the style by sending my_style to the create endpoint rather than echoing the settings object yourself.
curl https://app.jemsu.ai/api/v1/my-styles \
-H "Authorization: Bearer $JEMSU_API_KEY"
Snapshots reference media by URL (photos, logos, reference images) — those files must still exist when you generate. A snapshot’s voice-clone id may have expired; pass a fresh elevenlabs_voice_id explicitly if the source video used a cloned voice.
Generate with a style
Pass preset_style (a public style id) or my_style (one of your My Styles ids) to the create endpoint and the API loads allof the style’s settings and prompts as the base of the request. Any field you send explicitly overrides the style’s value — except the style’s hard-locked fields, which keep the style’s value. The two reference fields are mutually exclusive.
A page-driven style just needs your page: send the style id plus urland the style’s prompts are grounded in that page. A no-URL style generates exactly as authored — the style id alone is enough (no url, videoIdea, or segments needed).
curl -X POST https://app.jemsu.ai/api/v1/videos \
-H "Authorization: Bearer $JEMSU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset_style": "STYLE_ID_FROM_GET_STYLES",
"url": "https://example.com/your-page"
}'
With one of your saved My Styles
curl -X POST https://app.jemsu.ai/api/v1/videos \
-H "Authorization: Bearer $JEMSU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "my_style": "SAVED_STYLE_ID_FROM_GET_MY_STYLES" }'
JavaScript — style + a couple of overrides
const res = await fetch("https://app.jemsu.ai/api/v1/videos", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JEMSU_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
preset_style: "STYLE_ID_FROM_GET_STYLES",
url: "https://example.com/your-page",
aspectratio: "9:16", // overrides the style (unless hard-locked)
captions: "yes",
}),
});
const { jobId } = await res.json();
- Generating with a
communitystyle automatically credits its creator — nothing extra to send. - If you send your own
segments[], it replaces the style’s plan entirely (the style’s per-segment prompts are dropped, not mixed in). - Sending a
urlwithout an explicitmodeswitches a no-URL style to URL mode, grounded in your page.
Complete field reference
Every key below can be included in the POST /api/v1/videos body. This list is generated from the live form configuration, so it always matches what the app itself accepts. A field with a Shown whencondition only takes effect when that condition is met by the other values you send — otherwise it’s ignored (same as the in-app form).
| Field | Type | Allowed values | Req. | Shown when |
|---|---|---|---|---|
|
videogenerationmodel
Video Generation Model
|
select | grok, veo, seedance (default: seedance) | Yes | Always |
|
resolution
Resolution
|
select | 720p, 1080p, 4k (default: 720p) | Yes | Always |
|
video_mode
Video Mode
|
select | image-to-video, text-to-video, reference-to-video (default: image-to-video) | Yes | Always |
|
veovideolength
Video Length (in Seconds)
|
select | 10, 20, 30, 40 (default: 10) | — | Shown when videogenerationmodel = veo |
|
videolength
Video Length (in Seconds)
|
select | 15, 30, 45, 60 (default: 15) | — | Shown when videogenerationmodel = grok or seedance |
|
voicescriptstyle
Voice Script Style
|
select | promotional, informational, comical, myth, tip | — | Shown when ai_assistance = ai |
|
captions
Captions
|
select | yes, no (default: no) | — | Always |
|
caption_highlight_color
Caption Highlight Color (Hex)
|
text | free text | — | Shown when captions = yes |
|
caption_position
Caption Position
|
select | 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 (default: 60) | — | Shown when captions = yes |
|
captions_language
Captions Language
|
select | auto, en, es, fr, de, pt, it, zh, ja, ko, af, sq, am, ar, hy, az, bn, eu, be, bg, ca, ceb, hr, cs, da, nl, et, fil, fi, gl, ka, el, gu, ht, he, hi, hu, is, id, jv, kn, kok, lo, la, lv, lt, lb, mk, mai, mg, ms, ml, mr, mn, ne, nb, nn, or, ps, fa, pl, pa, ro, ru, sr, sd, si, sk, sl, sw, sv, ta, te, th, tr, uk, ur, vi, as, ba, bs, br, my, cy, fo, ha, haw, kk, km, ky, ln, mt, mi, no, oc, sa, sn, so, su, tg, tt, tk, uz, yi, yo (default: auto) | — | Shown when captions = yes |
|
aspectratio
Aspect Ratio
|
select | 9:16, 16:9 (default: 9:16) | — | Always |
|
url
Product or Service Webpage / URL
|
text | free text | — | Always |
|
use_photo
Use Your Own Photo
|
select | Yes, No (default: No) | — | Always |
|
use_saved_images
Use Saved Images?
|
select | No, Yes (default: No) | — | Always |
|
use_logo
Custom Logo
|
select | Yes, No (default: No) | — | Always |
|
logo_position
Logo Position
|
select | topLeft, topRight (default: topLeft) | — | Always |
|
logo_width
Logo Width (px)
|
number | number | — | Always |
|
audio_source
Audio Source
|
select | native, voiceover, none (default: native) | — | Always |
|
voice
Voice
|
select | Achernar, Achird, Algenib, Algieba, Alnilam, Aoede, Autonoe, Callirrhoe, Charon, Despina, Enceladus, Erinome, Fenrir, Gacrux, Iapetus, Kore, Laomedeia, Leda, Orus, Puck, Pulcherrima, Rasalgethi, Sadachbia, Sadaltager, Schedar, Sulafat, Umbriel, Vindemiatrix, Zephyr, Zubenelgenubi (default: Kore) | — | Shown when audio_source = voiceover |
|
voiceover_language
Voiceover Language
|
select | auto, en, es, fr, de, pt, it, zh, ja, ko, af, sq, am, ar, hy, az, bn, eu, be, bg, ca, ceb, hr, cs, da, nl, et, fil, fi, gl, ka, el, gu, ht, he, hi, hu, is, id, jv, kn, kok, lo, la, lv, lt, lb, mk, mai, mg, ms, ml, mr, mn, ne, nb, nn, or, ps, fa, pl, pa, ro, ru, sr, sd, si, sk, sl, sw, sv, ta, te, th, tr, uk, ur, vi (default: auto) | — | Shown when audio_source = voiceover or voice_clone or voice_design |
|
generate_native_audio
Native Audio
|
select | yes, no (default: yes) | — | Shown when videogenerationmodel = seedance |
|
ai_voice_scripts
AI Writes Voice Scripts
|
select | No, Yes (default: No) | — | Always |
|
imagegenerationmodel
Image Generation Model
|
select | nano-banana-pro, grok-imagine, seedream-5-pro (default: nano-banana-pro) | — | Always |
|
people_image_source
People Image Source
|
select | scraped, ai_generated, uploaded (default: ai_generated) | — | Always |
|
products_image_source
Products / Objects Image Source
|
select | scraped, ai_generated, uploaded (default: scraped) | — | Always |
|
clone_voice
Clone Voice
|
select | Yes, No | — | Always |
|
elevenlabs_voice_id
Eleven Labs Voice ID
|
text | free text | — | Always |
|
scriptinstructions
Script Instructions (All Segments)
|
textarea | free text | — | Always |
|
visualinstructions
Visual Instructions (All Segments)
|
textarea | free text | — | Always |
|
videoinstructions
Video Instructions (All Segments)
|
textarea | free text | — | Always |
|
visualinstructions_1
Visual Instructions (Segment 1)
|
textarea | free text | — | Always |
|
videoinstructions_1
Video Instructions (Segment 1)
|
textarea | free text | — | Always |
|
visioninstructions_1
Vision Instructions (Segment 1)
|
textarea | free text | — | Always |
|
continuation_2
Segment 2 Continuation Mode
|
select | change_scene, change_scene_edit, extend, first-frame-from-last, text-to-video, reference-to-video (default: change_scene) | — | Shown when (videolength = 30 or 45 or 60) or (veovideolength = 20 or 30 or 40) |
|
visualinstructions_2
Visual Instructions (Segment 2)
|
textarea | free text | — | Shown when (videolength = 30 or 45 or 60) or (veovideolength = 20 or 30 or 40) |
|
videoinstructions_2
Video Instructions (Segment 2)
|
textarea | free text | — | Shown when (videolength = 30 or 45 or 60) or (veovideolength = 20 or 30 or 40) |
|
visioninstructions_2
Vision Instructions (Segment 2)
|
textarea | free text | — | Shown when (videolength = 30 or 45 or 60) or (veovideolength = 20 or 30 or 40) |
|
continuation_3
Segment 3 Continuation Mode
|
select | change_scene, change_scene_edit, extend, first-frame-from-last, text-to-video, reference-to-video (default: change_scene) | — | Shown when (videolength = 45 or 60) or (veovideolength = 30 or 40) |
|
visualinstructions_3
Visual Instructions (Segment 3)
|
textarea | free text | — | Shown when (videolength = 45 or 60) or (veovideolength = 30 or 40) |
|
videoinstructions_3
Video Instructions (Segment 3)
|
textarea | free text | — | Shown when (videolength = 45 or 60) or (veovideolength = 30 or 40) |
|
visioninstructions_3
Vision Instructions (Segment 3)
|
textarea | free text | — | Shown when (videolength = 45 or 60) or (veovideolength = 30 or 40) |
|
continuation_4
Segment 4 Continuation Mode
|
select | change_scene, change_scene_edit, extend, first-frame-from-last, text-to-video, reference-to-video (default: change_scene) | — | Shown when (videolength = 60) and (veovideolength = 40) |
|
visualinstructions_4
Visual Instructions (Segment 4)
|
textarea | free text | — | Shown when (videolength = 60) or (veovideolength = 40) |
|
videoinstructions_4
Video Instructions (Segment 4)
|
textarea | free text | — | Shown when (videolength = 60) or (veovideolength = 40) |
|
visioninstructions_4
Vision Instructions (Segment 4)
|
textarea | free text | — | Shown when (videolength = 60) and (veovideolength = 40) |
|
metadatainstructions
Metadata Instructions
|
textarea | free text | — | Always |
|
language
Video Language
|
select | en, es, fr, de, pt, it, zh, ja, ko (default: en) | — | Always |
|
music
Music
|
select | none, background, music_only (default: none) | — | Always |
|
attach_music_reference
Attach Music File as Reference?
|
select | no, yes (default: no) | — | Shown when (videogenerationmodel = seedance) and (music = background or music_only) |
|
music_instructions
Music Instructions
|
textarea | free text | — | Shown when music = background or music_only |
Modes & advanced fields
These control fields are not in the field reference above but are accepted by the create endpoint. URL mode (just url + a few options) is the simplest path; the segment / no-URL path is advanced and mirrors how the in-app AVA assistant plans a video.
| Field | Notes |
|---|---|
| mode | url (default) builds the video from a webpage; no-url uses a structured segments plan instead. |
| ai_assistance | ai (default) lets the engine rewrite your prompts; no_ai uses your instructions verbatim. |
| video_mode | text-to-video, image-to-video, or reference-to-video. |
| videoIdea | Free-text description of the video. Required in no-url mode (unless using no-AI text-to-video). |
| preset_style | The id of an approved public Style (see Preset Styles). Loads allof the style’s settings and prompts as the base of the request; fields you send explicitly override them (hard-locked fields excepted). |
| my_style | The id of one of your saved My Styles items — same loading behavior as preset_style. Mutually exclusive with preset_style. |
| segments | Advanced (no-URL): an array of segment objects — see the shape below. |
segments[] shape (no-URL mode)
{
"mode": "no-url",
"videoIdea": "30-second promo for my new espresso blend",
"segments": [
{
"index": 1,
"type": "text-to-video", // | "image-to-video" | "reference-to-video"
"textPrompt": "what happens in this segment",
"imagePrompt": "still-image prompt (image-to-video)",
"motionPrompt": "camera / motion direction",
"referenceImageUrl": "https://… (image-to-video)",
"referenceImageUrls": ["https://…"], // reference-to-video
"script": "voiceover line for this segment",
"continuation": null // | "extend" | "first-frame-from-last" | "change_scene_edit"
}
]
}
API caveats
- Media references must be public HTTPS URLs. Fields like
userphoto2,customlogo,saved_image_urls, and segmentreferenceImageUrl(s)take URLs — the in-app Memory pickers (saved photos / images / logos) aren’t available to API callers. - Reference-to-video references are optional.With no images attached anywhere, the backend generates a per-subject reference SET from your instructions — one clean image per key subject (person/character first, then products/objects, one environment), QA’d and shared across segments with per-reference roles. Attached references are capped per model (veo 3, seedance 4, grok 7); overlay logos never ride reference slots.
- Voice cloning is set up in the app. Recording a clone (
clone_voice) isn’t practical over the API, but if you already have an ElevenLabs voice id you can sendaudio_source: "voice_clone"withelevenlabs_voice_id. - Server-managed keys are stripped.
account_id,job_id,webhook, andwebhookkeyare set by the platform — they’re removed from your request (and from any loaded style) before the job runs.
Check a video’s status
Poll this until status is completed (a videoUrl is then present) or failed. Statuses progress: pending → processing → running → completed.
curl https://app.jemsu.ai/api/v1/videos/$JOB_ID \
-H "Authorization: Bearer $JEMSU_API_KEY"
Response
{
"jobId": "b3f1c2a4-...",
"status": "completed",
"videoUrl": "https://.../video.mp4",
"videoTitle": "...",
"thumbnailUrl": "https://.../thumb.jpg",
"creditsReserved": 750,
"error": null,
"createdAt": "2026-06-25T18:00:00.000Z",
"completedAt": "2026-06-25T18:04:12.000Z",
"failedAt": null
}
List recent videos
Returns your 25 most recent videos (same shape as the status endpoint).
{ "videos": [ { "jobId": "...", "status": "completed", ... } ] }
How credits are charged
Cost is computed from the chosen model and video length: credits = ceil(usdPerSecond × seconds × 100). Higher resolutions and premium models cost more per second. The cost is reserved when the job is created and refunded automatically if the job fails. If your balance is too low, the create call returns 402 insufficient_credits with the amount required.
Errors
Errors return a non-2xx status and a JSON body with an error code:
| Status | error | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, unknown, or revoked key. |
| 402 | subscription_required | No active paid subscription. |
| 402 | insufficient_credits | Balance too low. Body includes available and required. |
| 400 | url_required / invalid_combo | Invalid request parameters. |
| 404 | not_found | No such video for this key’s owner. |
| 404 | style_not_found | No style with that id (public styles must be published; My Styles must belong to the key’s owner). |
| 400 | style_not_available | The referenced style exists but isn’t published (removed or awaiting review). |
| 400 | invalid_style_reference | Both preset_style and my_style were sent — pick one. |
| 422 | wrong_page_type | The style requires a specific page type (vdp/pdp) and the urlis missing or doesn’t match. Checked before credits are charged. |
| 422 | segment_count_mismatch | segments[]doesn’t match the requested video length — supply matching segments, or drop segments when changing the length. |