Bygg ett helautomatiskt arbetsflöde för transkribering med n8n

De flesta team transkriberar fortfarande för hand: någon laddar ner inspelningen, laddar upp den någonstans, väntar, laddar ner en SRT-fil, byter namn på den och lägger den i rätt mapp. Det fungerar fint — ända tills det händer fyrtio gånger i veckan.


Den här guiden ersätter den personen med ett arbetsflöde. Så fort en ny inspelning kommer in blir den transkriberad, undertextad och levererad — utan att någon behöver hålla koll. Vi använder n8n eftersom det kör var som helst, kan driftas i din egen infrastruktur och pratar med vilket HTTP-API som helst — även vårt.


Till slut har du ett arbetsflöde som:


  1. startar så fort en ny video- eller ljudfil dyker upp
  2. skickar den till Inwistas API för transkribering
  3. väntar tills jobbet är klart, utan att gissa hur lång tid det tar
  4. laddar ner den färdiga SRT-filen
  5. levererar den dit ditt team behöver den


Allt nedan körs på det öppna Inwista API v1. Inga tillägg, inga egna kodnoder.

Innan du börjar

Du behöver tre saker:


  • En n8n-instans — moln eller egen drift, version 1.x
  • En API-nyckel till Inwista — skapa en under Min arbetsyta → API-nycklar. Den börjar med inw_live_
  • Media som API:et kan nå — API:et tar en offentlig https-URL, så filer i Dropbox, Google Drive, S3 eller ditt CMS behöver en delbar eller signerad länk


Ett ord om kostnaden innan du bygger något som går utan tillsyn: transkribering kostar 4 krediter per påbörjad mediaminut, som dras när jobbet tas emot. Jobb som misslyckas återbetalas automatiskt. Testa arbetsflödet på ett par korta filer innan du släpper lös det på hela arkivet.

Steg 1: välj din utlösare

Flödet startar med den händelse som betyder ”här finns något nytt att transkribera”. De vanligaste valen:


  • Google Drive Trigger / Dropbox Trigger — en bevakad mapp där teamet lägger inspelningarna
  • Webhook — ditt eget CMS eller LMS anropar n8n när en uppladdning är klar
  • Schedule Trigger — kolla ett RSS-flöde, en poddvärd eller en databas med obehandlade rader
  • Manual Trigger — för att bygga och testa, och det är där vi börjar

Lägg till en Manual Trigger tills vidare. Byt ut den mot den riktiga när resten fungerar.

Vad du än väljer: utlösarens uppgift är att leverera en enda sak — en offentligt nåbar URL till mediefilen. Spara den i ett fält som heter mediaUrl, så stämmer resten av guiden med ditt arbetsflöde.

Steg 2: skicka in transkriberingen

Lägg till en HTTP Request-nod som heter Submit transcription.

  • Metod — POST
  • URL — https://api.inwista.ai/v1/transcriptions
  • Autentisering — Generic Credential Type → Header Auth
  • Headernamn — Authorization
  • Headervärde — Bearer inw_live_din_nyckel_har
  • Send body — på, JSON


Body:

{
  "source_url": "{{ $json.mediaUrl }}",
  "language": "sv",
  "diarization": true,
  "metadata": { "source": "n8n", "folder": "{{ $json.folderName }}" }
}


Tre fält du bör förstå:

  • language är språket som talas i filen, inte språket du vill få ut. Att transkribera på källspråket är det som ger exakta tidkoder och ren text; översättningen kommer efteråt, som ett eget steg på det färdiga transkriptet. Hanterar ditt flöde flera språk kan du härleda det här fältet från utlösarens mapp eller metadata.


  • diarization slår på märkning av talare. Låt den vara av för innehåll med bara en röst — den kostar bearbetningstid du inte behöver.


  • metadata är ditt. Upp till 1 KB med vad du vill, returnerat oförändrat vid varje hämtning. Använd det för de identifierare dina egna system bryr sig om — ett kurs-id, ett ärendenummer, en avsnitts-slug — så att senare steg slipper återskapa sammanhanget.


