Skip to content

Speech to text

Transcripts of audio files with POST /v1/audio/transcriptions, with word and segment timestamps.

Access: Keys with the media:read or media:generate scope, on an account with media access granted by Albastar Corp.

POST /v1/audio/transcriptions takes a multipart upload of an audio file and a model, and returns the transcript. The request shape is OpenAI's, so OpenAI SDKs work with the base URL changed. The key needs the media:generate scope. The model is parakeet, which transcribes 25 European languages: Transcription models.

Transcribe with word timestamps

response_format=verbose_json returns the transcript with timings. timestamp_granularities[] picks segment (the default), word, or both. The example downloads the reference clip of Narrator A, a synthetic voice, and transcribes the clip with word timestamps.

curl

curl -o reference.mp3 \
  https://api.albastov.com/v1/media/samples/reference-848c3d555461736a.mp3

curl https://api.albastov.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $ALBASTAR_API_KEY" \
  -F model=parakeet \
  -F [email protected] \
  -F response_format=verbose_json \
  -F "timestamp_granularities[]=word"

Python

import os

import httpx
from openai import OpenAI

CLIP = (
    "https://api.albastov.com/v1/media/samples/"
    "reference-848c3d555461736a.mp3"
)

client = OpenAI(
    base_url="https://api.albastov.com/v1",
    api_key=os.environ["ALBASTAR_API_KEY"],
)

clip = httpx.get(CLIP)
clip.raise_for_status()
t = client.audio.transcriptions.create(
    model="parakeet",
    file=("reference.mp3", clip.content),
    response_format="verbose_json",
    timestamp_granularities=["word"],
)
for w in t.words:
    print(f"{w.start:6.2f} {w.end:6.2f} {w.word}")

JavaScript

const base = "https://api.albastov.com/v1";

const clip = await fetch(
  `${base}/media/samples/reference-848c3d555461736a.mp3`,
);
if (!clip.ok) throw new Error(`${clip.status} ${await clip.text()}`);

const form = new FormData();
form.append("model", "parakeet");
form.append("file", await clip.blob(), "reference.mp3");
form.append("response_format", "verbose_json");
form.append("timestamp_granularities[]", "word");
const res = await fetch(`${base}/audio/transcriptions`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}` },
  body: form,
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
for (const w of (await res.json()).words) {
  console.log(w.start.toFixed(2), w.end.toFixed(2), w.word);
}

The response Parakeet returned for the clip:

Response
{
  "duration": 13.975,
  "language": "auto",
  "task": "transcribe",
  "text": "Okay, so here's the plan. We land just after sunset, …",
  "words": [
    {"end": 0.72, "start": 0.16, "word": "Okay,"},
    {"end": 0.96, "start": 0.72, "word": "so"},
    {"end": 1.28, "start": 0.96, "word": "here's"},
    {"end": 1.6, "start": 1.28, "word": "the"},
    {"end": 2.08, "start": 1.6, "word": "plan."}
  ]
}

The response above is shortened: text holds the whole transcript, and words lists all 41 words. With segment, segments lists each segment with start, end and text. The whole text, wrapped at 76 characters:

Transcript
Okay, so here's the plan. We land just after sunset, grab the bags, and head
straight for the harbor. If the ferry's late again, and honestly, it usually
is, we'll get dinner by the water and wait it out. Sound good.

Response formats

response_formatContent typeBody
jsonapplication/json{"text": "..."}; the default
texttext/plainThe transcript and a newline
verbose_jsonapplication/jsontask, language, duration, text, and segments, words or both

The file is WAV, MP3, M4A, Ogg, WebM or FLAC, 20 MB at most.

Language

language takes auto (the default) or one of Parakeet's 25 language codes, such as en or de; another value fails with 400 bad-param. A code other than auto goes to the model with the audio as a hint; Parakeet detects the spoken language itself. language in verbose_json is the language the model reports, else the language sent: for the reference clip, sent without language, the value was auto. The codes: GET /v1/media/models?kind=transcription.

OpenAI model name

whisper-1 runs as parakeet, so code written for OpenAI transcribes without changes in Parakeet's 25 European languages. Audio in another language, such as Japanese or Arabic, is outside those 25 languages. prompt and temperature are accepted and ignored; stream takes false only, because the transcript arrives whole.

Transcription jobs

POST /v1/media/jobs with kind transcription transcribes a file already on the account, or an https:// URL, without a second upload. params.audio takes an upload up_…, a job result job:<id>/<i>, a saved voice vo_… or an https:// URL; params.timestamps true adds word timings. The result entry carries the whole transcript in text. The example uploads the reference clip, then transcribes the upload as a job.

Shell
curl -o reference.mp3 \
  https://api.albastov.com/v1/media/samples/reference-848c3d555461736a.mp3

UPLOAD=$(curl -s https://api.albastov.com/v1/media/uploads \
  -H "Authorization: Bearer $ALBASTAR_API_KEY" \
  -H "Content-Type: audio/mpeg" \
  --data-binary @reference.mp3 \
  | python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])')

curl -s "https://api.albastov.com/v1/media/jobs?wait=30" \
  -H "Authorization: Bearer $ALBASTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"kind\": \"transcription\", \"model\": \"parakeet\",
       \"params\": {\"audio\": \"$UPLOAD\"}}" \
  | python3 -c '
import json, sys
job = json.load(sys.stdin)
print(job["state"], job["results"][0]["text"])'
Output
done Okay, so here's the plan. We land just after sunset, grab the bags, …

The output above is shortened; the whole transcript is the text of the response above. A transcription waits in the queue up to its queue_timeout_s, 60 s. On POST /v1/audio/transcriptions, a job not done in time answers 503 with code queued and job_id, as on the speech route: Waiting for a result.

Reference