REST API|Video-ondertiteling API

Ondertitel elke video
met drie API-calls

Stuur een video-URL en krijg een mp4 terug met woord-voor-woord ingebrande ondertitels. Whisper timet elk woord, een AI-pass kiest de accenten, en het renderen draait op onze worker-vloot.

POSThttps://autostud.ai/api/v1/actions/subtitles.generate
Snelstart

De hele route, in één bestand

Plakken, sleutel invullen, uitvoeren. Niets hiervan is pseudocode.

const API = 'https://autostud.ai/api/v1'
const KEY = process.env.AUTOSTUD_API_KEY

const call = async (path, body) => {
  const response = await fetch(API + path, {
    method: body ? 'POST' : 'GET',
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  })

  const payload = await response.json()
  if (!response.ok) throw new Error(payload.error?.message || response.statusText)
  return payload
}

// 1. A video project to hang the timeline on.
const created = await call('/actions/videos.create', {
  video_name: 'Interview clip',
  video_type_id: 'timeline',
  video_format: 'portrait',
  video_lang: 'en',
})
const video_id = created.data.video_id

// 2. Transcribe, emphasise, style — one call.
const subtitled = await call('/actions/subtitles.generate', {
  video_id,
  media_url: 'https://cdn.example.com/interview.mp4',
  style_preset: 'beast',
  words_per_group: 3,
})
console.log(subtitled.data.word_count, 'words', subtitled.data.duration_seconds, 's')

// 3. Render it.
await call('/actions/renders.create', {
  video_id,
  video_type_id: 'timeline',
  video_format: 'portrait',
})

// 4. Wait for the worker. A webhook is better; this is the short version.
let render
do {
  await new Promise((resolve) => setTimeout(resolve, 15000))
  const list = await call(`/renders?video_id=${video_id}&limit=1`)
  render = list.data[0]
} while (render && ['not_started', 'processing'].includes(render.render_status))

if (render.render_status !== 'done') throw new Error('Render failed')
console.log(render.render_url)

Basis-URL: https://autostud.ai/api/v1 — authenticeer met `Authorization: Bearer sk_live_…`. Een `sk_test_`-sleutel legt exact dezelfde weg af en stopt vlak voor er iets wordt uitgegeven.

Stap voor stap

Wat je echt moet doen

Zes stappen, waarvan vier één request.

  1. 1

    Maak een API-sleutel

    Dashboard, Instellingen, API-sleutels. Neem de preset "Automation" als je scopes niet met de hand wilt kiezen: die dekt video’s, renders en bestanden. Het secret zie je één keer. `sk_live_` geeft uit, `sk_test_` valideert het hele request en stopt voor de schrijfactie.

    export AUTOSTUD_API_KEY="sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    
    # What can this key actually do?
    curl https://autostud.ai/api/v1 \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY"
  2. 2

    Zet je video achter een URL

    Er is geen upload-endpoint: de API leest een URL. Je eigen bucket, je CDN, een ondertekende link — alles wat onze servers kunnen ophalen. Registreren in de werkruimtebibliotheek is optioneel en verandert niets aan de flow.

    # The API takes a URL, never a file upload. Anything publicly
    # reachable works: your bucket, your CDN, a signed URL.
    export MEDIA_URL="https://cdn.example.com/interview.mp4"
    
    # Optional: keep a record of it in the workspace library.
    curl -X POST https://autostud.ai/api/v1/assets \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "provider_file_url": "'"$MEDIA_URL"'",
        "provider_file_name": "interview.mp4",
        "file_mime_type": "video/mp4",
        "provider_file_id": "interview-2026-08-18",
        "sync_method": "api"
      }'
  3. 3

    Maak het videoproject

    `videos.create` geeft de `video_id` terug waar al het andere aan hangt. Stuur een `Idempotency-Key` en een herhaald request geeft het eerste antwoord in plaats van een tweede project.

    curl -X POST https://autostud.ai/api/v1/actions/videos.create \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: interview-2026-08-18" \
      -d '{
        "video_name": "Interview clip",
        "video_type_id": "timeline",
        "video_format": "portrait",
        "video_lang": "en"
      }'
  4. 4

    Ondertitel hem

    `subtitles.generate` doet het hele werk serverside: Whisper transcribeert met woordtiming, een AI-pass markeert de woorden met nadruk, en een van 116 stijlen wordt toegepast. Terug krijg je het aantal woorden, de exacte duur en de gedetecteerde taal.

    export VIDEO_ID="9f0c4e2a-1d6b-4a77-9d51-6b0f2e8c3a4d"
    
    curl -X POST https://autostud.ai/api/v1/actions/subtitles.generate \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "video_id": "'"$VIDEO_ID"'",
        "media_url": "'"$MEDIA_URL"'",
        "style_preset": "beast",
        "words_per_group": 3,
        "enable_emphasis": true,
        "enable_diarization": false
      }'
  5. 5

    Zet de render in de wachtrij

    `renders.create` zet de job op de worker-vloot en antwoordt meteen met de `render_id`. Een render kost 20 credits, ongeacht de lengte.

    curl -X POST https://autostud.ai/api/v1/actions/renders.create \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "video_id": "'"$VIDEO_ID"'",
        "video_type_id": "timeline",
        "video_format": "portrait"
      }'
  6. 6

    Haal de mp4 op

    Abonneer je op `render.completed` en de definitieve URL wordt ondertekend naar je gestuurd. Wil je liever zelf ophalen: lijst de renders van de video en lees `render_status` tot die `done` zegt.

    # Register once, then stop polling.
    curl -X POST https://autostud.ai/api/v1/webhooks \
      -H "Authorization: Bearer $AUTOSTUD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-app.com/hooks/autostud",
        "events": ["render.completed", "render.failed"],
        "description": "Subtitled videos"
      }'
    # The signing secret comes back once, in this response. Store it.
