Speech to text

Upload a recording and get its transcript back as JSON or plain text, through the OpenAI-compatible audio endpoint.

The current-service quickstart below uses sophea-asr; the separate K1 language guide covers sophea-asr-k1. Send a file to POST https://platform.sophea.ai/api/v1/audio/transcriptions and receive the completed transcript as JSON or plain text.

Before you start

Follow the API quickstart to sign in and create an active Platform API key after qualifying credit has been added. Billing shows your balance and whether top-ups are available. Keep the key in SOPHEA_API_KEY on your server, not in browser code or source control. The same key can call every model this environment publishes.

Check model availability before uploading. Use a model only when its exact ID appears in GET https://platform.sophea.ai/api/v1/models with your Platform key. sophea-asr and sophea-asr-k1 are separate services; one does not establish availability of the other. Local, staging and production keys must be used with their own environment.

Send a recording to the current service

Place a recording named audio.wav in your working directory. Send the file, model and response format as multipart form data. Let curl set the multipart boundary; do not add a JSON Content-Type header.

Terminal
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@audio.wav" \
  -F "model=sophea-asr" \
  -F "response_format=json"

The JSON response contains text and may include input-audio duration under usage. For example:

JSON
{
  "text": "Example transcript.",
  "usage": { "type": "duration", "seconds": 5.0 }
}

usage.seconds is the input audio duration reported by the model, not the time taken to process it. The response does not include a model ID, language field, segment times or speaker labels.

Use the OpenAI Python SDK

Install the official OpenAI SDK in your server's Python environment, as shown in the quickstart. The base URL already includes /api/v1; do not append another /v1.

Python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://platform.sophea.ai/api/v1",
    api_key=os.environ["SOPHEA_API_KEY"],
    max_retries=0,
)

with open("audio.wav", "rb") as audio:
    result = client.audio.transcriptions.create(
        model="sophea-asr",
        file=audio,
        response_format="json",
    )

print(result.text)

Note This example disables automatic retries. If the connection is lost, the request may still have completed; resending the recording may make another billable call.

Get plain text

Set response_format=text to receive only the transcript, with Content-Type: text/plain; charset=utf-8.

Terminal
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@audio.wav" \
  -F "model=sophea-asr" \
  -F "response_format=text"

Read either response format as UTF-8. JSON is the default if you omit response_format. Plain-text responses do not include usage fields.

Request fields and language

file and model are required. Send one file and use the exact public model ID. The current-service examples above use sophea-asr.

language is optional. Send a lowercase, two-letter ISO 639-1 code such as en. The value is forwarded with the request; accepting it does not guarantee that the model supports that language.

Only json and text response formats are supported. verbose_json, srt, vtt, timestamp_granularities, prompt, temperature, stream, chunking_strategy and include are refused with 400 feature_not_supported. Other invalid response-format values return 400 invalid_request. See the API reference for the exact field names and error shape.

K1 language recipes

Four-language Sophea K1 uses the separate model sophea-asr-k1.

Warning Before using any recipe below, confirm that this exact ID appears in GET https://platform.sophea.ai/api/v1/models with your Platform key. If it is absent, K1 is not available in that environment yet. Do not substitute sophea-asr, which is the existing service.

Choose Greek, English, German or Polish explicitly as shown below.

K1 is configured for inference on rented GPUs in Iceland. This location describes K1, not the existing sophea-asr service, and does not claim that all Platform processing or storage takes place there.

Use the same Platform API key, endpoint and sophea-asr-k1 model for each language. Choose the language spoken in the recording: Greek uses el, English en, German de and Polish pl. Greek uses el, not gr. This selects transcription in that language, not translation. Always include the language for these recipes; do not rely on automatic language detection or an omitted-language default.

Greek (el)

Place your Greek recording in greek.wav. Use el, the ISO 639-1 language code for Greek.

Terminal
# Use only if sophea-asr-k1 is listed by GET /api/v1/models.
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@greek.wav" \
  -F "model=sophea-asr-k1" \
  -F "language=el" \
  -F "response_format=json"

English (en)

Place your English recording in english.wav and select en.

Terminal
# Use only if sophea-asr-k1 is listed by GET /api/v1/models.
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@english.wav" \
  -F "model=sophea-asr-k1" \
  -F "language=en" \
  -F "response_format=json"

German (de)

Place your German recording in german.wav and select de.

Terminal
# Use only if sophea-asr-k1 is listed by GET /api/v1/models.
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@german.wav" \
  -F "model=sophea-asr-k1" \
  -F "language=de" \
  -F "response_format=json"

Polish (pl)

Place your Polish recording in polish.wav and select pl.

Terminal
# Use only if sophea-asr-k1 is listed by GET /api/v1/models.
curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
  -H "Authorization: Bearer $SOPHEA_API_KEY" \
  -F "file=@polish.wav" \
  -F "model=sophea-asr-k1" \
  -F "language=pl" \
  -F "response_format=json"

K1 with the Python SDK

This example checks model availability before uploading a Greek recording. For English, German or Polish, change the filename and language to en, de or pl; keep model="sophea-asr-k1" and max_retries=0.

Python
# sophea-asr-k1 must be listed by GET /api/v1/models first.
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://platform.sophea.ai/api/v1",
    api_key=os.environ["SOPHEA_API_KEY"],
    max_retries=0,
)

if "sophea-asr-k1" not in {model.id for model in client.models.list()}:
    raise SystemExit("Sophea K1 is not available in this environment yet.")

with open("greek.wav", "rb") as audio:
    result = client.audio.transcriptions.create(
        model="sophea-asr-k1",
        file=audio,
        language="el",
        response_format="json",
    )

