ScribeZip API

Build transcription into your product

Upload media or submit a remote file URL through a simple REST API, then retrieve an asynchronous timestamped transcript.

Overview

Audio + video

All transcription formats supported by the site.

Async jobs

Requests return a job ID immediately. Each account may have up to 50 active jobs, processed asynchronously by the background queue.

US$0.04 / hour

Credits are charged by rounded-up minute.

Authentication

Every request needs a Bearer API key in the Authorization header. The full secret is shown only once when created.

HTTP header
Authorization: Bearer sz_live_your_api_key

Quick start: submit a remote file

The server must be able to download the HTTPS URL directly without a login or cookies. POST returns 202 immediately and transcription runs asynchronously. Reuse the same Idempotency-Key during network retries to avoid duplicate jobs.

cURL
curl -X POST https://scribezip.com/api/v1/transcriptions \
  -H "Authorization: Bearer $SCRIBEZIP_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/meeting.mp3",
    "language": "auto",
    "accuracy_mode": "balanced"
  }'

Multipart upload for local files

  1. Create an upload session with the name, byte size, and MIME type.
  2. Upload every part in order using the returned part_size.
  3. Call complete_url to start processing.
Node.js 22+
import { open, stat } from "node:fs/promises";

const apiKey = process.env.SCRIBEZIP_API_KEY;
const path = "./meeting.mp3";
const file = await stat(path);
const idempotencyKey = crypto.randomUUID();

async function requestJson(url, init) {
  const response = await fetch(url, init);
  const body = await response.json().catch(() => null);
  if (!response.ok) {
    throw new Error(body?.error?.message ?? `HTTP ${response.status}`);
  }
  return body;
}

const created = await requestJson("https://scribezip.com/api/v1/transcriptions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    file_name: "meeting.mp3",
    file_size: file.size,
    mime_type: "audio/mpeg",
    language: "auto",
  }),
});

const handle = await open(path, "r");
const { part_size, part_count, part_url, complete_url } = created.upload;

for (let part = 1; part <= part_count; part += 1) {
  const length = Math.min(part_size, file.size - (part - 1) * part_size);
  const buffer = Buffer.alloc(length);
  await handle.read(buffer, 0, length, (part - 1) * part_size);
  await requestJson(`${part_url}?part_number=${part}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Length": String(length),
    },
    body: buffer,
  });
}
await handle.close();

const job = await requestJson(complete_url, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}` },
});

console.log(job.data.id);

After a network interruption, retry creation with the same Idempotency-Key. A part may also be uploaded again with the same part_number, and complete_url may be called again.

API reference

Endpoints and success statuses

HTTPEndpointStatusPurpose
POST/api/v1/transcriptions202Submit a remote HTTPS file
POST/api/v1/transcriptions201Create a local-file upload session
PUT/api/v1/transcriptions/{id}/upload?part_number=N200Upload one file part
POST/api/v1/transcriptions/{id}/complete202 / 200Start processing; a completed replay returns 200
GET/api/v1/transcriptions/{id}200Get one job and its result
GET/api/v1/transcriptions200List the latest 100 API records
DELETE/api/v1/transcriptions/{id}200Cancel a job or delete its result

Create-job parameters

NameLocation/typeRequiredDescription
urlJSON / stringYes (remote)A directly downloadable HTTPS URL
file_nameJSON / stringYes (local)File name including its extension
file_sizeJSON / integerYes (local)Exact file size in bytes
mime_typeJSON / stringNoThe file MIME type; defaults to application/octet-stream
languageJSON / stringNoDefaults to auto
accuracy_modeJSON / stringNofast, balanced (default), or accurate
translate_to_englishJSON / booleanNoSet true to translate into English
Idempotency-KeyHeader / stringNo; recommended1–128 letters, numbers, dots, underscores, colons, or hyphens

The same Idempotency-Key and parameters return the original job (HTTP 200 with Idempotency-Replayed: true). Reusing the key with different parameters returns 409 idempotency_conflict.

