Batch transcription API: transcribe many audio files

Batch transcription by API is a loop: one speech-to-text job per file, keyed by the file's id, collected by webhook or polling. How it works on Sume.

5 min readSume
All posts

With a job-based speech-to-text API, batch transcription is a loop: submit one job per audio file with an idempotency key derived from that file, let the service queue what it can't start yet, and save each transcript when its job finishes. Sume STT 1.0 works this way. Every file is its own POST /v1/stt-1.0/transcribe job, and jobs past your workspace's concurrency limit wait as queued while queue capacity remains.

STT 1.0 is specified in the OpenAPI document behind the Sume API reference, which the API reference docs page names as the source for exact request and response shapes. Queueing and polling follow Generation admission and Jobs and results. All were read on 2026-09-27. For a single file, see Speech-to-text API with word timestamps.

Is there a batch endpoint for many files?

No. An STT 1.0 request takes one audio_url string and rejects fields its schema doesn't define, so there is no list of files to send. The batch is a loop on your server, one request per file:

  • audio_url: a public HTTPS URL for the file. The request has no upload field, so each file must already be reachable at a URL.
  • duration_seconds: the file's length, 1–600. It sizes the usage reservation; omit it and Sume reserves 1 minute. Files over 10 minutes need splitting into parts first.
  • segmentation: { "mode": "sentence" } adds sentence timestamps to each result.
  • webhook_url: a public HTTPS callback for when each job ends. Leave it out to poll instead.
  • Idempotency-Key: built from the file's own id, so a resend with the same body returns the original job instead of a second one.
// Server-side Node 18+. files: [{ id, url, seconds }] from your own storage.
const jobIds = new Map(); // your file id -> Sume job id; persist it
for (const file of files) {
  const res = await fetch("https://api.sume.com/v1/stt-1.0/transcribe", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.SUME_API_KEY,
      "Content-Type": "application/json",
      "Idempotency-Key": "stt-" + file.id, // same key, same body on every retry
    },
    body: JSON.stringify({
      audio_url: file.url,
      duration_seconds: file.seconds,
      segmentation: { mode: "sentence" },
      webhook_url: "https://example.com/webhooks/sume",
    }),
  });
  if (res.status === 429) break; // queue full or rate limited: wait, then resume
  if (!res.ok) throw new Error(await res.text());
  const { data } = await res.json();
  jobIds.set(file.id, data.request_id); // request_id is the job id
}

What happens when I submit more files than can run at once?

The extra jobs wait. Sume admits paid jobs queue-first: a valid job starts at once or waits in queued until a workspace concurrency slot opens. In current code, speech_to_text is one of the job types that take such a slot.

Your plan sets the concurrency limit, and queue capacity defaults to max(3, concurrency_limit × 5). Submit responses carry a generation_limits snapshot when Sume can compute one; video job concurrency and queueing shows how to size each wave from it. Your loop should handle these responses:

From Generation admission and Jobs and results, read 2026-09-27.
ResponseWhat it meansWhat the loop does
2xxThe job exists and paid work is in flight; that doesn't mean it finished.Store the job id and move to the next file.
429 queue_fullThe workspace has no accepted generation capacity left.Stop, wait for jobs to finish, then retry the same file with the same key.
429 rate_limitedRequest volume passed an abuse-protection limit.Back off, using retry-after when present, and retry with the same key.
402 insufficient_creditsThe balance can't cover the estimate. Nothing starts.Stop the batch until the balance can cover it.
409 idempotency_conflictThe key was already used with a different payload.Fix how you build keys; reuse a key only for exact retries.

How do I collect the transcripts?

Take webhooks, poll, or both. The docs call a webhook a delivery optimization, not your only recovery path, so keep polling available.

  • Webhook: Sume sends a signed job.completed, job.failed, or job.canceled event when each job ends, with no progress callbacks. Verify its signature, then fetch that job's result.
  • Polling: call GET /v1/jobs/{id}/status with backoff, honoring next_poll_after_seconds, until terminal is true. GET /v1/jobs/{id}/result answers 409 job_not_completed until the job has completed.
  • Recovery: GET /v1/jobs?type=speech_to_text lists jobs newest first, up to 100 per page. Each row carries the idempotency_key it was created with, so join results to your files on that key, not on position.
  • A completed result has text, words[] with each word's start and end in seconds, segments[] if you asked for sentences, and language_code when available.
  • A resend under the same key returns the original job, so to try a failed file again, send it under a new key, such as the file id plus an attempt number.

How much does batch transcription cost?

The same as transcribing the files one by one: $0.01 per audio minute on API pricing, plus a 5.5% agent fee by default. A backlog of 100 recordings of 5 minutes each is 500 audio minutes, or $5.00 before the fee.

Sume reserves each job's estimate when it accepts the submit, captures it when the job completes, and releases or refunds it, where applicable, when a job fails. Cancel queued jobs you no longer need with POST /v1/jobs/{id}/cancel. Once a job has started, cancel answers 409 job_generation_already_started and the job runs to the end.

What are the limits?

  • One file per request, up to 10 minutes of audio: duration_seconds stops at 600.
  • webhook_url must be public HTTPS and at most 2,048 characters. Localhost and private-network URLs are rejected.
  • mode: "sync" holds a request for at most 30 seconds, so use async or webhook for a batch.
  • Accepted jobs per workspace stop at the concurrency limit plus queue capacity; past that, submits answer 429 queue_full.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume