{
  "name": "Transcribe audio in seven languages with Speech Expert and optional summaries",
  "nodes": [
    {
      "id": "82acc756-9caf-5e3e-935e-7b3f134e9088",
      "name": "Workflow guide",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -900
      ],
      "parameters": {
        "content": "## Transcribe audio in seven languages with Speech Expert and optional summaries\n### How it works\nUpload one audio or video file and choose Russian, English, Kazakh, Kyrgyz, Uzbek, Tajik or Armenian. Speech Expert recognizes speech asynchronously. Optionally request a short or conversation summary. The workflow polls the operation and returns a UTF-8 transcript with any available summary.\n\n### Setup\nImport this inactive workflow into n8n. Create a Header Auth credential named Speech Expert: set the header name to `Authorization` and its value to `Api-Key YOUR_KEY`. Select it in every HTTP Request node. Create a separate Basic Auth credential and select it in Input form so only intended users can spend your API balance. Keep credentials out of workflow JSON and screenshots. Run the test form before publishing a production form.\n\nTested on n8n 2.46.6 with core nodes. Requires a Speech Expert account, API key and credits. The form defaults to a 200 MiB upload limit; hosting may impose less.\n\nBlank language means Russian. Each language maps to a compatible model; pricing varies. The selected Kazakh, Kyrgyz, Tajik and Armenian Large models use external audio processing. Optional summaries use external text processing and normally follow the transcript language. Summary failure preserves the transcript.\n\n### Customization\nReplace the form with your existing workflow input while preserving binary field names. The API host is fixed deliberately; only send credentials to your trusted Speech Expert endpoint. Review your n8n file-size, binary-storage and execution-retention settings. Errors return a downloadable report. Paid creation requests are never automatically retried. See [setup and API details](https://speech-expert.ru/documentation/integrations/n8n/).\n",
        "width": 980,
        "height": 650,
        "color": 1
      }
    },
    {
      "id": "d5e8b136-fea7-5298-9cbe-5b63d8748a9b",
      "name": "Input form",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.4,
      "position": [
        0,
        0
      ],
      "parameters": {
        "authentication": "basicAuth",
        "formTitle": "Transcribe audio in seven languages with Speech Expert and optional summaries",
        "formDescription": "Choose one of seven speech languages (blank: Russian). Recognition and optional summaries use your Speech Expert balance. Kazakh, Kyrgyz, Tajik and Armenian use external audio processing; summaries use external text processing.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Audio or video file",
              "fieldName": "audio",
              "fieldType": "file",
              "requiredField": true,
              "multipleFiles": false,
              "acceptFileTypes": ".wav,.mp3,.m4a,.ogg,.flac,.mp4,.webm"
            },
            {
              "fieldLabel": "Speech language (blank: Russian)",
              "fieldName": "language",
              "fieldType": "dropdown",
              "requiredField": false,
              "fieldOptions": {
                "values": [
                  {
                    "option": "Russian (ru-RU)"
                  },
                  {
                    "option": "English (en-US)"
                  },
                  {
                    "option": "Kazakh (kk-KZ)"
                  },
                  {
                    "option": "Kyrgyz (ky-KG)"
                  },
                  {
                    "option": "Uzbek (uz-UZ)"
                  },
                  {
                    "option": "Tajik (tg-TJ)"
                  },
                  {
                    "option": "Armenian (hy-AM)"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Optional summary",
              "fieldName": "summaryPreset",
              "fieldType": "dropdown",
              "requiredField": false,
              "fieldOptions": {
                "values": [
                  {
                    "option": "No summary"
                  },
                  {
                    "option": "Short summary"
                  },
                  {
                    "option": "Conversation summary"
                  }
                ]
              }
            }
          ]
        },
        "responseMode": "onReceived",
        "options": {}
      },
      "webhookId": "04046186-790d-5bfb-afbe-2ffcdd491cfe"
    },
    {
      "id": "47870616-885e-5ce6-bb83-297e25e4e978",
      "name": "Validate input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "parameters": {
        "jsCode": "const languages = {\n  \"Russian (ru-RU)\": {\n    \"lang\": \"ru-RU\",\n    \"model\": \"SpeechExpert-STT-RU\"\n  },\n  \"English (en-US)\": {\n    \"lang\": \"en-US\",\n    \"model\": \"Parakeet-EN\"\n  },\n  \"Kazakh (kk-KZ)\": {\n    \"lang\": \"kk-KZ\",\n    \"model\": \"SpeechExpert-STT-KK-Large\"\n  },\n  \"Kyrgyz (ky-KG)\": {\n    \"lang\": \"ky-KG\",\n    \"model\": \"SpeechExpert-STT-KY-Large\"\n  },\n  \"Uzbek (uz-UZ)\": {\n    \"lang\": \"uz-UZ\",\n    \"model\": \"SpeechExpert-STT-UZ\"\n  },\n  \"Tajik (tg-TJ)\": {\n    \"lang\": \"tg-TJ\",\n    \"model\": \"SpeechExpert-STT-TG-Large\"\n  },\n  \"Armenian (hy-AM)\": {\n    \"lang\": \"hy-AM\",\n    \"model\": \"SpeechExpert-STT-HY-Large\"\n  }\n};\n\nconst i = $input.first();\nif (!i.binary?.audio) throw new Error('Upload one audio file.');\nconst language = i.json.language || 'Russian (ru-RU)';\nif (!Object.hasOwn(languages, language)) throw new Error('Choose one of the seven supported speech languages.');\nconst map = {'No summary':'','Short summary':'short','Conversation summary':'conversation'};\nconst selected = i.json.summaryPreset || 'No summary';\nif (!Object.hasOwn(map, selected)) throw new Error('Unknown summary option.');\nreturn [{json:{...languages[language],summaryPreset:map[selected]},binary:i.binary}];\n"
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "0311da72-0554-5407-a31c-4ea456de436e",
      "name": "Create recognition",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://speech-expert.ru/api/stt/v1:recognizeAsync?model=' + encodeURIComponent($json.model) + '&lang=' + encodeURIComponent($json.lang) + '&channelMode=mono&llmCorrection=false&diarization=false' + ($json.summaryPreset ? '&summarizationPreset=' + encodeURIComponent($json.summaryPreset) + '&summarizationDetail=standard' : '') }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 300000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "audio",
              "inputDataFieldName": "audio"
            }
          ]
        }
      },
      "retryOnFail": false,
      "maxTries": 2,
      "waitBetweenTries": 1000,
      "onError": "continueErrorOutput"
    },
    {
      "id": "f3483788-41da-58a8-9670-ad9c65f1260c",
      "name": "Track operation",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "jsCode": "\nconst o = $input.first().json;\nif (o.error || typeof o.id !== 'string' || !o.id.trim()) throw new Error('Invalid operation response: ' + JSON.stringify(o.error || o));\nreturn [{json:{operationId:o.id,deadline:Date.now()+10*60*1000}}];\n"
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "45d7f359-85fe-5ee3-b788-9662285798b5",
      "name": "Wait before polling",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        880,
        0
      ],
      "parameters": {
        "resume": "timeInterval",
        "amount": 5,
        "unit": "seconds"
      },
      "webhookId": "dd9f3b35-3aef-5ee7-bc12-5a780e5fe321"
    },
    {
      "id": "18f7c1e1-f30b-585f-b554-c11f970fd59e",
      "name": "Read operation",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://speech-expert.ru/api/operations/' + encodeURIComponent($('Track operation').first().json.operationId) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 1000,
      "onError": "continueErrorOutput"
    },
    {
      "id": "1f6b9ab8-a58c-5dc8-9d5a-6ce97603c4c0",
      "name": "Inspect operation",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        0
      ],
      "parameters": {
        "jsCode": "\nconst o = $input.first().json;\nconst t = $('Track operation').first().json;\nif (o.error) throw new Error('Operation ' + t.operationId + ': ' + JSON.stringify(o.error));\nif (o.id !== t.operationId || typeof o.done !== 'boolean') throw new Error('Invalid status response for ' + t.operationId);\nif (!o.done && (Date.now() >= t.deadline || $runIndex >= 119))\n  throw new Error('Polling limit reached. Operation ' + t.operationId + ' may still be running; query its status before resubmitting.');\n\nreturn [{json:o}];"
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "0a96f465-7bd8-54c7-a180-97bc4bc645ce",
      "name": "Operation finished?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1540,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "done",
              "leftValue": "={{ $json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "f30391d7-db46-5ab3-b857-763741e927d0",
      "name": "Bounded polling",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        850,
        -220
      ],
      "parameters": {
        "content": "### Wait for completion\nPoll every five seconds for up to ten minutes or 120 responses. HTTP GET can retry once. A timeout does not cancel the paid operation; the error report keeps its ID for recovery.",
        "width": 920,
        "height": 180,
        "color": 7
      }
    },
    {
      "id": "5577e75d-ba20-5d53-950a-01cfa7e5980e",
      "name": "Build transcript",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        0
      ],
      "parameters": {
        "jsCode": "\nconst o = $input.first().json;\nif (!o.response || typeof o.response.result !== 'string') throw new Error('Completed operation has no transcript: ' + o.id);\nlet report = o.response.result || '[No speech recognized]';\nif (o.response.summarization?.summary) report += '\\n\\nSUMMARY\\n' + o.response.summarization.summary;\nif ($('Validate input').first().json.summaryPreset && o.response.summarization_status !== 'ready')\n  report += '\\n\\nSummary status: ' + (o.response.summarization_status || 'unavailable') + '. Transcript preserved.';\nreport += '\\n\\nOperation: ' + o.id;\nreturn [{json:{report}}];\n"
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "8c60cd6a-08a3-5b87-b974-3388f592939b",
      "name": "Upload and recognize",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -220
      ],
      "parameters": {
        "content": "### Send a file\nChoose a language in the form; blank means Russian. Validate input selects the matching model. The multipart binary field is audio. No paid POST is retried. Summary is optional and does not translate the transcript.",
        "width": 760,
        "height": 180,
        "color": 7
      }
    },
    {
      "id": "8ba34620-8899-5d41-8554-b44ad6185126",
      "name": "Download and recover",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1980,
        -220
      ],
      "parameters": {
        "content": "### Download or recover\nThe TXT contains the transcript, available summary and operation ID. Summary failure preserves the transcript. On failure, download the JSON error report and check its operation ID before submitting again; a timeout does not cancel the paid task.",
        "width": 880,
        "height": 180,
        "color": 7
      }
    },
    {
      "id": "212bb766-b8ea-53d9-ae39-33c2b0302e96",
      "name": "Save result file",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        2420,
        0
      ],
      "parameters": {
        "operation": "toText",
        "sourceProperty": "report",
        "binaryPropertyName": "data",
        "options": {
          "fileName": "speech-expert-transcript.txt",
          "encoding": "utf8"
        }
      }
    },
    {
      "id": "fb6030a5-c4c1-52c6-9fb7-cf2884907871",
      "name": "Download result",
      "type": "n8n-nodes-base.form",
      "typeVersion": 2.4,
      "position": [
        2640,
        0
      ],
      "parameters": {
        "operation": "completion",
        "respondWith": "returnBinary",
        "completionTitle": "Your result is ready",
        "completionMessage": "Download the result below.",
        "inputDataFieldName": "data",
        "options": {}
      }
    },
    {
      "id": "aae837be-728c-5d9b-943e-4c436bbb0027",
      "name": "Prepare error report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        520
      ],
      "parameters": {
        "jsCode": "let operationId = null; try { operationId = $('Track operation').first(0).json.operationId ?? null; } catch {}\nif (!operationId) for (const n of ['Create recognition', 'Create synthesis']) { try { const id = $(n).first(0).json.id; if (typeof id === 'string' && id) operationId = id; } catch {} }\nlet recordingIds = []; for (const n of ['Upload source', 'Upload A', 'Upload B']) { try { const id = $(n).first(0).json.id; if (id) recordingIds.push(id); } catch {} }\n\nconst e = $input.first().json;\nconst message = typeof e.error === 'string' ? e.error : (e.error?.message || e.message || 'Request failed. See the n8n execution for details.');\nreturn [{json:{report:JSON.stringify({status:'failed',message,operationId,recordingIds,\n  note:'No paid POST was retried automatically. An interrupted request may still be running. Check its status before submitting again.'},null,2)}}];\n"
      }
    },
    {
      "id": "128ae520-d6ac-5057-9c36-c8081f4943c1",
      "name": "Save error report",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        2200,
        520
      ],
      "parameters": {
        "operation": "toText",
        "sourceProperty": "report",
        "binaryPropertyName": "data",
        "options": {
          "fileName": "speech-expert-error.json",
          "encoding": "utf8"
        }
      }
    },
    {
      "id": "5887ed40-242a-5e3f-9514-5a6f39bbdf4e",
      "name": "Download error report",
      "type": "n8n-nodes-base.form",
      "typeVersion": 2.4,
      "position": [
        2420,
        520
      ],
      "parameters": {
        "operation": "completion",
        "respondWith": "returnBinary",
        "completionTitle": "The request did not finish",
        "completionMessage": "Download the error details. Check an existing operation before retrying.",
        "inputDataFieldName": "data",
        "options": {}
      }
    }
  ],
  "connections": {
    "Input form": {
      "main": [
        [
          {
            "node": "Validate input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate input": {
      "main": [
        [
          {
            "node": "Create recognition",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create recognition": {
      "main": [
        [
          {
            "node": "Track operation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Track operation": {
      "main": [
        [
          {
            "node": "Wait before polling",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait before polling": {
      "main": [
        [
          {
            "node": "Read operation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read operation": {
      "main": [
        [
          {
            "node": "Inspect operation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inspect operation": {
      "main": [
        [
          {
            "node": "Operation finished?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Operation finished?": {
      "main": [
        [
          {
            "node": "Build transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait before polling",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build transcript": {
      "main": [
        [
          {
            "node": "Save result file",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save result file": {
      "main": [
        [
          {
            "node": "Download result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare error report": {
      "main": [
        [
          {
            "node": "Save error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save error report": {
      "main": [
        [
          {
            "node": "Download error report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}