Lägg till en header till medan du ändå håller på:


  • Idempotency-Key{{ $execution.id }}


Om n8n kör noden igen efter en nätverksglapp känner API:et igen nyckeln och ger tillbaka det ursprungliga jobbet i stället för att starta — och fakturera — ett nytt.

Svaret kommer direkt, med status: "processing" och ett id. Transkriberingen är inte klar; den är mottagen.

Steg 3: vänta på resultatet, på rätt sätt

Det är här de flesta flöden går snett. Ett fast ”vänta fem minuter” är för kort för en föreläsning och slöseri på ett röstmemo. Fråga efter status i stället.

Lägg till en Wait-nod (Wait 15s) inställd på 15 sekunder.

Lägg till en HTTP Request-nod (Check status):

  • Metod — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}
  • Autentisering — samma header som tidigare

Lägg till en IF-nod (Is it done?) med det här villkoret:

{{ $json.status }}  equals  completed


Koppla false-grenen tillbaka till Wait 15s. Det är din loop: kolla, vänta, kolla igen, tills jobbet rapporterar completed. True-grenen går vidare till nästa steg.

Svaret innehåller också ett progress-tal som visar var flödet faktiskt står, så vill du ha en förloppsindikator i Slack eller i din egen instrumentpanel finns den redan.


Två saker att lägga till innan det här närmar sig produktion:

  • Hantera fel. Lägg till ytterligare en IF som kollar {{ $json.status }} equals failed och skickar vidare till den larmning du använder. Jobb som misslyckas återbetalas automatiskt, men du vill ändå få veta det.
  • Sätt tak för loopen. n8n:s loopskydd hjälper, men ett uttalat tak — en räknare som ger upp efter säg 80 varv — gör ett fastnat jobb till en varning i stället för en körning som pågår hela natten.

Steg 4: hämta undertexterna

Lägg till en HTTP Request-nod som heter Get SRT:

  • Metod — GET
  • URL — https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt
  • Response format — File (eller Text, om du vill ha innehållet vidare i flödet)

Slutpunkten returnerar själva undertextfilen, inte ett JSON-omslag — så nodens utdata är redo att skrivas till disk, bifogas ett mejl eller laddas upp.

Byt format mot det mottagaren behöver:

Format — används till

  • srt — videospelare, redigeringsprogram, YouTube, de flesta CMS
  • vtt — HTML5-<track>, webbspelare
  • txt — sökindex, LLM-flöden, dokumentation
  • json — tidkoder på ordnivå, märkning av talare, egen rendering

Varje format är bara en vy av samma färdiga jobb. Att hämta fyra av dem kostar inget extra.

Steg 5: leverera resultatet

Den sista noden är vad ”klart” betyder för ditt team:

  • Google Drive / Dropbox / S3 — lägg filen bredvid källvideon
  • Slack — posta transkriptet i kanalen som bad om det
  • HTTP Request — skicka in det i ditt CMS, ditt LMS eller ditt undertextfält
  • Postgres / Airtable / Notion — spara txt-versionen som sökbar text

Är din mottagare en av dem som Inwista redan integrerar direkt mot — Google Drive, OneDrive, SharePoint, Dropbox, Box, YouTube, Vimeo, Wistia med flera — kan du hoppa över den här noden helt och i stället ställa in molnleverans under Min arbetsyta → Integrationer. Då kommer filerna automatiskt vid varje färdigt jobb, med eller utan arbetsflöde.

Nästa nivå: översätt innan du levererar

Lägg in två noder före leveransen, så ger samma arbetsflöde dig undertexter på hur många språk du vill.

POST /v1/transcriptions/{id}/translate med:

{ "target_language": "en" }


Den svarar 202 Accepted — översättningen körs på det färdiga transkriptet, så tidkoderna stämmer redan och bara texten ändras. Fråga GET /v1/transcriptions/{id}/translations/en på samma sätt som du frågade om jobbet, och hämta sedan:

GET /v1/transcriptions/{id}/captions?format=srt&language=en


