Skip to content

Media jobs

Speech and transcription jobs, the wait for a result, queue timeouts, and the model list with parameters.

A media job runs one speech or transcription request with a model's own parameters and keeps the result. POST /v1/audio/speech and POST /v1/audio/transcriptions create media jobs too. POST /v1/media/jobs needs the media:generate scope; the GET routes need media:read.

Errors on these routes have the shape {"ok": false, "error": "bad-param", "code": "bad-param", "message": "...", "param": "text"}. Branch on code. Key, scope and rate-limit errors: Errors.

The job object

FieldTypeValue
idstringmj_ and 22 random characters
kindstringspeech or transcription
modelstringThe model id, such as kokoro
labelstringThe caller's label; openai/speech and openai/transcription for jobs of the audio routes
statestringqueued, waiting_device, running, done, failed, cancelled or expired
waitingobject or nullWith waiting_device: reason (offline or busy), note and since
positioninteger or nullPlace in the queue while queued, from 1
prioritystringinteractive or batch
paramsobjectThe parameters as the job runs them, defaults filled in
resultsarrayThe output files once done
errorobject or nullcode, message and retryable of a job that did not finish
created_at, started_at, finished_atstring or nullRFC 3339 times
gen_snumber or nullSeconds the model took
queue_timeout_sinteger or nullSeconds the job may wait in the queue; null: as long as it takes

waiting_device is a queued job whose model cannot start now; the job runs when the model is back. A result entry has index, url, content_type and bytes; a speech result adds duration_s and sample_rate, and a transcription result adds the whole transcript in text and duration_s. url fetches the file without a key. Fields not listed above are for image and video jobs.

Waiting and queue timeouts

A job waits in the queue up to its queue_timeout_s. A job still queued at its timeout ends expired with error.code queue-timeout. Only time in the queue counts: a running job never expires.

KindQueue timeout
speech300 s
transcription60 s

The values above are the defaults. queue_timeout_s in the job body, or X-Queue-Timeout on the audio routes, asks for a shorter wait; a longer value is lowered to the kind's queue timeout. The job object shows the value in effect.

?wait=<s> on POST /v1/media/jobs and GET /v1/media/jobs/{id} holds the request up to s seconds, 90 at most, and at most 85 s from the request's arrival. The answer is 200 once the job is done, failed, cancelled or expired, else 202 with the job as it is. An account holds 16 waiting requests at most; past that, ?wait answers 202 at once.

Create a job

POST /v1/media/jobs queues a speech or transcription job.

Parameters

A JSON body. A top-level field outside the list fails with 400 bad-request.

NameTypeDescription
model requiredstringA model id from GET /v1/media/models. Optional for a speech job whose params.voice is a saved voice vo_…: the voice's default_model runs
paramsobjectThe model's parameters (tables below); a parameter outside the model's schema fails with 400 bad-param
kindstringspeech or transcription; must match the model's kind
prioritystringinteractive (default for speech and transcription) or batch
labelstringFree text on the job, 200 characters at most
idempotency_keystring1 to 200 printable ASCII characters; the Idempotency-Key header does the same. A second request with the key returns the first job for 7 days
queue_timeout_sintegerSeconds the job may wait in the queue, 1 or more
waitnumberQuery parameter: seconds to hold the request, 0 to 90
params of a speech jobTypeDescription
text requiredstringThe text to speak; 5,000 characters at most, 20,000 on kokoro
voicestringA built-in voice, or a voice reference vo_…, up_…, job:<id>/<i> or https://…; required on cloning models other than omnivoice
reference_textstringThe words of the voice clip, 2,000 characters at most; cloning models
instructstringA voice description or delivery, 500 characters at most; omnivoice without voice, and breeze2
languagestringA language code, or auto; values per model
speednumber0.5 to 2.0; kokoro and omnivoice
seedinteger0 to 4294967295; the same seed and parameters give the same audio; cloning models
formatstringmp3 (default), wav or opus
params of a transcription jobTypeDescription
audio requiredstringAn upload up_…, a job result job:<id>/<i>, a saved voice vo_… or an https:// URL
languagestringA language code as a hint, or auto (default)
timestampsbooleantrue adds words with start and end times to the transcript; default false