Referentie

Elke call op deze pagina

Scopes zijn wat de sleutel moet dragen, niet wat je meestuurt.

CallScopeWat het doet
GET /api/v1-Discovery: wat deze sleutel mag, zijn limieten en zijn verbruik.
POST /api/v1/actions/videos.createvideos:writeMaakt het videoproject en geeft de id terug.
POST /api/v1/actions/subtitles.generatevideos:executeTranscribeert, markeert, stijlt en bewaart de ondertitelspoor.
POST /api/v1/actions/renders.createrenders:executeZet de render op de worker-vloot. 20 credits.
GET /api/v1/renders?video_id=…renders:readDe renders van een video, nieuwste eerst, met status en URL.
POST /api/v1/webhookswebhooks:writeRegistreert een https-endpoint en geeft het signing secret terug, één keer.

Stijlpresets

Geef er één mee als `style_preset`. Elke preset zet lettertype, outline, accentkleur en animatie in één keer — er valt verder niets te configureren.

makeitlookeasy
default4 woorden · simple
makeitloud
bold3 woorden · simple
thisoneword
impact3 woorden · focus_on_one_word
huge
beast1 woorden · focus_on_one_word
onewordatatime
karaoke5 woorden · focus_on_one_word
itfillsasyousing
karaoke_fill5 woorden · karaoke
lightsonthewords
neon4 woorden · progressively_visible
nothingbehindthetextatall
minimal6 woorden · simple
wordbywordtheyland
pop5 woorden · progressively_visible
darktextonapill
pill5 woorden · simple
twocalmlinesatthebottomoftheframe
cinematic9 woorden · simple

Wat je moet weten voor je bouwt

  • Rate limits gelden per sleutel en komen terug in headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After` bij een 429.
  • `Idempotency-Key` op elke POST maakt een retry 24 uur lang veilig; het eerste antwoord wordt letterlijk herhaald.
  • Een `sk_test_`-sleutel doorloopt authenticatie, scopes, limieten en validatie en antwoordt dan `simulated: true`, zonder te schrijven of uit te geven.
  • Fouten zijn getypeerd: `invalid_request`, `insufficient_credits`, `rate_limit_exceeded`, `upstream_error` — vertak op de code, nooit op de tekst.

Als het misgaat

Elke fout heeft dezelfde vorm: een stabiele `code` om op te vertakken, een `hint` die de call noemt die het oplost, en een `request_id` om te citeren. Het volledige vocabulaire staat op /docs/api/errors.

CodeWat het betekent
401 missing_credentialsGeen sleutel op het verzoek, of een die we niet kennen.
403 insufficient_scopeDe sleutel mist een scope; `details.required_scopes` noemt hem.
422 validation_failedEen veld klopt niet; `details.issues` somt ze allemaal op.
402 insufficient_creditsTe weinig credits voor de render. Vul aan, of gebruik een testsleutel.
429 rate_limit_exceededTe veel calls. Wacht `Retry-After` seconden en probeer opnieuw.
502 upstream_errorEen provider waarvan we afhangen viel uit. Opnieuw met backoff.
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This API key is missing the required scope: videos:execute.",
    "details": {
      "required_scopes": ["videos:execute"],
      "granted_scopes": ["videos:read", "videos:write"]
    },
    "hint": "`details.required_scopes` lists what is missing. Call GET /api/v1 to see what this key does hold, then re-scope it at /app/settings/api-keys.",
    "retryable": false,
    "request_id": "req_8f2c41d0a95b",
    "doc_url": "https://autostud.ai/docs/api/errors#insufficient_scope"
  }
}

Foutcodes

Webhooks

Stop met pollen

Een render duurt minuten. Laat hem naar je toe komen.

Ondertekend, in tien regels te verifiëren

`X-Autostud-Signature: t=<unix>,v1=<hex>` is een HMAC-SHA256 over `"<timestamp>.<ruwe body>"`. Verifieer tegen de ruwe body, voordat je hem parset.

Voor je opnieuw geprobeerd

Vijf pogingen over ongeveer twee uur, met de bezorgrij vóór de eerste geschreven. Een endpoint dat twintig keer op rij faalt wordt uitgezet in plaats van platgelegd.

Dedupliceer op het delivery-id

Elke poging draagt `X-Autostud-Delivery`. Zelfde id is hetzelfde event: behandel het als de primaire sleutel van het werk dat je start.

import crypto from 'node:crypto'

// The header is "t=<unix>,v1=<hex>" and the signed string is "<t>.<raw body>".
// Raw body: parse it AFTER verifying, never re-serialize before.
export function verify(raw_body, header, secret, tolerance = 300) {
  const parts = Object.fromEntries(
    String(header || '').split(',').map((entry) => entry.split('=').map((s) => s.trim()))
  )

  const timestamp = Number(parts.t)
  if (!Number.isFinite(timestamp)) return false
  if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${raw_body}`)
    .digest('hex')

  const received = String(parts.v1 || '')
  if (received.length !== expected.length) return false

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}

