Media jobs
Speech, transcription and image jobs, the wait for a result, queue timeouts, and the model list with parameters.
Access: Keys with the media:read or media:generate scope, on an account with media access granted by Albastar Corp.
A media job runs one speech, transcription or image 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
| Field | Type | Value |
|---|---|---|
id | string | mj_ and 22 random characters |
kind | string | speech, transcription or image |
model | string | The model id, such as kokoro or zimage-turbo |
label | string | The caller's label; openai/speech and openai/transcription for jobs of the audio routes, voices/preview and voices/design for the jobs that make a voice's preview and a designed voice |
state | string | queued, waiting_device, running, done, failed, cancelled or expired |
waiting | object or null | With waiting_device: reason (offline or busy), note and since |
position | integer or null | Place in the queue while queued, from 1 |
priority | string | interactive or batch |
params | object | The parameters as the job runs them, defaults filled in |
results | array | The output files once done |
error | object or null | code, message and retryable of a job that did not finish |
created_at, started_at, finished_at | string or null | RFC 3339 times |
queue_timeout_s | integer or null | Seconds the job may wait in the queue; null: as long as it takes |
trace_id | string | The job's trace id, 32 lowercase hex characters, as X-Trace-Id of the audio routes; "" for a job made before the field existed |
waiting_device is a queued job while the model service is offline or busy with other work; the job keeps its place
and starts when the service is back. note says which, in one of two sentences:
reason | note |
|---|---|
offline | The model service is offline. The job starts when the service is back. |
busy | The model service is busy with other work. The job starts when the service is free. |
A result entry has index, url, content_type and bytes; a speech result adds duration_s and sample_rate,
a transcription result adds the whole transcript in text and duration_s, and an image result adds width,
height, seed and thumb_url, a small WebP preview of the image. url and thumb_url fetch the file without a
key. A job carries more fields than the table above lists; code that reads jobs can ignore the other fields.
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.
| Kind | Queue timeout |
|---|---|
speech | 300 s |
transcription | 60 s |
image | None: the job waits as long as it takes, and queue_timeout_s is null |
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 until the job settles. s is
a number of seconds, 0 or more; the hold ends after s seconds, and 85 s after the request arrives at the latest.
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. An image job often runs
longer than one hold: read the job again with ?wait until state is done.
Create a job
POST /v1/media/jobs queues a speech, transcription or image job.
Parameters
A JSON body. A top-level field outside the list fails with 400 bad-request.
| Name | Type | Description |
|---|---|---|
model required | string | A 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 |
params | object | The model's parameters (tables below); a parameter outside the model's schema fails with 400 bad-param |
kind | string | speech, transcription or image; must match the model's kind |
priority | string | interactive or batch; a speech or transcription job without priority runs as interactive, an image job without priority as batch. An interactive job starts before the batch jobs queued ahead of it |
label | string | Free text on the job, 200 characters at most |
idempotency_key | string | 1 to 200 printable ASCII characters; the Idempotency-Key header does the same. A second request of the same key with the same idempotency_key returns the first job for 7 days |
queue_timeout_s | integer | Seconds the job may wait in the queue, 1 or more |
wait | number | Query parameter: seconds to hold the request, 0 or more; 85 s at most from the request's arrival |
params of a speech job | Type | Description |
|---|---|---|
text required | string | The text to speak; 5,000 characters at most, 20,000 on kokoro |
voice | string | A built-in voice, or a voice reference vo_…, up_…, job:<id>/<i> or https://…; required on cloning models other than omnivoice |
reference_text | string | The words of the voice clip, 2,000 characters at most; cloning models |
instruct | string | A voice description or delivery, 500 characters at most. Used by breeze2, and by omnivoice when voice is not set |
language | string | A language code, or auto; values per model |
speed | number | 0.5 to 2.0; kokoro and omnivoice |
seed | integer | 0 to 4294967295; the same seed and parameters give the same audio; cloning models |
format | string | mp3 (default), wav or opus |
params of a transcription job | Type | Description |
|---|---|---|
audio required | string | An upload up_…, a job result job:<id>/<i>, a saved voice vo_… or an https:// URL |
language | string | auto (default) or one of Parakeet's 25 language codes; Parakeet detects the spoken language itself and does not use the code |
timestamps | boolean | true adds words with start and end times to the transcript; default false |
params of an image job | Type | Description |
|---|---|---|
prompt required | string | What the image shows; 4,000 characters at most |
negative_prompt | string | What the image leaves out, 2,000 characters at most; zimage-turbo |
aspect | string | 1.91:1, 16:9, 4:5, 1:1 (default), 3:2, 2:3, 9:16 or 21:9; not used when width and height are set |
width, height | integer | The exact size in pixels, 512 to 2048 each; set both or neither |
count | integer | Images per job, 1 (default) to 8; image i uses seed + i |
seed | integer | 0 to 9007199254740991; without seed, a random seed, returned in params and on each result |
steps | integer | 1 to 50; default 8 on zimage-turbo, 24 on flux-dev |
init_image | string | An image to start from: an upload up_…, a job result job:<id>/<i> or an https:// URL. With init_image, the job runs image to image |
strength | number | 0.05 to 1.0, default 0.65: how far the result departs from init_image |
loras | array | Up to 4 {"name", "weight"} items, weight -2.0 to 2.0; names from the model's loras list in GET /v1/media/models |
format | string | png (default), webp or jpg |
The size of an image to image result follows aspect, or width and height, not the size of init_image. Each
image model takes more parameters (cfg, sampler, scheduler, shift, guidance); GET /v1/media/models
lists them with their ranges and defaults.
Each model's exact schema: List models.
Request
curl
curl "https://api.albastov.com/v1/media/jobs?wait=30" \
-H "Authorization: Bearer $ALBASTAR_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
body = {
"kind": "speech",
"model": "kokoro",
"params": {
"text": "The river bends past the old mill.",
"voice": "bm_george",
"format": "wav",
},
}
req = urllib.request.Request(
"https://api.albastov.com/v1/media/jobs?wait=30",
data=json.dumps(body).encode(),
headers={
"User-Agent": "my-app/1.0",
"Authorization": f"Bearer {os.environ['ALBASTAR_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.ALBASTAR_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. Without wait, a
repeated idempotency_key answers 200 with the first job.
{
"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": "…/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",
"queue_timeout_s": 300
}The response above shows the fields of the table and shortens url to its path; the URL starts with
https://api.albastov.com.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request | A top-level field is unknown or has the wrong type, or priority, label or idempotency_key is not valid; param names the field |
| 400 | bad-param | model, kind, queue_timeout_s, wait or a parameter fails the model's schema; param names the field |
| 400 | bad-json | The body is not a JSON object |
| 404 | voice_not_found | No saved voice with the id, or a voice the key may not use |
| 409 | voice_not_ready | A designed voice still pending, or failed |
| 409 | idempotency-mismatch | This idempotency key was used with a different request. |
| 413 | too-large | The body is over 4 MB |
| 422 | voice_required | A cloning model that needs voice, without one |
| 422 | voice_not_supported | The model does not speak saved voices; models lists the models that do |
| 422 | prompt-refused | The account's prompts are reviewed, and the image prompt was refused: That prompt isn't allowed here. |
| 429 | budget-exhausted | The account's daily generation time is used up: That would go over today's generation time. It starts over at midnight UTC.; resets_at is the next 00:00 UTC |
| 429 | too-many-open | The key has 1,000 jobs queued or running, the limit per key: You have 1000 jobs queued or running; that's the limit. |
| 503 | voice_model_unavailable | No model that speaks saved voices is in the model list, for a job without model |
Get a job
GET /v1/media/jobs/{id} returns a job of the key's account.
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | The job id mj_…, in the path; also X-Job-Id and job_id of the audio routes |
wait | number | Query parameter: seconds to hold the request until the job settles, 0 or more; 85 s at most from the request's arrival |
Request
The example queues a Kokoro job without waiting, then reads the job with wait=30.
curl
JOB=$(curl -s https://api.albastov.com/v1/media/jobs \
-H "Authorization: Bearer $ALBASTAR_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 $ALBASTAR_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['ALBASTAR_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.ALBASTAR_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
| Status | Code | Meaning |
|---|---|---|
| 400 | bad-param | wait is not a number, or is below 0 |
| 404 | not-found | No 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 the speech and
the transcription kind, the models are in the order of their ids.
Parameters
| Name | Type | Description |
|---|---|---|
kind | string | Query parameter: speech, transcription or image; 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 $ALBASTAR_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['ALBASTAR_API_KEY']}",
},
)
with urllib.request.urlopen(req, timeout=30) as res:
for m in json.load(res)["models"]:
print(m["id"], m["available"], sorted(m["params"]))JavaScript
const res = await fetch(
"https://api.albastov.com/v1/media/models?kind=speech",
{ headers: { Authorization: `Bearer ${process.env.ALBASTAR_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.available, Object.keys(m.params));
}Response
{
"models": [
{
"id": "kokoro",
"kind": "speech",
"label": "Kokoro 82M",
"family": "kokoro",
"notes": "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
}
]
}The response above is shortened to one model, three of its 54 voices and four of its five parameters.
| Field | Value |
|---|---|
id, kind, label, family | The model id, its kind, its display name and its model family |
notes | One line on what the model does |
params | The parameter schema: per name, type (string, enum, float, int, bool, seed, audio, image, loras), and required, default, min, max, step, values, max_length, description where set. An audio parameter takes a voice reference, an image parameter an image reference |
available | true when a job for the model starts without waiting for the model service |
queueable | true when the model accepts jobs; a job waits in the queue while available is false |
loras, modes | Image models: the names params.loras accepts, and the modes the model runs (txt2img, img2img, lora) |
The response carries more fields than the table above lists; code that reads the list can ignore the other fields.