Kör det genom en lista med målspråk, så blir en enda inspelning en komplett flerspråkig undertextuppsättning i en körning. Observera att språkförfrågan är strikt: ber du om ett språk utan färdig översättning får du ett tydligt fel i stället för källspråket i tysthet. Det är precis vad du vill ha i ett flöde som går utan tillsyn.

Nästa nivå: undertexter med sändningskvalitet

Rå transkribering är ordagrann. Undertexter är hantverk: radlängder, läshastighet, var en mening bryts över två block.

POST /v1/transcriptions/{id}/enhance kör det färdiga transkriptet genom just den behandlingen — förtätar texten, balanserar om radbrytningar, formaterar dialog och tillämpar minsta och största blocklängd:

{
  "settings": {
    "maxLinesPerBlock": "2",
    "maxCharactersPerLine": 42,
    "textCondensation": "smart",
    "speakerDialogueFormat": "hyphens",
    "gapBetweenBlocks": "broadcasting"
  }
}


Den svarar 202; fråga GET /v1/transcriptions/{id}/enhancements/{enhancementId} tills den är klar. Därefter serverar undertextslutpunkterna automatiskt den förbättrade versionen — du behöver inte ändra något i din leveransnod.

Känsliga inspelningar

Om ditt flöde behandlar material som du helst inte vill att vi behåller — patientsamtal, juridiska inspelningar, interna personalmöten — lägger du till ett fält i inskickningen från steg 2:

{
  "source_url": "{{ $json.mediaUrl }}",
  "language": "sv",
  "retention": "none"
}


Med retention: "none" raderas källmediet så fort transkriberingen är klar. Transkriptet, undertexterna, översättningarna och senare förbättringar fungerar precis som vanligt — det är bara ljudet och videon som är borta. Det finns också store_media: false, som behåller filen för bearbetningen men inte skapar några uppspelningskopior.

Och när lagringstiden för ett jobb tar slut hos dig raderar DELETE /v1/transcriptions/{id} allt som hör till — media, transkript, revisioner, översättningar — med ett enda anrop. Ett schemalagt n8n-flöde som raderar jobb äldre än din policy är ungefär fyra noder, och det gör din lagringspolicy till något du kan visa upp i stället för att beskriva.

Webhooks: motsvarigheten

Att fråga efter status är rätt styrning inuti en enskild n8n-körning: den står på egna ben, kräver ingen offentlig URL och håller hela flödet på ett ställe som du kan felsöka.

Webhooks löser ett annat problem: att få färdigt arbete fram till en central mottagare, oavsett var jobbet kom ifrån. Inspelningar som dina kollegor laddar upp i instrumentpanelen, jobb som skickas in från ett annat system, exporter som blir klara timmar senare — allt kan landa på en enda slutpunkt i stället för att varje flöde sköter sitt. Ställ in det under Min arbetsyta → Integrationer.

Peka den mot en Webhook-nod i n8n, så får du en signerad transcript.completed-händelse:

{
  "event": "transcript.completed",
  "id": "evt_...",
  "timestamp": 1786902819,
  "workspaceId": "...",
  "project": {
    "id": "...",
    "name": "styrelsemote-augusti.mp4",
    "language": "sv",
    "durationSeconds": 3184
  },
  "files": [
    { "format": "srt", "name": "styrelsemote-augusti.srt", "url": "https://...", "expiresAt": 1786989219 }
  ]
}


Varje förfrågan är signerad med HMAC-SHA256 över den råa bodyn, i headern X-Inwista-Signature som sha256=<hex>. Kontrollera den innan du litar på innehållet — i n8n räcker det med en Crypto-nod och en IF-jämförelse. Fil-URL:erna är signerade och giltiga i 24 timmar, så hämta det du behöver i stället för att spara länkarna.

Leveransen görs om tre gånger, och en slutpunkt som fortsätter att misslyckas stängs av automatiskt med orsaken synlig i dina integrationsinställningar — en trasig mottagare dyker alltså upp som en status du kan se, i stället för händelser som tyst försvinner.

