Sovran
API documentation

Create your first video

Upload two clips, estimate one video, and read its finished output URL.

This guide joins a hook clip and a body clip into one vertical video. The runnable Node.js example carries each returned ID into the next request. It waits for media preparation, checks the credit estimate, and reads the finished output URL.

Before you start

  • Use Node.js 22 or later and pnpm.
  • Create an API key with Read, Write, and Generate permissions in API Playground.
  • Use a workspace with an active paid plan and available render credits.
  • You can use your own key as an accepted workspace member. Each operation keeps the dashboard's owner and admin checks.
  • Prepare two MP4, MOV, or WebM files. Their combined size must be at most 1 GiB (1,073,741,824 bytes).
  • Complete the first read request. If it returns api_disabled, access is closed on that host. If it returns stage_unavailable, this feature is closed. Stop before you upload files.

No music or provider connection is needed for this example. Captions are off.

Run the example

Save these files in the same local directory:

The commands below run the example on the production host. Use http://localhost:3000 for the local host. Use a key from the workspace on that host. See Authentication.

Set up and review the first estimate
pnpm add tus-js-client@4.3.1
export SOVRAN_API_ORIGIN="https://sovran.ai"
# Set SOVRAN_API_KEY through your secret store or server environment.
export SOVRAN_MAX_CREDITS="0"
node first-video.mjs ./hook.mp4 ./body.mp4 ./first-video-state.json

The first run creates a project, uploads both clips, waits for preparation, creates a sequence, and prints the estimate. With a credit limit of 0, it stops before render submission. Read the reported creditCost. This example estimates one finished video at one render credit.

After you accept that charge, run the same command with a limit of 1:

Submit the saved plan and read its output
export SOVRAN_MAX_CREDITS="1"
node first-video.mjs ./hook.mp4 ./body.mp4 ./first-video-state.json

The second run uses the saved resources. It sends the estimated planFingerprint, waits for the accepted job, and prints renderId, filename, url, and expiresAt. It does not download the finished video.

Keep the state file when a request times out or the result is unknown. The example saves the exact request body and its Idempotency-Key before each change. A repeated run reuses them. Do not delete the state file to resolve an uncertain result. Use the same host, key, files, and state file. Run one process per state file.

The state file contains resource IDs and limited upload tokens. It does not contain the API key. Keep it private and out of version control. If preparation or job polling reaches its time limit, a repeated run reads the existing work. It does not submit another render batch.

Follow the requests

The paths below include the /api/v1 prefix. Successful JSON responses wrap the result in data and include requestId. The downloaded createApi helper returns data.

1. Create a project

POST /api/v1/projects returns HTTP 201. Save data.id as projectId.

POST /api/v1/projects — request body
{ "name": "API first video" }

Each change needs its own request token. The example stores one token for project creation and reuses it only for that same request.

2. Upload and prepare both clips

For each file, call POST /api/v1/projects/{projectId}/uploads. Save the returned upload id and assetId. Transfer the file to the returned TUS endpoint with its headers, metadata, and chunk size. Then call POST /api/v1/uploads/{uploadId}/complete with {} and a separate saved request token.

The upload guide has the complete standalone example. Read GET /api/v1/assets/{assetId} until data.preparation.renderReady is true. A completed transfer is not a prepared video. Stop if preparation.state is failed.

3. Create a sequence with the returned asset IDs

POST /api/v1/projects/{projectId}/sequences returns HTTP 201. Save data.id as sequenceId. The runnable example builds this body with the two returned asset IDs:

POST /api/v1/projects/{projectId}/sequences — request body
const body = {
  name: 'API first video',
  blocks: [
    { position: 0, label: 'Hook', assetIds: [hookAsset.id] },
    { position: 1, label: 'Body', assetIds: [bodyAsset.id] },
  ],
  musicTracks: [],
  voiceoverTracks: [],
  durationLimits: { voiceover: true, music: true, loopMusic: false },
}

4. Select clips and estimate credits

