SSE API

Import Progress

Connection Information

ItemValue
Base Pathhttps://vas-poc.vurbo.ai/api/v1/sse
ProtocolHTTP + Server-Sent Events (SSE)
Data Formattext/event-stream
AuthenticationHeader X-API-Key: {KEY}

Note: The browser's native EventSource API does not support custom headers. Use the fetch API together with ReadableStream, or use an SSE client library that supports headers.


Endpoint Overview

MethodEndpointDescription
GET/api/v1/sse/imports/{importId}/progressTrack audio import progress in real time

GET /api/v1/sse/imports/{importId}/progress

Description

Track the processing progress of an audio import in real time. After the connection is established, progress updates are continuously pushed over the SSE stream until the import completes, fails, or the connection times out.

Use Cases

  • Display a real-time progress bar after uploading an audio file
  • Track the progress of each stage: audio conversion, transcription, translation, and summary

Authentication

Header: X-API-Key (see Authentication)

Request Parameters

ParameterLocationTypeRequiredDescription
importIdpathstringYesImport task ID (UUID)

Request Example

curl -N "https://vas-poc.vurbo.ai/api/v1/sse/imports/550e8400-e29b-41d4-a716-446655440000/progress" \
  -H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW"
// Use the fetch API (because EventSource does not support headers)
async function connectSSE(importId, apiKey) {
  const response = await fetch(
    `https://vas-poc.vurbo.ai/api/v1/sse/imports/${importId}/progress`,
    {
      headers: {
        'X-API-Key': apiKey
      }
    }
  );
  const reader = response.body.getReader();
  // ... handle SSE events
}

Event Sequence

Scenario 1: Import still in progress
┌────────────────────────────────────────────────────┐
│ 1. connected       → Connection confirmed          │
│ 2. progress        → Send current progress          │
│ 3. progress ×N     → Pushed continuously on change  │
│    heartbeat ×N    → Sent every 15s if no change    │
│ 4. completed       → Import succeeded, connection ends │
│    or failed       → Import failed, connection ends    │
│    or timeout      → Exceeded 15 minutes, connection ends │
└────────────────────────────────────────────────────┘

Scenario 2: Import already completed (terminal state)
┌────────────────────────────────────────────────────┐
│ 1. connected       → Connection confirmed          │
│ 2. progress        → Send final progress            │
│ 3. completed       → Send completed event directly and end │
│    or failed       → Send failed event directly and end    │
└────────────────────────────────────────────────────┘

Event Formats


connected

Connection success confirmation.

event: connected
data: {"message":"Import progress service connected (importId: 550e8400-e29b-41d4-a716-446655440000)"}
{
  "message": "Import progress service connected (importId: xxx)"
}
FieldTypeDescription
messagestringConnection confirmation message

progress

Processing progress update. Sent whenever the progress percentage changes.

event: progress
data: {"import_id":"550e8400-...","status":"processing","stage":"transcribing","progress":45,"message":"Transcribing..."}
{
  "import_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "stage": "transcribing",
  "progress": 45,
  "message": "Transcribing..."
}
FieldTypeDescription
import_idstringImport task ID (UUID)
statusstringImport status, see "Status and Stage" below
stagestring | nullCurrent processing stage, see "Status and Stage" below
progressintegerProgress percentage (0-100)
messagestringHuman-readable progress message

Status (status) values:

ValueDescription
pendingWaiting to be processed
processingProcessing
completedCompleted
failedFailed

Stage (stage) values and corresponding progress ranges:

ValueDescriptionProgress Range
convertingAudio format conversion0% - 10%
transcribingSpeech-to-text10% - 60%
translatingText translation60% - 85%
summarizingSummary generation85% - 100%
nullNot yet started (pending status)

completed

Import processing completed. The connection closes automatically after this event is received.