app.post('/hooks/autostud', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body.toString('utf8')

  if (!verify(raw, req.get('X-Autostud-Signature'), process.env.AUTOSTUD_WEBHOOK_SECRET)) {
    return res.status(400).send('bad signature')
  }

  // Deliveries can repeat: deduplicate on this id before doing any work.
  const delivery_id = req.get('X-Autostud-Delivery')
  const event = JSON.parse(raw)

  if (event.event === 'render.completed') {
    console.log(event.data.render_url)
  }

  res.status(200).send('ok')
})
Wat je krijgt

Waarom dit één call is en geen vijftien

De stukken die moeilijk na te bouwen zijn, draaien bij ons.

Timing per woord

Elk woord heeft zijn eigen begin en eind, dus de highlight valt op de lettergreep. Timing per zin is precies waarom de meeste automatische ondertitels te laat aanvoelen.

116 stijlen, één string

Van strakke broadcast-ondertitels tot de stuiterende highlight-look. De preset zet lettertype, outline, kleur en animatie samen, geschaald op je canvas.

De AI kiest de accenten

Een tweede pass leest het transcript en markeert de woorden die de betekenis dragen. Stopwoorden blijven rustig. Eén boolean zet het uit.

Twee sprekers, twee kleuren

Diarisatie is een vlag. Een interview komt terug met elke stem in zijn eigen kleur, zodat een kijker zonder geluid weet wie er praat.

Dezelfde objecten als het dashboard

Een via de API ondertitelde video opent in de editor als elke andere. Corrigeer een woord met de hand, render opnieuw, blijf automatiseren — beide paden schrijven hetzelfde document.

Gebouwd om opnieuw geprobeerd te worden

Idempotency-keys, getypeerde fouten, testsleutels, ondertekende webhooks en een requestlog van 30 dagen. De saaie helft van een API, oftewel de helft die je om 3 uur ’s nachts voelt.

FAQ

De vragen die ontwikkelaars echt sturen

Nee, en dat is bewust: de API neemt een URL. Host de video waar onze servers hem kunnen lezen — je eigen bucket, een CDN, een ondertekende link — en geef hem mee als `media_url`. Bestanden die je via het dashboard uploadt hebben al een bruikbare URL.

Vandaag niet. De ondertitels worden in de mp4 gerenderd, wat kortevideoplatforms nodig hebben. De woordenlijst met timings staat op de video, dus een los bestand is een omzetting die je zelf kunt doen met wat de API teruggeeft.

Een render is 20 credits, vast, ongeacht de lengte. De transcriptie en de nadrukpass worden in je werkruimte gelogd zoals elke andere AI-call, en `GET /api/v1/usage` laat zien wat een sleutel deze periode heeft verbruikt.

Minuten, geen seconden: hij draait op een worker-vloot, niet in het request. Precies daarvoor bestaat `render.completed` — abonneer je in plaats van een verbinding open te houden.

Ja. Roep `subtitles.generate` opnieuw aan met `restyle_only: true` en een andere `style_preset`: de woorden die al op de video staan worden hergebruikt, dus niets wordt twee keer getranscribeerd.

Whisper detecteert de taal zelf en de ondertitels komen terug in wat er gesproken wordt — niets te declareren. De gedetecteerde taal zit in het antwoord, zodat je erop kunt vertakken.

Lever ondertitelde video vanuit je eigen backend

Maak een sleutel, draai de quickstart, en de eerste ondertitelde mp4 is minuten weg.

Veilig betalen
Direct toegang
Altijd opzegbaar