Skip to content

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

FieldTypeValue
idstringmj_ and 22 random characters
kindstringspeech, transcription or image
modelstringThe model id, such as kokoro or zimage-turbo
labelstringThe 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
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
queue_timeout_sinteger or nullSeconds the job may wait in the queue; null: as long as it takes
trace_idstringThe 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:

reasonnote
offlineThe model service is offline. The job starts when the service is back.
busyThe 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.

KindQueue timeout
speech300 s
transcription60 s
imageNone: 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.

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, transcription or image; must match the model's kind
prioritystringinteractive 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
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 of the same key with the same idempotency_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 or more; 85 s at most from the request's arrival
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. Used by breeze2, and by omnivoice when voice is not set
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
languagestringauto (default) or one of Parakeet's 25 language codes; Parakeet detects the spoken language itself and does not use the code
timestampsbooleantrue adds words with start and end times to the transcript; default false
params of an image jobTypeDescription
prompt requiredstringWhat the image shows; 4,000 characters at most
negative_promptstringWhat the image leaves out, 2,000 characters at most; zimage-turbo
aspectstring1.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, heightintegerThe exact size in pixels, 512 to 2048 each; set both or neither
countintegerImages per job, 1 (default) to 8; image i uses seed + i
seedinteger0 to 9007199254740991; without seed, a random seed, returned in params and on each result
stepsinteger1 to 50; default 8 on zimage-turbo, 24 on flux-dev
init_imagestringAn 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
strengthnumber0.05 to 1.0, default 0.65: how far the result departs from init_image
lorasarrayUp to 4 {"name", "weight"} items, weight -2.0 to 2.0; names from the model's loras list in GET /v1/media/models
formatstringpng (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.

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": "…/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

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-mismatchThis idempotency key was used with a different 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
422prompt-refusedThe account's prompts are reviewed, and the image prompt was refused: That prompt isn't allowed here.
429budget-exhaustedThe 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
429too-many-openThe key has 1,000 jobs queued or running, the limit per key: You have 1000 jobs queued or running; that's the limit.
503voice_model_unavailableNo 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

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 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

StatusCodeMeaning
400bad-paramwait is not a number, or is below 0
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 the speech and the transcription kind, the models are in the order of their ids.

Parameters

NameTypeDescription
kindstringQuery 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

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.

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, 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
availabletrue when a job for the model starts without waiting for the model service
queueabletrue when the model accepts jobs; a job waits in the queue while available is false
loras, modesImage 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.