Read GET /api/v1/sequences/{sequenceId}/combinations?limit=1. Save the first items[].id, inputFingerprint, and sequenceUpdatedAt. These values protect the selected clips and sequence revision.

Use them in POST /api/v1/sequences/{sequenceId}/render-estimate:

POST /api/v1/sequences/{sequenceId}/render-estimate — request body
const settings = {
  expectedUpdatedAt: manifest.sequenceUpdatedAt,
  combinationIds: [manifest.items[0].id],
  inputFingerprint: manifest.inputFingerprint,
  maxOutputs: 1,
  aspectRatio: '9:16',
  resolution: '1080',
  quality: 'high',
  fps: 30,
  captionsEnabled: false,
}

The estimate returns HTTP 200. It includes outputCount, creditCost, featureId, planFingerprint, inputFingerprint, namingPattern, requiredCustomFields, maximumSourceBytes, and maxSourceBytes. Save planFingerprint. An estimate does not submit a render or charge credits.

5. Submit the estimated plan

POST /api/v1/sequences/{sequenceId}/renders needs Generate permission. Use the same settings, add the estimated fingerprint, and send a saved Idempotency-Key.

POST /api/v1/sequences/{sequenceId}/renders — request body
const renderBody = { ...settings, planFingerprint: estimate.planFingerprint }

Acceptance returns HTTP 202. This is an example response; IDs differ for each job:

POST /api/v1/sequences/{sequenceId}/renders — HTTP 202
{
  "data": {
    "jobId": "10000000-0000-4000-8000-000000000004",
    "statusUrl": "/api/v1/jobs/10000000-0000-4000-8000-000000000004",
    "outputCount": 1
  },
  "requestId": "10000000-0000-4000-8000-000000000005"
}

If the sequence or render inputs changed, the API rejects the old revision or plan. Read the current resources and obtain a new estimate. Keep the old request token with its original body. Use a new token for a reviewed, changed request. Do not create a new token after an unknown result.

6. Poll the job and read the output URL

Read GET /api/v1/jobs/{jobId}. The sample reads the job immediately, then increases its delay from 1 second to at most 10 seconds. It stops after 5 minutes. It stops for a failure or a result that needs review. See Jobs and recovery.

This is an example success response:

GET /api/v1/jobs/{jobId} — HTTP 200
{
  "data": {
    "id": "10000000-0000-4000-8000-000000000004",
    "type": "sequence_render",
    "status": "succeeded",
    "counts": { "total": 1, "succeeded": 1, "failed": 0, "pending": 0 },
    "progress": 100,
    "outputs": [
      {
        "renderId": "10000000-0000-4000-8000-000000000006",
        "aspectRatio": "9:16",
        "status": "ready",
        "error": null
      }
    ],
    "error": null,
    "createdAt": "2026-10-04T16:00:00.000Z",
    "completedAt": "2026-10-04T16:02:00.000Z"
  },
  "requestId": "10000000-0000-4000-8000-000000000007"
}

Take the ready output's renderId and read GET /api/v1/renders/{renderId}/outputs. Select the entry with status: "ready" and the required aspectRatio. Its url is a signed media URL. Use expiresAt to check its lifetime. Read this route again for a new URL when it expires.

Create more variations

Add more video asset IDs to a block to create clip choices. Music and voiceover choices also multiply the finished output count. Set maxOutputs to a hard limit from 1 to 100 videos. A larger batch is rejected before a charge; it is not shortened. The selection must contain at most 100,000 clip combinations.

Each finished video's distinct source files must total at most 1 GiB (1,073,741,824 bytes). See Limits for upload and caption limits. Render resolution values are preview, mobile, sd, hd, and 1080. Quality values are verylow, low, medium, high, and veryhigh. Frame rates are 25, 30, and 60.

To replace a saved sequence configuration, use PUT /api/v1/sequences/{sequenceId}/configuration with its current expectedUpdatedAt and all configuration fields. A stale revision returns HTTP 409. Compatible saved row edits can join plain combinations in the same sequence batch. Other saved timelines use the composition workflow.

Manual and Smart sequence batches