Each model's exact schema: List models.

Request

curl

curl "https://api.albastov.com/v1/media/jobs?wait=30" \
  -H "Authorization: Bearer $ALBASTOV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "speech", "model": "kokoro", "params": {"text": "The river bends past the old mill.", "voice": "bm_george", "format": "wav"}}'

Python

import json
import os
import urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/media/jobs?wait=30",
    data=json.dumps({"kind": "speech", "model": "kokoro",
                     "params": {"text": "The river bends past the old mill.", "voice": "bm_george", "format": "wav"}}).encode(),
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_API_KEY']}", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as res:
    job = json.load(res)
print(job["id"], job["state"], [r["url"] for r in job["results"]])

JavaScript

const res = await fetch("https://api.albastov.com/v1/media/jobs?wait=30", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    kind: "speech",
    model: "kokoro",
    params: { text: "The river bends past the old mill.", voice: "bm_george", format: "wav" },
  }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const job = await res.json();
console.log(job.id, job.state, job.results.map((r) => r.url));

Response

202 with the job while it is queued or running; 200 once the job is settled within wait, and for a repeated idempotency_key.

Response
{
  "id": "mj_HSRJJxSiza6v7xZULYpwgh",
  "kind": "speech",
  "model": "kokoro",
  "label": "",
  "state": "done",
  "waiting": null,
  "position": null,
  "priority": "interactive",
  "params": {"format": "wav", "language": "auto", "speed": 1, "text": "The river bends past the old mill.", "voice": "bm_george"},
  "results": [
    {
      "index": 0,
      "url": "https://api.albastov.com/v1/media/files/mj_HSRJJxSiza6v7xZULYpwgh/0.wav",
      "content_type": "audio/wav",
      "bytes": 113324,
      "duration_s": 2.36,
      "sample_rate": 24000
    }
  ],
  "error": null,
  "created_at": "2026-11-03T02:16:32.602Z",
  "started_at": "2026-11-03T02:16:32.611Z",
  "finished_at": "2026-11-03T02:16:32.792Z",
  "gen_s": 0.18,
  "queue_timeout_s": 300
}

The response above leaves out the fields that are null for speech results and the fields for image and video jobs.

Errors

StatusCodeMeaning
400bad-requestA top-level field is unknown or has the wrong type, or priority, label or idempotency_key is not valid; param names the field
400bad-parammodel, kind, queue_timeout_s, wait or a parameter fails the model's schema; param names the field
400bad-jsonThe body is not a JSON object
404voice_not_foundNo saved voice with the id, or a voice the key may not use
409voice_not_readyA designed voice still pending, or failed
409idempotency-mismatchThe idempotency key was used before with another request
413too-largeThe body is over 4 MB
422voice_requiredA cloning model that needs voice, without one
422voice_not_supportedThe model does not speak saved voices; models lists the models that do
429budget-exhaustedThe account's daily media time is used up; resets_at is the next 00:00 UTC
429too-many-openThe key has 1,000 jobs queued or running
503voice_model_unavailableNo model that speaks saved voices is online for a job without model

Get a job

GET /v1/media/jobs/{id} returns a job of the key's account.

Parameters

NameTypeDescription
id requiredstringThe job id mj_…, in the path; also X-Job-Id and job_id of the audio routes
waitnumberQuery parameter: seconds to hold the request until the job settles, 0 to 90

Request

The example queues a Kokoro job without waiting, then reads the job with wait=30.

curl

