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.
Authorization: Bearer sz_live_your_api_keyQuick 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 -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
- Create an upload session with the name, byte size, and MIME type.
- Upload every part in order using the returned part_size.
- Call complete_url to start processing.
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
| HTTP | Endpoint | Status | Purpose |
|---|---|---|---|
| POST | /api/v1/transcriptions | 202 | Submit a remote HTTPS file |
| POST | /api/v1/transcriptions | 201 | Create a local-file upload session |
| PUT | /api/v1/transcriptions/{id}/upload?part_number=N | 200 | Upload one file part |
| POST | /api/v1/transcriptions/{id}/complete | 202 / 200 | Start processing; a completed replay returns 200 |
| GET | /api/v1/transcriptions/{id} | 200 | Get one job and its result |
| GET | /api/v1/transcriptions | 200 | List the latest 100 API records |
| DELETE | /api/v1/transcriptions/{id} | 200 | Cancel a job or delete its result |
Create-job parameters
| Name | Location/type | Required | Description |
|---|---|---|---|
| url | JSON / string | Yes (remote) | A directly downloadable HTTPS URL |
| file_name | JSON / string | Yes (local) | File name including its extension |
| file_size | JSON / integer | Yes (local) | Exact file size in bytes |
| mime_type | JSON / string | No | The file MIME type; defaults to application/octet-stream |
| language | JSON / string | No | Defaults to auto |
| accuracy_mode | JSON / string | No | fast, balanced (default), or accurate |
| translate_to_english | JSON / boolean | No | Set true to translate into English |
| Idempotency-Key | Header / string | No; recommended | 1–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 https://scribezip.com/api/v1/transcriptions/REQUEST_ID \
-H "Authorization: Bearer $SCRIBEZIP_API_KEY"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));
}{
"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
| Rate | US$0.04 / transcription hour |
|---|---|
| Credit unit | 1 credit = 1 billed minute |
| Rounding | Under 1 minute → 1 minute; 5:30 → 6 minutes |
| Top-ups | US$10 / $20 / $50 / $100 |
| First top-up refund | Contact 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 cancelled | Credits charged to unfinished jobs are refunded; deleting completed jobs is not refundable |
Data retention
Common error codes
| HTTP | Code | Meaning |
|---|---|---|
400 | invalid_json / invalid_idempotency_key / empty_upload | Invalid request body, idempotency key, or upload body |
400 | invalid_part / part_size_mismatch / invalid_parts | Invalid part number, size, or completeness |
400 | invalid_remote_url / insecure_remote_url / remote_host_blocked | The remote URL is invalid, insecure, or blocked |
401 | invalid_api_key | Missing, invalid, or revoked key |
402 | insufficient_credits | Not enough API credits |
404 | not_found | Job or upload session not found |
409 | idempotency_pending / idempotency_conflict | The original request is pending, or the key was reused with different parameters |
409 | invalid_status / upload_session_missing / upload_incomplete | The job state or upload session does not allow this operation |
410 | upload_expired | The upload session expired |
413 | file_too_large | The file size is invalid or exceeds the limit |
415 | unsupported_file / invalid_file_signature | Unsupported format, or content does not match the extension |
422 | remote_host_unresolved | The remote host cannot be resolved |
429 | active_job_limit | The account already has 50 active jobs |
429 | rate_limited | Too many requests |
500 | request_failed / upload_failed / complete_failed | Request creation, part upload, or processing start failed |
502 | remote_dns_failed | The remote host could not be verified; retryable |
503 | provider_unavailable / queue_unavailable | The provider or queue is unavailable; retry after Retry-After |