event: completed
data: {"import_id":"550e8400-...","status":"completed","task_id":"abc123-...","message":"Processing completed"}
{
  "import_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "task_id": "abc123-e29b-41d4-a716-446655440000",
  "message": "Processing completed"
}
FieldTypeDescription
import_idstringImport task ID (UUID)
statusstringAlways completed
task_idstringThe generated recording ID (recording_id), which can be used for subsequent queries
messagestringAlways Processing completed

Edge case (v1.3.5): If no speech content can be recognized in the audio—because of silence, low volume, noise, or a mismatch between the recognition language and the audio—the import still ends with a completed event (not failed), and task_id is produced normally. However, the transcript later loaded via GET /api/v1/sse/history/transcribe/{taskId} has a sentence count of 0. Clients should use the sentence count to decide whether to show an empty "no speech content" state, rather than treating this as an error. See File Import Guide – Behavior When Audio Cannot Be Recognized.


failed

Import processing failed. The connection closes automatically after this event is received.

event: failed
data: {"import_id":"550e8400-...","status":"failed","error_code":"import_invalid_format","error_message":"Unsupported audio format"}
{
  "import_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "failed",
  "error_code": "import_invalid_format",
  "error_message": "Unsupported audio format"
}
FieldTypeDescription
import_idstringImport task ID (UUID)
statusstringAlways failed
error_codestringError code
error_messagestringHuman-readable error message

heartbeat

Heartbeat event. Sent every 15 seconds when there is no progress change, to keep the connection alive.

event: heartbeat
data: {"timestamp":1708761600}
{
  "timestamp": 1708761600
}
FieldTypeDescription
timestampintegerUnix timestamp

timeout

Connection timeout event. Sent when the import does not complete within 15 minutes; the connection then closes automatically.

event: timeout
data: {"message":"Connection timed out"}
{
  "message": "Connection timed out"
}
FieldTypeDescription
messagestringAlways Connection timed out

Specific Error Codes

After the connection is established, if the import task does not exist, it is returned via an error event:

Error CodeDescriptionRecommended Action
import_not_foundThe specified import task was not foundVerify that the importId is correct

Frontend Example

async function trackImportProgress(importId, apiKey) {
  const response = await fetch(
    `https://vas-poc.vurbo.ai/api/v1/sse/imports/${importId}/progress`,
    {
      headers: {
        'X-API-Key': apiKey
      }
    }
  );

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });

    // Parse SSE format: event: xxx\ndata: {...}\n\n
    const events = buffer.split('\n\n');
    buffer = events.pop(); // Keep the incomplete part

    for (const eventStr of events) {
      if (!eventStr.trim()) continue;

      const lines = eventStr.split('\n');
      let eventType = '';
      let eventData = '';

      for (const line of lines) {
        if (line.startsWith('event: ')) {
          eventType = line.slice(7);
        } else if (line.startsWith('data: ')) {
          eventData = line.slice(6);
        }
      }

      if (!eventType || !eventData) continue;

      const data = JSON.parse(eventData);

      switch (eventType) {
        case 'connected':
          console.log('Connected:', data.message);
          break;

        case 'progress':
          console.log(`[${data.stage}] ${data.progress}% - ${data.message}`);
          // Update the UI progress bar
          updateProgressBar(data.progress, data.stage, data.message);
          break;

        case 'completed':
          console.log('Import completed! Recording ID:', data.task_id);
          // Navigate to the recording details page
          navigateToRecording(data.task_id);
          break;

        case 'failed':
          console.error('Import failed:', data.error_code, data.error_message);
          // Show the error message
          showError(data.error_message);
          break;

        case 'heartbeat':
          console.log('Heartbeat:', new Date(data.timestamp * 1000));
          break;

        case 'timeout':
          console.warn('Connection timed out:', data.message);
          break;

        case 'error':
          console.error('Error:', data);
          break;
      }
    }
  }
}

Version: V1.5.7 Last Updated: 2026-05-20

Copyright © 2026