JOB=$(curl https://api.albastov.com/v1/media/jobs \
  -H "Authorization: Bearer $ALBASTOV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "speech", "model": "kokoro", "params": {"text": "The river bends past the old mill."}}' \
  | python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])')

curl "https://api.albastov.com/v1/media/jobs/$JOB?wait=30" \
  -H "Authorization: Bearer $ALBASTOV_API_KEY"

Python

import json
import os
import urllib.request

BASE = "https://api.albastov.com/v1"
HEADERS = {"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_API_KEY']}", "Content-Type": "application/json"}

body = {"kind": "speech", "model": "kokoro", "params": {"text": "The river bends past the old mill."}}
req = urllib.request.Request(f"{BASE}/media/jobs", data=json.dumps(body).encode(), headers=HEADERS)
with urllib.request.urlopen(req, timeout=30) as res:
    job = json.load(res)

req = urllib.request.Request(f"{BASE}/media/jobs/{job['id']}?wait=30", headers=HEADERS)
with urllib.request.urlopen(req, timeout=120) as res:
    job = json.load(res)
print(job["id"], job["state"])

JavaScript

const base = "https://api.albastov.com/v1";
const headers = { Authorization: `Bearer ${process.env.ALBASTOV_API_KEY}`, "Content-Type": "application/json" };

const created = await fetch(`${base}/media/jobs`, {
  method: "POST",
  headers,
  body: JSON.stringify({ kind: "speech", model: "kokoro", params: { text: "The river bends past the old mill." } }),
});
if (!created.ok) throw new Error(`${created.status} ${await created.text()}`);
const { id } = await created.json();

const res = await fetch(`${base}/media/jobs/${id}?wait=30`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const job = await res.json();
console.log(job.id, job.state);

Response

200 with the job object, as in Create a job; 202 when wait passes before the job settles.

Errors

StatusCodeMeaning
400bad-paramwait is not a number of seconds
404not-foundNo job with the id on the key's account

List models

GET /v1/media/models returns the models the key may use, each with its parameter schema. Within a kind, recommended models come first.

Parameters

NameTypeDescription
kindstringQuery parameter: speech or transcription; without kind, every kind. An unknown kind returns an empty list

Request

curl

curl "https://api.albastov.com/v1/media/models?kind=speech" \
  -H "Authorization: Bearer $ALBASTOV_API_KEY"

Python

import json
import os
import urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/media/models?kind=speech",
    headers={"User-Agent": "my-app/1.0", "Authorization": f"Bearer {os.environ['ALBASTOV_API_KEY']}"},
)
with urllib.request.urlopen(req, timeout=30) as res:
    for m in json.load(res)["models"]:
        print(m["id"], m["recommended"], m["available"], sorted(m["params"]))

JavaScript

const res = await fetch("https://api.albastov.com/v1/media/models?kind=speech", {
  headers: { Authorization: `Bearer ${process.env.ALBASTOV_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
for (const m of (await res.json()).models) console.log(m.id, m.recommended, m.available, Object.keys(m.params));

Response

Response
{
  "models": [
    {
      "id": "kokoro",
      "kind": "speech",
      "label": "Kokoro 82M",
      "family": "kokoro",
      "notes": "Fast default: 54 built-in voices in 8 languages. No cloning.",
      "params": {
        "text": {"type": "string", "required": true, "max_length": 20000},
        "voice": {"type": "enum", "values": ["af_heart", "af_alloy", "bf_emma"], "default": "af_heart", "description": "A built-in voice; its prefix is its language."},
        "speed": {"type": "float", "min": 0.5, "max": 2.0, "step": 0.05, "default": 1.0, "description": "Speaking rate multiplier."},
        "format": {"type": "enum", "values": ["mp3", "wav", "opus"], "default": "mp3", "description": "The output file: mp3, wav or opus (Ogg)."}
      },
      "available": true,
      "queueable": true,
      "recommended": true
    }
  ]
}

The response above is shortened to one model, three of its voices and four of its parameters.

FieldValue
id, kind, label, familyThe model id, its kind, its display name and its model family
notesOne line on what the model does
paramsThe parameter schema: per name, type (string, enum, float, int, bool, seed, audio), and required, default, min, max, step, values, max_length, description where set. An audio parameter takes a voice reference
availabletrue when a job for the model starts without waiting
queueabletrue when the model accepts jobs; a job waits in the queue while available is false
recommendedtrue for the recommended models of the kind

Other fields of the response are for albastov.com's own pages.