Both sequence modes use the same export operations. Smart mode first checks whether each clip combination forms a complete story. This analysis does not render a video. It is separate from Smart Variations drafts, which the dashboard calls Auto-arrange.

StepOperation IDRequired saved input or result
Find project footageget__projects_projectId_assetsprojectId; use ready project asset IDs.
Create the sequencepost__projects_projectId_sequencesprojectId, ordered blocks, and their assetIds.
Read its current stateget__sequences_sequenceIdsequenceId; retain updatedAt.
Save configurationput__sequences_sequenceId_configurationsequenceId, current expectedUpdatedAt, and complete configuration.
Read clip combinationsget__sequences_sequenceId_combinationssequenceId; retain combination IDs, sequenceUpdatedAt, and inputFingerprint.
Analyze Smart combinationspost__sequences_sequenceId_smart_variationssequenceId and current expectedUpdatedAt; retain jobId and analysisId.
Read saved Smart decisionsget__sequences_sequenceId_smart_variations_resourceIdsequenceId and the saved analysisId as resourceId.
Estimate the selected batchpost__sequences_sequenceId_render_estimateCurrent revision, selected combinationIds, inputFingerprint, settings, and maxOutputs.
Submit that batchpost__sequences_sequenceId_rendersThe same estimate input plus its planFingerprint and a saved request token.

For Manual mode, choose the combinations from the manifest. For Smart mode, poll the analysis with get__jobs_jobId first. Read its saved analysis and choose from approvedCombinationIds. An empty approved list means that no combination passed. Do not render a rejected combination unless you review and select it for Manual mode.

Smart history is available through get__sequences_sequenceId_smart_variations. Saved rejected decisions retain their reason, decision fields, and any saved asset references. A rejection without saved asset references returns assets: []. The API does not copy assets from the current sequence into an older decision.

Read the combination manifest again before export. If the sequence or source context changed after Smart analysis, obtain a new analysis before selecting its approved combinations. A changed sequence, source, transcript, or caption style can invalidate a saved estimate. Use its current revision and input fingerprint. Save planFingerprint from a new estimate. Keep caption settings identical at submission. The caption style is part of the frozen render plan and its fingerprint.

maxOutputs permits 1–100 finished videos. It is a hard limit. Music and voiceover choices can increase the output count. Check outputCount and creditCost before submission. To render two selected clip combinations, use their two IDs and review the resulting estimate. Do not assume that two IDs always mean two finished outputs.

A sequence batch can contain plain combinations and compatible saved row edits. The server uses the current caller's saved combination-to-editor mappings. It checks the mapping, editor revision, saved state, and configuration fingerprint again before acceptance. Edited selections permit at most one music track and one voiceover. Changed mappings or revisions require a fresh estimate. An earlier full-editor draft without compatible row-edit context cannot use this batch path. Export other saved compositions separately. Do not send arbitrary composition IDs or timeline overrides in a sequence render request.

Use get__jobs_jobId until the render has a terminal result. Keep every ready outputs[].renderId. Read each with get__renders_renderId_outputs. A ready result supplies url, filename, aspectRatio, and expiresAt. Read the output again when its signed URL expires. Link expiry does not require another render.

Save each request token with its exact operation, path, query, and body before a change. In the API, send it as Idempotency-Key. In the CLI, use --idempotency-key. In MCP, use idempotencyKey. Replay the same request and token to read saved acceptance after an unknown result. A changed request needs a separate token only after you resolve the earlier result.

Sequence render retries use post__jobs_jobId_retry with {}. They retry confirmed failed children and keep successful outputs. The parent chain permits at most three explicit retries. Failed Smart sequence analysis can use this same retry operation to reuse completed analysis pages. Each analysis page has a fixed attempt limit. Stop when a retry is refused. The generic job API has no cancellation operation for sequence renders or Smart sequence analysis. Stopping a CLI wait or a chat does not stop accepted work.

For Creative Copilot, Voiceover Remix, and B-roll Remix, use draft creation and separate exports. Their draft count rules differ from a sequence batch limit.

Was this article helpful?

On this page