Response headers

  • Location The status URL for the job.
  • Retry-After Seconds to wait before polling or retrying.
  • Idempotency-Replayed: true An existing job was returned; no duplicate was created.

Get job status and results

Follow the Retry-After response header when polling (normally 3 seconds). Status can be accepted, uploading, inspecting, preprocessing, queued, processing, merging, completed, failed, or deleted. One account can have up to 50 active jobs.

cURL
curl https://scribezip.com/api/v1/transcriptions/REQUEST_ID \
  -H "Authorization: Bearer $SCRIBEZIP_API_KEY"
Node.js 22+ polling
const apiKey = process.env.SCRIBEZIP_API_KEY;
const requestId = "REQUEST_ID";
const terminalStatuses = new Set(["completed", "failed", "deleted"]);

while (true) {
  const response = await fetch(
    `https://scribezip.com/api/v1/transcriptions/${requestId}`,
    { headers: { Authorization: `Bearer ${apiKey}` } },
  );
  const body = await response.json();
  if (!response.ok) {
    throw new Error(body.error?.message ?? `HTTP ${response.status}`);
  }
  if (terminalStatuses.has(body.data.status)) {
    console.log(body.data);
    break;
  }
  const retryAfter = Number(response.headers.get("retry-after") ?? 3);
  await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
}
200 response
{
  "data": {
    "id": "request_id",
    "status": "completed",
    "progress": 100,
    "file_name": "meeting.mp3",
    "source_kind": "audio",
    "duration_seconds": 330,
    "billed_minutes": 6,
    "credits_charged": 6,
    "transcript": {
      "text": "Welcome to the meeting…",
      "segments": [
        { "start": 0, "end": 2.4, "text": "Welcome to the meeting…" }
      ]
    },
    "error": null
  },
  "meta": { "balance_credits": 14994 }
}

DELETE /api/v1/transcriptions/REQUEST_ID cancels an active job or deletes a completed result and any retained media. Credits charged to an unfinished job are refunded; deleting a completed job does not issue a refund.

Billing

RateUS$0.04 / transcription hour
Credit unit1 credit = 1 billed minute
RoundingUnder 1 minute → 1 minute; 5:30 → 6 minutes
Top-upsUS$10 / $20 / $50 / $100
First top-up refundContact support within 7 days of the first API top-up; only the unused portion of that top-up is refunded proportionally at its original purchase price
Failed or cancelledCredits charged to unfinished jobs are refunded; deleting completed jobs is not refundable

Data retention

API usage history is retained for 30 days.
Actual uploaded video payloads are deleted after processing.
Audio uploaded directly or extracted from video is retained for up to 60 days unless deleted sooner.
API keys are stored only as irreversible hashes; full secrets cannot be revealed again.

Common error codes

HTTPCodeMeaning
400invalid_json / invalid_idempotency_key / empty_uploadInvalid request body, idempotency key, or upload body
400invalid_part / part_size_mismatch / invalid_partsInvalid part number, size, or completeness
400invalid_remote_url / insecure_remote_url / remote_host_blockedThe remote URL is invalid, insecure, or blocked
401invalid_api_keyMissing, invalid, or revoked key
402insufficient_creditsNot enough API credits
404not_foundJob or upload session not found
409idempotency_pending / idempotency_conflictThe original request is pending, or the key was reused with different parameters
409invalid_status / upload_session_missing / upload_incompleteThe job state or upload session does not allow this operation
410upload_expiredThe upload session expired
413file_too_largeThe file size is invalid or exceeds the limit
415unsupported_file / invalid_file_signatureUnsupported format, or content does not match the extension
422remote_host_unresolvedThe remote host cannot be resolved
429active_job_limitThe account already has 50 active jobs
429rate_limitedToo many requests
500request_failed / upload_failed / complete_failedRequest creation, part upload, or processing start failed
502remote_dns_failedThe remote host could not be verified; retryable
503provider_unavailable / queue_unavailableThe provider or queue is unavailable; retry after Retry-After