print(result.text)

For plain text with curl, change response_format=json to response_format=text; language selection stays the same. For a batch of recordings in one language, use the K1 upload loop below. Submit recordings in different languages separately with the matching code. Review the transcript before using it; these examples do not promise mixed-language detection, speaker identification or translation.

File formats and limits

Accepted file extensions are WAV, FLAC, OGG, MP3, M4A, MP4 and WebM. Keep the matching extension on the uploaded filename. A supported extension does not guarantee that every codec or a damaged recording can be decoded.

The file must be at most 25 MiB (26,214,400 bytes). Each other form field is limited to 1 KiB. The whole multipart body must be at most 25 MiB plus 64 KiB (26,279,936 bytes). Exceeding any of these limits returns 413 request_too_large.

The upload limits are byte limits, not an audio-duration limit. Platform does not inspect the audio duration or guarantee that a recording of any particular length will finish. Transcription is batch-only: the answer arrives after processing, not as a live stream.

Prepare audio locally

You can upload a supported file directly. If you need a consistent format, use FFmpeg on your own computer to create a mono, 16 kHz, 16-bit PCM WAV file. This conversion is optional; these settings are not an API requirement or an accuracy guarantee. The command selects the first audio track, omits video and copied file-level metadata, and refuses to overwrite an existing output. Run these Bash examples in a private working directory; umask 077 restricts access to newly created files on systems that honor POSIX permissions.

Terminal
umask 077
ffmpeg -nostdin -n -i "recording.m4a" \
  -map 0:a:0 -vn -map_metadata -1 -ac 1 -ar 16000 \
  -c:a pcm_s16le "audio.wav"

Replace recording.m4a with your input filename. Listen to the result to check that you selected the intended track and that speech remains audible after mixing channels. WAV can be larger than the original compressed file, so check its size before uploading.

Split and send one part at a time

Splitting can help you keep individual files below the byte limit. Prefer pauses between sentences when choosing cuts. Fixed cuts can split words, and each request loses the preceding part's context. Keep the parts in order and review the combined transcript, especially at the joins.

After conversion, create a new parts directory and split the WAV into numbered files. The 30-second setting is only an example for preparing files locally, not a supported duration limit or a promise that requests will finish. Continue to uploading only after splitting succeeds and you have checked the output files, including their sizes against the 25 MiB limit.

Terminal
umask 077
mkdir parts && ffmpeg -nostdin -n -i "audio.wav" \
  -map 0:a:0 -c:a copy -f segment -segment_time 30 \
  -reset_timestamps 1 "parts/part-%06d.wav"

The K1 loop below uploads Greek parts one at a time, only after sophea-asr-k1 appears in GET https://platform.sophea.ai/api/v1/models. For English, German or Polish parts, change language=el to language=en, language=de or language=pl. Do not mix languages in one batch. It saves each JSON response and its headers, including X-Request-Id, in a new transcripts directory. It refuses to reuse that directory and stops if a file is too large or a request fails. There are no automatic retries.

Terminal
# Use only if sophea-asr-k1 is listed by GET /api/v1/models.
umask 077
mkdir transcripts || exit 1
for audio in parts/part-*.wav; do
  [ -f "$audio" ] || { printf '%s\n' "No audio parts found." >&2; exit 1; }
  size=$(wc -c < "$audio") || exit 1
  if [ "$size" -gt 26214400 ]; then
    printf '%s\n' "File exceeds 25 MiB: $audio" >&2
    exit 1
  fi
  name=$(basename "$audio" .wav)
  curl --fail-with-body "https://platform.sophea.ai/api/v1/audio/transcriptions" \
    -H "Authorization: Bearer $SOPHEA_API_KEY" \
    -F "file=@$audio" \
    -F "model=sophea-asr-k1" \
    -F "language=el" \
    -F "response_format=json" \
    --dump-header "transcripts/$name.headers" \
    --output "transcripts/$name.json" || exit 1
done

Each submitted part is a separate API request. If the loop stops, keep the saved results and inspect the response before continuing. A lost response does not prove that transcription or charging failed. Do not delete the results and rerun the whole batch; after checking, explicitly select only the parts you intend to submit. Keep audio and transcripts in your own protected storage.

Usage and cost

Speech to text is priced per second of input audio, not per token. The request's cost is deducted from your organisation's prepaid EUR balance. Usage shows imported cost and audio duration when reported; it also shows when usage was last imported, so a completed request may not appear immediately.

The JSON duration field is not a receipt or a promise that the balance has already updated. Use Billing to review your balance and Usage for recorded charges. The model catalogue shows K1's approved language-specific rates when it is published in this environment. Price approval alone does not make the model available; check its exact ID before uploading.

If a request fails

A 400 invalid_request means the multipart body, file or field value needs correction. A 400 feature_not_supported means an option or response format is not supported. Check your request against the API reference rather than retrying it unchanged.

A 401 invalid_api_key means the Platform key is missing, invalid or revoked. Check its status in API keys. A 402 spend_limit_reached means calls are blocked by the organisation's balance or a hold; adding credit does not remove a hold.

A 404 model_not_found means the model is not published here. Check the models page and the model ID. A 413 request_too_large means the file, a field or the multipart body exceeds the limits above.

For 429 rate_limit_exceeded, reduce concurrent requests and respect Retry-After. A 503 capacity_unavailable is a temporary service or capacity refusal. A 504 timeout means the request did not finish within the service's timeout. Keep the X-Request-Id response header when asking for help, and account for a possibly completed request before resending after a lost response.