Sådant som biter dig förr eller senare

Förfrågningsgränser. 300 läs- och 60 skrivförfrågningar per minut och nyckel. Generöst för normal användning, snabbt uppnått om du släpper ett helt arkiv parallellt utan kö. Ska du beta av hundratals filer, kör dem i omgångar.

Käll-URL:erna måste gå att nå. API:et undersöker mediefilen innan jobbet tas emot — det är så längd och kostnad är kända i förväg. En Drive-länk som kräver inloggning ger unreadable_source, och inget faktureras. Använd direkta eller signerade URL:er.

Fel är strukturerade. Varje misslyckande ger samma omslag:

{ "error": { "code": "insufficient_credits", "message": "..." } }


Förgrena på code, aldrig på meddelandetexten. Koder tillkommer bara inom v1 och byter aldrig namn; meddelanden kan däremot skrivas om.

Idempotensnycklar gäller per förfrågan, inte per fil. De skyddar dig mot dubbel debitering när samma nodkörning görs om. Att rensa bort samma inspelning när två olika körningar skickar in den är ditt arbetsflödes uppgift — en uppslagstabell på fil-id är det vanliga svaret.

Det färdiga arbetsflödet

Här är stommen att importera och anpassa. Byt ut credential-referensen mot din egen Header Auth-credential i stället för att klistra in en nyckel direkt i noden.

{
  "name": "Inwista — automated transcription",
  "nodes": [
    {
      "parameters": {},
      "id": "trigger",
      "name": "When clicking Test workflow",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [0, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.inwista.ai/v1/transcriptions",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"source_url\": \"{{ $json.mediaUrl }}\", \"language\": \"sv\", \"diarization\": true}",
        "options": {}
      },
      "id": "submit",
      "name": "Submit transcription",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [220, 0]
    },
    {
      "parameters": { "amount": 15 },
      "id": "wait",
      "name": "Wait 15s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [440, 0]
    },
    {
      "parameters": {
        "url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}",
        "options": {}
      },
      "id": "status",
      "name": "Check status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [660, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "version": 2 },
          "conditions": [
            {
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "isdone",
      "name": "Is it done?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [880, 0]
    },
    {
      "parameters": {
        "url": "=https://api.inwista.ai/v1/transcriptions/{{ $('Submit transcription').item.json.id }}/captions?format=srt",
        "options": { "response": { "response": { "responseFormat": "text" } } }
      },
      "id": "srt",
      "name": "Get SRT",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1100, -100]
    }
  ],
  "connections": {
    "When clicking Test workflow": { "main": [[{ "node": "Submit transcription", "type": "main", "index": 0 }]] },
    "Submit transcription": { "main": [[{ "node": "Wait 15s", "type": "main", "index": 0 }]] },
    "Wait 15s": { "main": [[{ "node": "Check status", "type": "main", "index": 0 }]] },
    "Check status": { "main": [[{ "node": "Is it done?", "type": "main", "index": 0 }]] },
    "Is it done?": {
      "main": [
        [{ "node": "Get SRT", "type": "main", "index": 0 }],
        [{ "node": "Wait 15s", "type": "main", "index": 0 }]
      ]
    }
  }
}


Sex noder. Allt annat — översättning, förbättring, leverans, radering — hänger på samma stomme.

Vad det här faktiskt förändrar

Det intressanta är inte att transkribering automatiseras. Det är vad som slutar vara ett beslut.


När undertexter kostar en eftermiddag av någons tid ransoneras de: de viktiga videorna får dem, resten inte. När de inte kostar något per fil och finns där innan någon hinner fråga slutar de vara ett projekt och blir en egenskap hos ditt innehåll — varje inspelning sökbar, varje video tillgänglig, varje kurs i linje med kraven, utan att det behöver bli ett möte av det.


Det är värt mer än timmarna det sparar.


Redo att bygga? Skapa en API-nyckel — gratisnivån räcker för att köra igenom hela det här. Den fullständiga slutpunktsreferensen hittar du i API-dokumentationen.