Docs

Build with Inferentium

An OpenAI-compatible speech-to-text API. Keep your client, change one base_url. Every endpoint is documented below with the request it takes and the response it returns.

Quickstart

1) Get an API key from the console. 2) Point your SDK at https://api.inferentium.ai/v1. 3) Transcribe.

Python
pip install openai

from openai import OpenAI
client = OpenAI(base_url="https://api.inferentium.ai/v1", api_key="tapi_...")

tx = client.audio.transcriptions.create(
    model="inferentium-stt-1",
    file=open("call.mp3", "rb"),
)
print(tx.text)
curl
curl https://api.inferentium.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer tapi_..." \
  -F model=inferentium-stt-1 \
  -F file=@call.mp3

# → { "text": "Olá, boa noite. Sejam bem-vindos…", "usage": { "type": "duration", "seconds": 5833 } }

Authentication

Keys are prefixed tapi_ and scoped to your account's prepaid credits. Send one of:

HeaderWho uses it
Authorization: Bearer tapi_…OpenAI SDKs and most HTTP clients
authorization: tapi_…AssemblyAI SDKs (bare, no scheme)
X-API-Key: tapi_…Anything else
X-Upload-Token: <token>Browsers and mobile apps, with a 5-minute token from POST /api/v1/upload-tokens
?apikey=tapi_… / ?uploadToken=<token>WebSockets only, where headers cannot be set

Every response carries x-request-id, X-RateLimit-Limit and X-RateLimit-Remaining. The default limit is 1,000 requests per hour per key.

OpenAI compatibility

We implement the standard /v1/audio/transcriptions shape, including response_format (json, text, verbose_json, srt, vtt) and timestamp_granularities[]. Set the base URL and key; everything else stays the same. Works with the official SDKs, LangChain, LlamaIndex, and anything that lets you override OPENAI_BASE_URL. Errors use the OpenAI envelope, so existing error handling keeps working.

JavaScript / TypeScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.inferentium.ai/v1", apiKey: "tapi_..." });

const tx = await client.audio.transcriptions.create({
  model: "inferentium-stt-1",
  file: fs.createReadStream("call.mp3"),
  response_format: "verbose_json",
});
console.log(tx.text, tx.duration);

Which endpoint?

You haveUsePrice
A file, any length, and an OpenAI clientPOST /v1/audio/transcriptionsUS$0.05 / h
A file and you want speakers, confidence and the charge in the responsePOST /api/v1/stt/transcribeUS$0.05 / h (+0.02 speakers)
A long file and you want text on screen before it endsPOST /api/v1/stt/transcribe/streamUS$0.05 / h
Hours of audio or high volume; a webhook when donePOST /api/v1/stt/jobsUS$0.05 / h
A clip under 2 minutes: dictation, a voice note, a commandPOST /v2/transcribeUS$0.12 / h
Code written for AssemblyAIPOST /v2/upload → /v2/transcriptUS$0.05 / h (+0.02 speakers)
A microphone or a live callWS /ws/realtimeUS$0.12 / h (+0.12 speakers)

Errors

Authentication, permission and rate-limit errors always use the OpenAI envelope, on every route. The OpenAI-compatible routes use it for everything. The native routes (/api/v1/…) and the AssemblyAI-compatible routes (/v2/…) answer their own errors as { "error": "message" }, listed per endpoint below. Each response carries x-request-id — quote it if you contact us.

Envelope
{ "error": { "message": "…", "type": "…", "param": null, "code": "…" } }
HTTPcodeWhen
401invalid_api_keyMissing, unknown or revoked key
402insufficient_creditsBalance cannot cover the estimate — top up; nothing charged
403insufficient_permissionsKey lacks stt:write, or a browser token used outside transcription
400invalid_value / missing_fileBad parameter
413—Body over the route's limit (no envelope — server default)
429rate_limit_exceededPer-key hourly limit; honour Retry-After
501unsupported_endpointAudio translations
502inference_failedBackend failed or audio undecodable — not charged
503service_not_enabledA preview product that is switched off on this deployment
Examples
// 401
{"error":{"message":"Missing or invalid API key. Pass it as `Authorization: Bearer <key>` or `X-API-Key: <key>`.","type":"authentication_error","param":null,"code":"invalid_api_key"}}
// 402
{"error":{"message":"Insufficient credits for this request. Top up your balance and retry.","type":"insufficient_quota","param":null,"code":"insufficient_credits"}}
// 403
{"error":{"message":"This API key does not have permission for this operation.","type":"permission_error","param":null,"code":"insufficient_permissions"}}
// 429  (+ Retry-After: 1234)
{"error":{"message":"Rate limit exceeded for this API key.","type":"rate_limit_error","param":null,"code":"rate_limit_exceeded"}}
// 502
{"error":{"message":"The transcription backend failed to process this request. You were not charged.","type":"server_error","param":null,"code":"inference_failed"}}

API reference

Base URL https://api.inferentium.ai. Every endpoint below shows the request it takes and the response it returns, with the property names exactly as they go over the wire. Byte limits and prices are enforced before any GPU time, so a refused request is never charged.

Pre-recorded — OpenAI-compatible

The standard /v1/audio/transcriptions shape. Point any OpenAI SDK at the base URL and keep your code. No duration limit; billed at US$0.05 per hour of audio.

POST/v1/audio/transcriptions

Transcribe a file

Upload audio as multipart/form-data and get the transcript in the response. Up to 500 MB per request; any container the decoder reads (wav, flac, mp3, ogg/opus, m4a, webm).

Auth: API key or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
filerequiredform fieldfile—The audio. Must not be empty.
modelform fieldstringinferentium-stt-1Accepted for SDK compatibility. Every value runs the same model; the ids listed by GET /v1/models are inferentium-stt-1 and whisper-1.
languageform fieldstringenISO-639-1 hint, echoed back as language in verbose_json. The model detects the spoken language itself.
response_formatform fieldstringjsonjson, text, verbose_json, srt or vtt.
timestamp_granularities[]form fieldstring[]segmentsegment and/or word (repeat the field or comma-separate). Only affects verbose_json: word adds words[], segment adds segments[].
prompt, temperatureform fieldstring, number—Accepted and ignored.
streamform field——Not supported on this route. Use POST /api/v1/stt/transcribe/stream for server-sent partials.
curl
curl https://api.inferentium.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer tapi_..." \
  -F model=inferentium-stt-1 \
  -F language=pt \
  -F response_format=verbose_json \
  -F "timestamp_granularities[]=word" \
  -F file=@call.mp3
Python (openai SDK)
from openai import OpenAI
client = OpenAI(base_url="https://api.inferentium.ai/v1", api_key="tapi_...")

tx = client.audio.transcriptions.create(
    model="inferentium-stt-1",
    file=open("call.mp3", "rb"),
    language="pt",
)
print(tx.text)

Response

200 · response_format=json (default)
{ "text": "Olá, tudo bem? Sim, obrigado.", "usage": { "type": "duration", "seconds": 8 } }
200 · response_format=verbose_json
{
  "task": "transcribe",
  "language": "pt",
  "duration": 7.43,
  "text": "Olá, tudo bem? Sim, obrigado.",
  "words": [
    { "word": "Olá,", "start": 0.12, "end": 0.44 },
    { "word": "tudo", "start": 0.46, "end": 0.69 }
  ],
  "segments": [
    { "id": 0, "start": 0.12, "end": 1.35, "text": "Olá, tudo bem?" },
    { "id": 1, "start": 2.10, "end": 3.40, "text": "Sim, obrigado." }
  ],
  "usage": { "type": "duration", "seconds": 8 }
}
200 · response_format=text (text/plain)
Olá, tudo bem? Sim, obrigado.
200 · response_format=srt (text/plain; vtt is the same cues with a WEBVTT header)
1
00:00:00,120 --> 00:00:01,350
Olá, tudo bem?

2
00:00:02,100 --> 00:00:03,400
Sim, obrigado.

Errors

StatusCode / bodyWhen
400missing_filefile missing or empty
400invalid_valueresponse_format not one of json, text, verbose_json, srt, vtt
402insufficient_creditsbalance does not cover the estimate; nothing charged
413—body over 500 MB (no JSON body)
502inference_failedbackend failed or the audio could not be decoded; nothing charged
  • Response headers: x-request-id (quote it when you contact us), openai-processing-ms, X-RateLimit-Limit, X-RateLimit-Remaining.
  • duration and usage.seconds come from the decoded audio, not the file size; usage.seconds is rounded up. The charge is not echoed on this route — read it from GET /api/v1/account/usage or use the native route.
  • words[] is null (not absent) when word was not requested. segments[] are sentences cut at sentence-final punctuation, at pauses over 1.2 s and at 320 characters.
  • Speaker labels are not on this route: use POST /api/v1/stt/transcribe with diarize=true.
GET/v1/models

List models

The OpenAI list shape, so SDK model pickers work. Both ids run the same model.

curl
curl https://api.inferentium.ai/v1/models -H "Authorization: Bearer tapi_..."

Response

200
{
  "object": "list",
  "data": [
    { "id": "inferentium-stt-1", "object": "model", "created": 1757462400, "owned_by": "inferentium" },
    { "id": "whisper-1",         "object": "model", "created": 1757462400, "owned_by": "inferentium" }
  ]
}
GET/v1/models/{id}

Get a model

One entry of the list above. Matched case-insensitively.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—inferentium-stt-1 or whisper-1.
curl
curl https://api.inferentium.ai/v1/models/inferentium-stt-1 -H "Authorization: Bearer tapi_..."

Response

200
{ "id": "inferentium-stt-1", "object": "model", "created": 1757462400, "owned_by": "inferentium" }
404
{ "error": { "message": "The model 'gpt-4o' does not exist.", "type": "invalid_request_error", "param": "model", "code": "model_not_found" } }
POST/v1/audio/translations

Translations (not supported)

Answers 501 so an SDK gets a clear error instead of a transcript in the wrong language.

Response

501
{ "error": { "message": "Audio translation is not supported. Use /v1/audio/transcriptions with `language`.", "type": "invalid_request_error", "param": null, "code": "unsupported_endpoint" } }

Pre-recorded — native

Our own shape: word timestamps and confidence always on, speaker labels as an add-on, the charge in the response, and a streaming variant that sends partial transcripts while the file is processed. Same price as the OpenAI route.

POST/api/v1/stt/transcribe

Transcribe a file (native)

Multipart upload, transcript in the response, with words, segments, speakers and the exact charge per product line.

Auth: API key or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
filerequiredform fieldfile—The audio, up to 500 MB.
languageform fieldstringenISO-639-1 hint, echoed back. The model detects the spoken language itself.
diarizeform fieldbooleanfalseSpeaker labels (up to 4 voices). Adds segments[], speakers[] and words[].speaker, and a second billing line stt-diarization at US$0.02 per hour.
featuresform fieldJSON string—Feature object, e.g. {"diarization":{"max_speakers":2}}. Unknown keys are rejected with 400 unknown_feature. On the public API only diarization is enabled today; the other keys (profanity, pii_text, sentiment, summary, …) answer 400 feature_not_enabled until the understanding service ships.
curl
curl https://api.inferentium.ai/api/v1/stt/transcribe \
  -H "Authorization: Bearer tapi_..." \
  -F language=pt \
  -F diarize=true \
  -F file=@call.mp3

Response

200
{
  "requestId": "req_9f1c2d3e4b5a6f708192a3b4c5d6e7f8",
  "transcription": {
    "text": "Olá, tudo bem? Sim, obrigado.",
    "language": "pt",
    "language_confidence": null,
    "duration_s": 7.43,
    "words": [
      { "text": "Olá,", "start": 0.12, "end": 0.44, "confidence": 0.9812, "speaker": "A", "channel": null },
      { "text": "tudo", "start": 0.46, "end": 0.69, "confidence": 0.9790, "speaker": "A", "channel": null }
    ],
    "segments": [
      { "text": "Olá, tudo bem?", "start": 0.12, "end": 1.35, "speaker": "A", "confidence": 0.9731, "words": [ … ] },
      { "text": "Sim, obrigado.", "start": 2.10, "end": 3.40, "speaker": "B", "confidence": 0.9650, "words": [ … ] }
    ],
    "speakers": ["A", "B"],
    "understanding": null
  },
  "metrics": { "processingTimeMs": 812, "audioDurationS": 7.43, "realTimeFactor": 0.109, "queueMs": 12, "computeMs": 790, "batchSize": 2, "diarized": true },
  "creditsChargedMicro": 14452,
  "charged": [
    { "product": "stt",             "creditsMicro": 10321 },
    { "product": "stt-diarization", "creditsMicro": 4131 }
  ],
  "failedFeatures": null,
  "balanceMicroCredits": 99985548
}

Errors

StatusCode / bodyWhen
400{"error":"file is required"}file missing or empty
400unknown_feature / invalid_featuresfeatures is not valid JSON or names a feature that does not exist; the message lists the known ones
400{"error":"<message>"}a feature is not available or not enabled on this deployment
402insufficient_creditsbody also carries balanceMicroCredits and maxAffordableSeconds; nothing charged
502inference_failedbackend failed or the audio could not be decoded; body carries requestId; nothing charged
  • segments[] and speakers[] are null without diarize. words[].speaker is a letter by order of first appearance (A, B, C, D).
  • failedFeatures lists an add-on that could not run, keyed by feature name with the reason. Its line is released and never charged, and the transcript still arrives.
  • Errors on this route use {"error": …} rather than the OpenAI envelope; 401, 403 and 429 from the auth layer always use the envelope.
POST/api/v1/stt/transcribe/stream

Transcribe a file with partials (SSE)

Same upload, but the response is a text/event-stream: one partial per chunk as the file is processed, then a done event with the full result and the charge. Useful for long files where you want text on screen before the end.

Auth: API key or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
filerequiredform fieldfile—The audio, up to 500 MB.
languageform fieldstringenISO-639-1 hint.
curl
curl -N https://api.inferentium.ai/api/v1/stt/transcribe/stream \
  -H "Authorization: Bearer tapi_..." \
  -F language=pt \
  -F file=@podcast.mp3

Response

200 · text/event-stream
event: start
data: {"audioDurationS":930.42}

event: partial
data: {"index":0,"startS":0.0,"endS":28.5,"text":"Olá, tudo bem?","words":[{"text":"Olá,","start":0.12,"end":0.44,"confidence":0.9812}]}

event: partial
data: {"index":1,"startS":28.5,"endS":57.0,"text":"…","words":[…]}

event: done
data: {"requestId":"req_9f1c…","creditsChargedMicro":1292353,"balanceMicroCredits":98707647,
       "result":{"language":"pt","diarized":false,
                 "transcription":{"text":"…","words":[…]},
                 "metrics":{"processingTimeMs":8120,"audioDurationS":930.42,"realTimeFactor":0.0087,"modelName":"inferentium-stt-1"}}}
error event (stream ends after it)
event: error
data: {"error":"inference_failed"}

Errors

StatusCode / bodyWhen
400{"error":"file is required"}before the stream starts
402insufficient_creditsbefore the stream starts, with balanceMicroCredits; or after decoding, with audioDurationS and maxAffordableSeconds
  • Speaker labels and features are not available on the streaming route.
  • If the connection drops before done, the reservation is released and nothing is charged.

Async jobs & webhooks

For hours of audio or high volume: submit, get a job id back at once, then poll or receive a signed webhook. The transcript is stored so it can be fetched later. Same price as pre-recorded.

POST/api/v1/stt/jobs

Submit a job

Multipart upload. Credits are reserved from the file size at submit and settled on the real duration when the job completes.

Auth: API key with stt:write (upload tokens cannot submit jobs)

Request

ParameterWhereTypeDefaultDescription
filerequiredform fieldfile—The audio, up to 500 MB.
languageform fieldstringenISO-639-1 hint.
diarizeform fieldbooleanfalseSpeaker labels, billed as a second line.
featuresform fieldJSON string—Same object as the native route. Validated before anything is reserved.
webhookUrlform fieldstring—https URL on a public host. We POST there when the job completes or fails.
curl
curl https://api.inferentium.ai/api/v1/stt/jobs \
  -H "Authorization: Bearer tapi_..." \
  -F language=pt \
  -F file=@podcast.mp3 \
  -F webhookUrl="https://your.app/hooks/inferentium"

Response

202 Accepted
{ "jobId": "3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8", "status": "queued", "webhookSecret": "7c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f" }
Webhook we send · POST webhookUrl
Content-Type: application/json; charset=utf-8
X-Signature: sha256=<hex HMAC-SHA256 of the body, keyed with webhookSecret>

{ "jobId": "3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8", "status": "completed", "completedAt": "2026-10-27T14:02:11.482Z" }

Errors

StatusCode / bodyWhen
400{"error":"file is required"}file missing or empty
400invalid_webhook_urlnot https, not absolute, has credentials, or points at a private host; message says which
400unknown_feature / feature_not_enabled / invalid_feature_optionsfeatures rejected; message names the key
402insufficient_creditswith balanceMicroCredits; nothing reserved
  • webhookSecret is null when no webhookUrl was given. Verify X-Signature with it before trusting a webhook; status is completed or failed.
  • Delivery is retried up to 6 times with growing delays (0, 1, 3, 7, 15, 31 minutes) until your endpoint answers 2xx.
  • Failed jobs are released and never charged.
GET/api/v1/stt/jobs/{id}

Job status

Poll this until status is completed or failed.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathuuid—The jobId.
curl
curl https://api.inferentium.ai/api/v1/stt/jobs/3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8 -H "Authorization: Bearer tapi_..."

Response

200
{
  "jobId": "3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8",
  "status": "processing",
  "createdAt": "2026-10-27T13:58:02.114Z",
  "startedAt": "2026-10-27T13:58:03.002Z",
  "completedAt": null,
  "error": null
}

Errors

StatusCode / bodyWhen
404—unknown id, or a job of another account (empty body)
  • status is one of queued, processing, completed, failed, cancelled. error carries the reason on failure, e.g. an undecodable file.
GET/api/v1/stt/jobs/{id}/result

Job result

The stored transcript of a completed job.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathuuid—The jobId.
curl
curl https://api.inferentium.ai/api/v1/stt/jobs/3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8/result -H "Authorization: Bearer tapi_..."

Response

200
{
  "jobId": "3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8",
  "result": {
    "Output": {
      "text": "Olá, tudo bem? Sim, obrigado.",
      "language": "pt",
      "language_confidence": null,
      "duration_s": 7.43,
      "words": [ { "text": "Olá,", "start": 0.12, "end": 0.44, "confidence": 0.9812, "speaker": null, "channel": null } ],
      "segments": null,
      "speakers": null,
      "understanding": null
    },
    "Metrics": { "processingTimeMs": 812, "audioDurationS": 7.43, "realTimeFactor": 0.109 },
    "FailedFeatures": null
  }
}
409 · not finished yet
{ "jobId": "3f2a1b0c-9d8e-4f70-8192-a3b4c5d6e7f8", "status": "processing" }

Errors

StatusCode / bodyWhen
404—unknown id, or a job of another account (empty body)
409—status is still queued or processing, or the job failed
  • The three keys inside result are capitalised (Output, Metrics, FailedFeatures); the transcript inside Output has the same snake_case fields as the native route.
  • Results are kept until you delete the job or we add a retention window; do not rely on them as your only copy.

Sync — short audio, answer in the same response

A separate product for dictation, voice commands and voice notes: one POST, the transcript in the response, no upload step and no job to track. At most 2 minutes of audio and 40 MB per request. The shape is AssemblyAI's Sync API, so their SDKs work unchanged. US$0.12 per hour of audio.

POST/v2/transcribe

Transcribe a short clip

Multipart audio in, transcript with word timestamps out. POST /transcribe at the root is the same endpoint for callers that only changed the host.

Auth: API key (bare authorization header, Bearer, or X-API-Key) or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
audiorequiredform fieldfile—The clip (file is accepted as an alias). Any container; Opus or mp3 halves the upload time versus WAV.
language_codeform fieldstringenHint; language is accepted as an alias. AssemblyAI codes like en_us are reduced to the ISO-639-1 code.
X-AAI-Modelheaderstring—Or the speech_model field. Echoed back as speech_model when it is one of AssemblyAI's model names; every request runs the same model.
speaker_labelsform field——Not available on Sync. Accepted and ignored; words[].speaker is always null here.
curl
curl https://api.inferentium.ai/v2/transcribe \
  -H "authorization: tapi_..." \
  -F audio=@note.m4a \
  -F language_code=pt
Browser (with a short-lived upload token)
const body = new FormData();
body.append("audio", blob, "clip.opus");
body.append("language_code", "pt");
const r = await fetch("https://api.inferentium.ai/v2/transcribe", {
  method: "POST",
  headers: { "X-Upload-Token": token },   // from POST /api/v1/upload-tokens, minted on your server
  body,
});
console.log((await r.json()).text);

Response

200
{
  "id": "req_4f1c9ab2d7e4446a9b0d8e1b2c3d4e5f",
  "status": "completed",
  "text": "Lembra de comprar o leite.",
  "words": [
    { "text": "Lembra",  "start": 120, "end": 410,  "confidence": 0.98, "speaker": null },
    { "text": "de",      "start": 430, "end": 520,  "confidence": 0.99, "speaker": null },
    { "text": "comprar", "start": 540, "end": 900,  "confidence": 0.97, "speaker": null },
    { "text": "o",       "start": 910, "end": 960,  "confidence": 0.99, "speaker": null },
    { "text": "leite.",  "start": 980, "end": 1340, "confidence": 0.96, "speaker": null }
  ],
  "confidence": 0.98,
  "audio_duration": 2,
  "language_code": "pt",
  "speech_model": null,
  "speech_models": ["inferentium-stt-1"],
  "summary": null,
  "sentiment_analysis_results": null,
  "entities": null,
  "chapters": null,
  "auto_highlights_result": null,
  "iab_categories_result": null,
  "content_safety_labels": null,
  "failed_features": null,
  "metrics": { "processingTimeMs": 139, "audioDurationS": 1.84, "realTimeFactor": 0.076, "queueMs": 0, "computeMs": 137, "batchSize": 1 },
  "credits_charged_micro": 6133,
  "balance_micro_credits": 9857326381
}

Errors

StatusCode / bodyWhen
400{"error":"…"}not multipart, or no audio part
400audio too longover 120 s: "This endpoint accepts up to 120s of audio and this file is 184.3s. You were not charged. Use POST /v2/transcript for longer audio."
413—body over 40 MB; nothing charged
402—balance does not cover the clip; nothing charged
502—backend failed or audio undecodable; nothing charged
  • words[].start and end are milliseconds; audio_duration is whole seconds. Billing uses the exact duration.
  • metrics, credits_charged_micro and balance_micro_credits are ours; an AssemblyAI SDK ignores them.
  • Response headers: x-request-id and x-processing-ms.

AssemblyAI-compatible — pre-recorded

AssemblyAI's upload → create → poll flow, with the same transcript object, list, delete, derived views (sentences, paragraphs, SRT, VTT, word search) and {transcript_id, status} webhooks. Already on AssemblyAI? Change base_url and the key; keep the SDK. Priced as pre-recorded: US$0.05 per hour, speaker labels +US$0.02.

POST/v2/upload

Upload a file

Raw body in, a signed upload_url out. Nothing is ever fetched from that URL: it is a pointer to the stored file, valid 24 hours, usable by your account for one transcript.

Auth: API key with stt:write

Request

ParameterWhereTypeDefaultDescription
(body)requiredbodybytes—The audio as the request body (application/octet-stream; chunked is fine). Up to 500 MB.
curl
curl https://api.inferentium.ai/v2/upload \
  -H "authorization: tapi_..." \
  --data-binary @call.mp3

Response

200
{ "upload_url": "https://api.inferentium.ai/v2/upload/eyJ0eXAiOiJ1cGxvYWRfcmVmIi…" }

Errors

StatusCode / bodyWhen
413—over 500 MB
422—empty body
POST/v2/transcript

Create a transcript

JSON body with an audio_url. Returns the transcript object with status queued at once; the job starts immediately.

Auth: API key with stt:write

Request

ParameterWhereTypeDefaultDescription
audio_urlrequiredJSON bodystring—An upload_url from POST /v2/upload, or any public https URL (fetched by us; up to 5 redirects, 500 MB, 30 minutes).
language_codeJSON bodystringenHint, echoed back as sent. AssemblyAI codes (en_us, es_419) are reduced to ISO-639-1.
language_detectionJSON bodybooleanfalseReported true when set or when no language_code was given. The model detects the language either way.
speaker_labelsJSON bodybooleanfalseSpeaker labels, up to 4 voices (A–D). Fills utterances[] and words[].speaker. Billed as stt-diarization, US$0.02 per hour.
speech_modelJSON bodystring—Echoed back when it is one of AssemblyAI's names; otherwise null. The model that ran is always speech_models.
webhook_urlJSON bodystring—https URL on a public host.
webhook_auth_header_name / webhook_auth_header_valueJSON bodystring—A header we add to the webhook call. Requires webhook_url.
filter_profanity, redact_pii, content_safety, sentiment_analysis, entity_detection, auto_highlights, iab_categories, summarization, auto_chaptersJSON bodyboolean—Understanding add-ons. Not enabled on the public API yet: a request that sets one answers 400 naming it. Leave them unset.
speakers_expected, punctuate, format_text, multichannel, word_boost, audio_start_from, audio_end_at, …JSON body——Accepted and ignored, so SDK defaults do not break.
curl
curl https://api.inferentium.ai/v2/transcript \
  -H "authorization: tapi_..." \
  -H "content-type: application/json" \
  -d '{ "audio_url": "https://api.inferentium.ai/v2/upload/eyJ0eXAi…", "language_code": "pt", "speaker_labels": true,
        "webhook_url": "https://hooks.example.com/aai",
        "webhook_auth_header_name": "X-Hook-Secret", "webhook_auth_header_value": "s3cr3t" }'
Python (assemblyai SDK)
import assemblyai as aai
aai.settings.base_url = "https://api.inferentium.ai"
aai.settings.api_key = "tapi_..."
tx = aai.Transcriber().transcribe("call.mp3", config=aai.TranscriptionConfig(language_code="pt", speaker_labels=True))
print(tx.status, tx.text)

Response

200 · queued
{
  "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
  "status": "queued",
  "audio_url": "https://api.inferentium.ai/v2/upload/eyJ0eXAi…",
  "text": null, "words": null, "utterances": null, "confidence": null, "audio_duration": null,
  "language_code": "pt", "language_detection": false,
  "punctuate": true, "format_text": true, "speaker_labels": true,
  "speech_model": null, "speech_models": ["inferentium-stt-1"],
  "webhook_url": "https://hooks.example.com/aai", "webhook_auth": true,
  "webhook_auth_header_name": "X-Hook-Secret", "webhook_status_code": null,
  "summary": null, "sentiment_analysis_results": null, "entities": null, "chapters": null,
  "auto_highlights_result": null, "iab_categories_result": null, "content_safety_labels": null,
  "failed_features": null,
  "error": null,
  "created": "2026-10-27T12:00:00Z", "completed": null
}
Webhook we send · POST webhook_url
content-type: application/json; charset=utf-8
X-Hook-Secret: s3cr3t            ← your webhook_auth_header_name / value

{ "transcript_id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33", "status": "completed" }

Errors

StatusCode / bodyWhen
400{"error":"audio_url is required."}missing audio_url
400{"error":"audio_url …"}not https, private host, expired upload_url, unreachable, over 500 MB, too many redirects
400{"error":"webhook_url …"}bad webhook URL or header name
403—the upload_url belongs to another account
402—balance does not cover the estimate
  • status goes queued → processing → completed | error. A failed transcript is status error with the reason in error, and is not charged.
  • Webhook delivery retries up to 6 times (0, 1, 3, 7, 15, 31 minutes) until a 2xx; the last HTTP status your endpoint answered is webhook_status_code on the transcript.
  • Every property is always present (null rather than missing), so SDK bindings never see an undefined field.
GET/v2/transcript/{id}

Get a transcript

The full object. text, words, confidence and audio_duration are filled once status is completed.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—The transcript id.
curl
curl https://api.inferentium.ai/v2/transcript/6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33 -H "authorization: tapi_..."

Response

200 · completed, with speaker_labels
{
  "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
  "status": "completed",
  "audio_url": "https://api.inferentium.ai/v2/upload/eyJ0eXAi…",
  "text": "Bom dia. Tudo bem?",
  "words": [
    { "text": "Bom",  "start": 100,  "end": 400,  "confidence": 0.98, "speaker": "A" },
    { "text": "dia.", "start": 450,  "end": 800,  "confidence": 0.96, "speaker": "A" },
    { "text": "Tudo", "start": 1500, "end": 1800, "confidence": 0.94, "speaker": "B" },
    { "text": "bem?", "start": 1850, "end": 2400, "confidence": 0.92, "speaker": "B" }
  ],
  "utterances": [
    { "speaker": "A", "text": "Bom dia.",  "start": 100,  "end": 800,  "confidence": 0.97, "words": [ … ] },
    { "speaker": "B", "text": "Tudo bem?", "start": 1500, "end": 2400, "confidence": 0.93, "words": [ … ] }
  ],
  "confidence": 0.95,
  "audio_duration": 2,
  "language_code": "pt",
  "language_detection": false,
  "punctuate": true,
  "format_text": true,
  "speaker_labels": true,
  "speech_model": null,
  "speech_models": ["inferentium-stt-1"],
  "webhook_url": "https://hooks.example.com/aai",
  "webhook_auth": true,
  "webhook_auth_header_name": "X-Hook-Secret",
  "webhook_status_code": 204,
  "summary": null,
  "sentiment_analysis_results": null,
  "entities": null,
  "chapters": null,
  "auto_highlights_result": null,
  "iab_categories_result": null,
  "content_safety_labels": null,
  "failed_features": null,
  "error": null,
  "created": "2026-10-27T12:00:00Z",
  "completed": "2026-10-27T12:00:05Z"
}

Errors

StatusCode / bodyWhen
404{"error":"Transcript lookup error, transcript id not found"}unknown id, or a transcript of another account
  • words[].start / end and utterances are milliseconds; audio_duration is whole seconds.
  • words and utterances are null (never []) when unavailable; utterances exists only when speaker_labels was set.
  • confidence is the mean word confidence, 4 decimals.
GET/v2/transcript

List transcripts

Newest first, keyset-paged.

Request

ParameterWhereTypeDefaultDescription
limitqueryinteger101 to 200.
statusquerystring—queued, processing, completed or error.
created_onquerydate—YYYY-MM-DD, UTC day.
before_idquerystring—Transcripts created before this one (older page; what prev_url carries).
after_idquerystring—Transcripts created after this one (newer page).
curl
curl "https://api.inferentium.ai/v2/transcript?limit=10&status=completed" -H "authorization: tapi_..."

Response

200
{
  "page_details": {
    "limit": 10,
    "result_count": 10,
    "current_url": "https://api.inferentium.ai/v2/transcript?limit=10&status=completed",
    "prev_url": "https://api.inferentium.ai/v2/transcript?limit=10&status=completed&before_id=1f0a…",
    "next_url": "https://api.inferentium.ai/v2/transcript?limit=10&status=completed&after_id=6b2d…"
  },
  "transcripts": [
    {
      "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
      "resource_url": "https://api.inferentium.ai/v2/transcript/6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
      "status": "completed",
      "created": "2026-10-27T12:00:00Z",
      "completed": "2026-10-27T12:00:05Z",
      "audio_url": "https://cdn.example.com/call.mp3",
      "error": null
    }
  ]
}

Errors

StatusCode / bodyWhen
400—status not one of queued, processing, completed, error
400—before_id / after_id does not name one of your transcripts
  • prev_url is null unless the page is full; next_url is null on an empty page.
DELETE/v2/transcript/{id}

Delete a transcript

Drops the stored text and the input file. The record stays, redacted, so lists and usage still line up.

Auth: API key with stt:write

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—The transcript id.
curl
curl -X DELETE https://api.inferentium.ai/v2/transcript/6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33 -H "authorization: tapi_..."

Response

200
{
  "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
  "status": "completed",
  "audio_url": "http://deleted_by_user",
  "text": null, "words": null, "utterances": null, "confidence": null, "audio_duration": null,
  "language_code": null, "language_detection": false, "punctuate": true, "format_text": true, "speaker_labels": true,
  "speech_model": null, "speech_models": ["inferentium-stt-1"],
  "webhook_url": null, "webhook_auth": false, "webhook_auth_header_name": null, "webhook_status_code": null,
  "summary": null, "sentiment_analysis_results": null, "entities": null, "chapters": null,
  "auto_highlights_result": null, "iab_categories_result": null, "content_safety_labels": null, "failed_features": null,
  "error": null, "created": "2026-10-27T12:00:00Z", "completed": "2026-10-27T12:00:05Z"
}

Errors

StatusCode / bodyWhen
400—still queued or processing
404—unknown id
GET/v2/transcript/{id}/sentences

Sentences

The transcript split into sentences: at sentence-final punctuation, at pauses over 1.2 s, at a speaker change, or at 320 characters. Free.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—A completed transcript.
curl
curl https://api.inferentium.ai/v2/transcript/6b2d…/sentences -H "authorization: tapi_..."

Response

200
{
  "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
  "confidence": 0.95,
  "audio_duration": 2,
  "sentences": [
    { "speaker": "A", "text": "Bom dia.",  "start": 100,  "end": 800,  "confidence": 0.97,
      "words": [ { "text": "Bom", "start": 100, "end": 400, "confidence": 0.98, "speaker": "A" }, { "text": "dia.", "start": 450, "end": 800, "confidence": 0.96, "speaker": "A" } ] },
    { "speaker": "B", "text": "Tudo bem?", "start": 1500, "end": 2400, "confidence": 0.93, "words": [ … ] }
  ]
}

Errors

StatusCode / bodyWhen
400—transcript still processing, failed, deleted, or made before word timestamps shipped
404—unknown id
GET/v2/transcript/{id}/paragraphs

Paragraphs

Same shape with a paragraphs[] key: sentences grouped until a 2 s silence, a speaker change, or about 700 characters. Free.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—A completed transcript.
curl
curl https://api.inferentium.ai/v2/transcript/6b2d…/paragraphs -H "authorization: tapi_..."

Response

200
{
  "id": "6b2d4c19-3a7e-4f58-9f0b-2c4d7e8a1b33",
  "confidence": 0.95,
  "audio_duration": 2,
  "paragraphs": [
    { "speaker": "A", "text": "Bom dia.",  "start": 100,  "end": 800,  "confidence": 0.97, "words": [ … ] },
    { "speaker": "B", "text": "Tudo bem?", "start": 1500, "end": 2400, "confidence": 0.93, "words": [ … ] }
  ]
}

Errors

StatusCode / bodyWhen
400 / 404—as for sentences
GET/v2/transcript/{id}/srt

SRT captions

text/plain. Cues break at sentence ends, at gaps over 0.8 s, at a speaker change, at 7 s on screen, and at chars_per_caption. Free.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—A completed transcript.
chars_per_captionqueryinteger32Maximum characters per cue.
curl
curl "https://api.inferentium.ai/v2/transcript/6b2d…/srt?chars_per_caption=40" -H "authorization: tapi_..."

Response

200 · text/plain
1
00:00:00,100 --> 00:00:00,800
Bom dia.

2
00:00:01,500 --> 00:00:02,400
Tudo bem?

Errors

StatusCode / bodyWhen
400—chars_per_caption negative; or the transcript is not ready (as for sentences)
404—unknown id
GET/v2/transcript/{id}/vtt

VTT captions

The same cues as SRT in WebVTT: a WEBVTT header, a dot before the milliseconds, no cue numbers. Free.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathstring—A completed transcript.
chars_per_captionqueryinteger32Maximum characters per cue.
curl
curl https://api.inferentium.ai/v2/transcript/6b2d…/vtt -H "authorization: tapi_..."

Response

200 · text/plain
WEBVTT

00:00:00.100 --> 00:00:00.800
Bom dia.

00:00:01.500 --> 00:00:02.400
Tudo bem?

Errors

StatusCode / bodyWhen
400 / 404—as for SRT
GET/v2/transcript/{id}/redacted-audio

Redacted audio (not yet)

Answers 501 until PII audio redaction ships.

Response

501
{ "error": "Redacted audio is not available yet. It arrives with the pii_audio feature; GET /v2/transcript/{id} returns the transcript in the meantime." }

Realtime — WebSocket

Stream microphone or call audio and get partial transcripts while the person is still speaking. One socket per session, PCM16 in, JSON out. US$0.12 per hour of audio received; speaker labels are a second line at the same rate.

WS/ws/realtime

Realtime session

Connect, optionally send a start message, then binary PCM16 frames. Partials arrive every 320 ms of speech, a final when the utterance closes, and an end with the charge when you stop. /v1/realtime is the same endpoint with header auth only.

Auth: API key (Authorization header, or ?apikey= on /ws/realtime) or an upload token (?uploadToken=) with stt:write

Request

ParameterWhereTypeDefaultDescription
apikeyquerystring—Your key, for clients that cannot set headers (browsers). Only on /ws/realtime. Prefer an upload token in a browser.
uploadTokenquerystring—A short-lived token from POST /api/v1/upload-tokens. The safe way to connect from a browser.
languagequerystringautoHint; overridden by the start message.
diarizequerybooleanfalseSpeaker labels; OR-ed with the start message.
typemessage"start"—First message (optional; a binary frame first means defaults).
sampleRatemessageinteger160008000, 16000, 22050, 24000, 32000, 44100 or 48000. Anything else closes with 4400.
languagemessagestringautoA bare code (pt, en, es) or a locale (pt-BR, pt-PT, en-GB). pt means pt-BR. Unknown codes close with 4400.
diarizemessagebooleanfalseSpeaker labels, up to 4 voices.
(binary frames)requiredmessagePCM16 LE mono—Raw signed 16-bit little-endian samples at sampleRate, any frame size. 100 ms at 16 kHz is 3200 bytes. No Opus, WAV or mu-law on this socket.
Client → server
{"type":"start","sampleRate":16000,"language":"pt","diarize":false}   // optional, must be first if sent
<binary PCM16 frames, sent at live pace>                                 // repeat
{"type":"ping"}                                                          // → {"type":"pong"}
{"type":"stop"}                                                          // flush, get "end", socket closes with 1000
Python
import asyncio, json, soundfile as sf, websockets

async def main():
    pcm, sr = sf.read("call.wav", dtype="int16")                 # 16 kHz mono
    async with websockets.connect("wss://api.inferentium.ai/ws/realtime",
                                  additional_headers={"Authorization": "Bearer tapi_..."}) as sock:
        await sock.send(json.dumps({"type": "start", "sampleRate": sr, "language": "pt"}))
        async def reader():
            async for raw in sock:
                m = json.loads(raw)
                if m["type"] in ("partial", "final"): print(m["type"], m["text"])
                if m["type"] == "end": print("billed", m["audioSeconds"], "s"); return
        task = asyncio.create_task(reader())
        for i in range(0, len(pcm), sr // 10):                    # 100 ms frames
            await sock.send(pcm[i:i + sr // 10].tobytes()); await asyncio.sleep(0.1)
        await sock.send(json.dumps({"type": "stop"})); await task
asyncio.run(main())
Browser
// On your server: POST https://api.inferentium.ai/api/v1/upload-tokens with your key → { token }
const sock = new WebSocket("wss://api.inferentium.ai/ws/realtime?uploadToken=" + token);
sock.onopen = () => sock.send(JSON.stringify({ type: "start", sampleRate: 16000, language: "pt" }));
sock.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === "partial") caption.textContent = m.text;      // still changing
  if (m.type === "final")   transcript.append(m.text + " ");   // committed
};
// an AudioWorklet turns the mic into Int16 frames: node.port.onmessage = (e) => sock.send(e.data);

Response

Server → client, in order
{"type":"ready","sessionId":"rt_7c1e…","sampleRate":16000,"language":"pt","languageCode":"pt-BR","chunkMs":320,"diarize":false}

{"type":"partial","utterance":0,"text":"hoje é sexta","startS":0.7,"endS":2.1}                      // every 320 ms while speaking

{"type":"final","utterance":0,"text":"Hoje é sexta-feira, dia de deploy.","startS":0.7,"endS":3.9,"closedBy":"eou",
 "words":[{"word":"Hoje","startS":0.72,"endS":0.96},{"word":"é","startS":0.98,"endS":1.04},…]}

{"type":"usage","audioSeconds":15.0}                                                                 // every 15 s of audio

{"type":"end","sessionId":"rt_7c1e…","audioSeconds":31.2,
 "metrics":{"utterances":4,"partials":82,"steps":97,"stepMs":2100,"gpuMs":610,"wallMs":31900},
 "requestId":"rt_7c1e…","creditsChargedMicro":103990,"balanceMicroCredits":99896010}
With diarize: true
{"type":"ready", …, "diarize":true, "diarizeChunkMs":1280}
{"type":"partial","utterance":0,"text":"oi, tudo","speaker":"A", …}          // speaker is null until the diarizer has heard it
{"type":"final","utterance":0,"text":"Oi, tudo bem?","speaker":"A",
 "words":[{"word":"Oi,","startS":0.72,"endS":0.96,"speaker":"A"},…], …}
{"type":"final","utterance":1,"text":"Tudo, e você?","speaker":"B", …}
{"type":"end", …, "metrics":{…,"speakers":2,"diarChunks":41},
 "creditsChargedMicro":207980,
 "charged":[{"product":"stt-realtime","creditsMicro":103990},{"product":"stt-realtime-diarization","creditsMicro":103990}],
 "balanceMicroCredits":99792020}
Error, then close
{"type":"error","error":"insufficient_credits"}      // followed by a close with the code below

Errors

StatusCode / bodyWhen
401 / 403 / 429HTTP, before the upgrademissing or invalid key, key without stt:write, rate limit
close 4400error message firstno start or audio within 15 s; bad first message; unsupported sampleRate or language; unknown control type; session over 4 h
close 4402insufficient_credits / audio budget exceededno balance at connect, or the hold ran out mid-session (what was received is charged)
close 4503overloaded / startingworker at capacity or still loading
close 1011backend_unavailable / internal_errorfleet unreachable; nothing charged
  • Utterances close after 600 ms without speech (closedBy eou) or on stop (flush). final.words carries word times, absolute from session start. partial.endS is the end of the audio the text covers, so now − sentAt(endS) is how far the caption trails.
  • Text is verbatim: numbers come out in words. Inverse text normalization is not applied yet.
  • Billing holds up to one hour of audio at connect (capped by your balance) and settles the seconds actually received on end, or on the last usage if the socket died. Sessions longer than an hour must reconnect. Exactly one end message always arrives.
  • Speaker letters go by first appearance in the session and stay stable. A fifth voice is merged into the closest.

Account, keys & tokens

Read your balance and usage, mint short-lived browser tokens, and manage keys from code. All of these are free.

GET/api/v1/account/balance

Balance

Current prepaid balance. Read from the database on every call, never cached.

curl
curl https://api.inferentium.ai/api/v1/account/balance -H "Authorization: Bearer tapi_..."

Response

200
{ "balanceMicroCredits": 98707647, "balanceCredits": 98.707647 }
  • 1 credit = US$0.01 = 1,000,000 micro-credits.
GET/api/v1/account/usage

Usage records

One record per product line per request, newest first. Add-on lines (speaker labels) are separate records whose requestId is the base id plus the product.

Request

ParameterWhereTypeDefaultDescription
fromquerydatetime—ISO 8601; records created at or after.
toquerydatetime—ISO 8601; records created at or before.
productquerystring—Exact product code: stt, stt-sync, stt-realtime, stt-diarization, stt-realtime-diarization.
takequeryinteger1001 to 1000.
curl
curl "https://api.inferentium.ai/api/v1/account/usage?product=stt&take=50" -H "Authorization: Bearer tapi_..."

Response

200
{
  "accountId": "5a7b8c9d-0e1f-4a2b-8c3d-4e5f60718293",
  "records": [
    {
      "requestId": "req_9f1c2d3e4b5a6f708192a3b4c5d6e7f8",
      "product": "stt",
      "modelName": "inferentium-stt-1",
      "quantity": 7.43,
      "unit": 0,
      "creditsChargedMicro": 10321,
      "status": 1,
      "createdAt": "2026-10-27T13:58:02.114Z",
      "settledAt": "2026-10-27T13:58:10.902Z"
    }
  ]
}
  • unit is numeric: 0 seconds, 1 tokens, 2 characters, 3 minutes. status is numeric: 0 reserved, 1 settled, 2 failed, 3 refunded.
  • quantity is seconds of audio for every transcription product.
POST/api/v1/upload-tokens

Mint a browser token

Never ship your API key to a browser or an app. Mint a token on your server and let the client post audio straight to us, so the audio crosses the network once. The token can only transcribe; it cannot read your account or mint another token.

Auth: API key (not an upload token)

curl
curl -X POST https://api.inferentium.ai/api/v1/upload-tokens -H "Authorization: Bearer tapi_..."

Response

200
{ "token": "eyJhaWQiOiI1YTdi…fQ.c0ZlZmVkU2ln…", "expiresInSeconds": 300, "uploadHeader": "X-Upload-Token" }
  • Send it as X-Upload-Token: <token> on POST /v1/audio/transcriptions, /api/v1/stt/transcribe, /api/v1/stt/transcribe/stream and /v2/transcribe, or as ?uploadToken=<token> on /ws/realtime.
  • Valid 5 minutes; 600 requests per hour per parent key.
POST/api/v1/account/keys

Create an API key

The raw key is returned once, here only. New keys get stt:read and stt:write and 1,000 requests per hour.

Request

ParameterWhereTypeDefaultDescription
nameJSON bodystringAPI keyA label for the console.
curl
curl https://api.inferentium.ai/api/v1/account/keys \
  -H "Authorization: Bearer tapi_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "production" }'

Response

200
{ "id": "7c4f1d2e-8b9a-4c3d-9e0f-1a2b3c4d5e6f", "name": "production", "apiKey": "tapi_AbC…", "createdAt": "2026-10-27T18:03:11.227Z" }
GET/api/v1/account/keys

List API keys

Your account's keys, newest first, without the secret.

curl
curl https://api.inferentium.ai/api/v1/account/keys -H "Authorization: Bearer tapi_..."

Response

200
{
  "keys": [
    { "id": "7c4f1d2e-8b9a-4c3d-9e0f-1a2b3c4d5e6f", "name": "production", "isActive": true,
      "createdAt": "2026-10-27T18:03:11.227Z", "lastUsedAt": "2026-10-27T18:40:02.001Z", "usageCount": 128 }
  ]
}
DELETE/api/v1/account/keys/{id}

Revoke an API key

Takes effect immediately on every gateway instance.

Request

ParameterWhereTypeDefaultDescription
idrequiredpathuuid—The key id from the list.
curl
curl -X DELETE https://api.inferentium.ai/api/v1/account/keys/7c4f1d2e-8b9a-4c3d-9e0f-1a2b3c4d5e6f -H "Authorization: Bearer tapi_..."

Response

204 No Content
(empty body)

Errors

StatusCode / bodyWhen
404—unknown id, or a key of another account
POST/api/v1/billing/checkout

Buy credits

Creates a Stripe Checkout session for a credit pack and returns the URL to send the buyer to. Credits land on the account when Stripe confirms payment.

Request

ParameterWhereTypeDefaultDescription
creditsrequiredJSON bodyinteger—Credits to buy; 1 credit = US$0.01.
successUrlrequiredJSON bodystring—Where Stripe sends the buyer after paying.
cancelUrlrequiredJSON bodystring—Where Stripe sends the buyer if they back out.
curl
curl https://api.inferentium.ai/api/v1/billing/checkout \
  -H "Authorization: Bearer tapi_..." \
  -H "Content-Type: application/json" \
  -d '{ "credits": 1000, "successUrl": "https://your.app/billing?ok=1", "cancelUrl": "https://your.app/billing" }'

Response

200
{ "url": "https://checkout.stripe.com/c/pay/cs_live_…", "sessionId": "cs_live_a1b2c3" }

Errors

StatusCode / bodyWhen
400{"error":"credits must be > 0"}credits missing or zero
503stripe_not_configuredcard payments are not enabled on this deployment

Preview — not enabled on the public API yet

preview

Text to speech and voice agents run on the same keys and credits and are already in the console playgrounds. On api.inferentium.ai they answer 503 service_not_enabled until they are switched on; GET /v1/services tells you what is live. Shapes documented here so you can build against them now.

GET/v1/services

What is enabled

No auth. The products and transcription features this deployment serves.

curl
curl https://api.inferentium.ai/v1/services

Response

200 · api.inferentium.ai today
{ "transcription": true, "understanding": false, "speech": false, "voice_agents": false, "features": ["diarization"] }
POST/v1/audio/speech

Text to speech

The OpenAI speech shape: JSON in, audio bytes out. US$5 per million characters, charged on the exact input length.

Auth: API key or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
inputrequiredJSON bodystring—Up to 5,000 characters.
voiceJSON bodystring—One of GET /v1/voices. OpenAI names (alloy, nova, …) map onto ours by timbre.
modelJSON bodystringinferentium-tts-1Accepted for SDK compatibility.
response_formatJSON bodystringmp3mp3, opus, flac, aac, wav, pcm (16-bit LE), pcm_mulaw.
sample_rateJSON bodyinteger24000Our extension: 8000 for a phone bridge.
speedJSON bodynumber1.0Playback speed.
curl
curl https://api.inferentium.ai/v1/audio/speech \
  -H "Authorization: Bearer tapi_..." \
  -H "Content-Type: application/json" \
  -d '{ "model": "inferentium-tts-1", "voice": "ipanema", "input": "Seu pedido chega amanhã.", "response_format": "mp3" }' \
  --output fala.mp3

Response

200 · audio/mpeg (bytes), with headers
x-request-id: req_…
x-voice: ipanema
x-characters: 24
x-audio-seconds: 1.9
x-credits-charged-micro: 12000
x-balance-micro-credits: 99988000
503 · until enabled
{ "error": { "message": "Speech is not enabled on this deployment.", "type": "invalid_request_error", "param": null, "code": "service_not_enabled" } }

Errors

StatusCode / bodyWhen
400missing_input / input_too_long / unknown_formatbad body
402insufficient_creditsbalance does not cover the characters
502synthesis_failedbackend failed; nothing charged
GET/v1/voices

Voices

No auth. Empty while speech is disabled.

Request

ParameterWhereTypeDefaultDescription
languagequerystring—Filter by language.
curl
curl https://api.inferentium.ai/v1/voices

Response

200
{ "object": "list", "data": [ { "id": "ipanema", "name": "Ipanema", "language": "pt", "timbre": "warm female", "bilingual": true, "sample_rate": 24000 } ] }
WS/v1/agents/ws

Voice agent session

One socket that hears, thinks and speaks: send a session.update first, then 24 kHz PCM16 frames base64-encoded in input.audio messages; receive transcripts, the agent's audio and tool calls. /ws/agent is the same endpoint and also takes ?apikey=. US$1.50 per hour of connected call.

Auth: API key or upload token with stt:write

Request

ParameterWhereTypeDefaultDescription
typerequiredmessage"session.update"—Must be the first message, within 30 s.
system_promptmessagestring—Up to 20,000 characters.
greetingmessagestring—Spoken verbatim when the session starts.
voicemessagestringipanemaOne of GET /v1/voices.
languagemessagestringpt
sample_ratemessageinteger240008000 to 48000, both directions.
turn_detectionmessagestring | objectbalancedmin_latency, balanced or max_accuracy; or {mode, min_silence, max_silence, vad_threshold, interrupt_response, interrupt_after}.
toolsmessageobject[]—Up to 20 {name, description, parameters}. Add an http block and we call the tool server-side; without it you answer tool.call with tool.result.
keytermsmessagestring[]—Up to 100 words to bias recognition.
volumemessageinteger1000 to 100.
Client → server
{"type":"session.update","system_prompt":"Você atende clientes por telefone. Responda em uma frase curta.",
 "greeting":"Alô, em que posso ajudar?","voice":"ipanema","language":"pt","turn_detection":"balanced",
 "tools":[{"name":"buscar_pedido","description":"Busca um pedido pelo número.",
           "parameters":{"type":"object","properties":{"numero":{"type":"string"}},"required":["numero"]}}]}

{"type":"input.audio","audio":"<base64 PCM16 24 kHz mono, 20 ms frames>"}     // repeat
{"type":"tool.result","call_id":"call_1","result":{"status":"a caminho"}}      // answer to a tool.call
{"type":"conversation.message","role":"system","content":"O cliente é VIP."}   // context without a reply
{"type":"reply.create","instructions":"Pergunte o número do pedido."}          // make the agent speak now
{"type":"session.update","volume":80}                                          // patch: system_prompt, turn_detection, tools, keyterms, volume
{"type":"session.end"}

Response

Server → client
{"type":"session.ready","session_id":"sess_19a3f2c1b","config":{ …the config as applied… }}
{"type":"input.speech.started"}
{"type":"transcript.user.delta","text":"quero saber"}
{"type":"input.speech.stopped"}
{"type":"transcript.user","text":"Quero saber o status do meu pedido."}
{"type":"reply.started","reply_id":"reply_1"}
{"type":"tool.call","call_id":"call_1","name":"buscar_pedido","arguments":{"numero":"A-91"}}
{"type":"transcript.agent.delta","reply_id":"reply_1","delta":"Seu pedido "}
{"type":"reply.audio","reply_id":"reply_1","audio":"<base64 PCM16>"}
{"type":"transcript.agent","reply_id":"reply_1","text":"Seu pedido está a caminho.","interrupted":false}
{"type":"reply.done","reply_id":"reply_1","status":"completed"}             // or "interrupted" on barge-in
{"type":"session.error","code":"invalid_config","message":"volume must be between 0 and 100."}
{"type":"session.ended","reason":"client","audio_seconds":37.4,
 "request_id":"call_9f2a…","billed_seconds":42,"credits_charged_micro":1750014,"balance_micro_credits":98249986}
503 · until enabled (HTTP, before the upgrade)
{ "error": "service_not_enabled", "message": "Voice agents are not enabled on this deployment." }

Errors

StatusCode / bodyWhen
close 4400session_not_configuredfirst message missing or not session.update
close 4402insufficient_creditsbalance does not cover a call
close 1011backend_unavailable / internal_errorruntime unreachable; nothing charged
  • Barge-in: when the caller speaks over the agent for interrupt_after ms (320 by default) the reply is cut, and only what was actually spoken stays in the conversation.
  • Billing is per second of connected call, rounded up, settled in session.ended. Idle sockets close after 120 s; calls end after 1 hour.

Status

No auth, no rate limit. Safe to poll from a monitor.

GET/health

Liveness

The gateway process is up.

curl
curl https://api.inferentium.ai/health

Response

200
{ "status": "healthy", "timestamp": "2026-10-27T18:03:11.227Z", "version": "1.0.0", "service": "inferentium-gateway" }
GET/health/ready

Readiness

The gateway can reach its database.

curl
curl https://api.inferentium.ai/health/ready

Response

200
{ "status": "ready", "database": "ok" }
503
{ "status": "not_ready", "database": "unreachable" }

Languages

25 languages with automatic detection on the pre-recorded routes, 40 locales on realtime. Pass language to hint, or omit it. Measured word and character error rates, per language and per audio length, are on the model spec sheet.

Pricing & credits

Prepaid credits, billed per second of audio and rounded up per request. 1 credit = US$0.01 = 1,000,000 micro-credits, which is the unit every response reports. The products run the same model but are priced for what they deliver:

Product linePricePer second
stt — pre-recorded (OpenAI, native, jobs, AssemblyAI)US$0.05 / hour1,389 micro-credits
stt-diarization — speaker labels on pre-recorded+US$0.02 / hour556 micro-credits
stt-sync — one request, transcript in the responseUS$0.12 / hour3,333 micro-credits
stt-realtime — WebSocketUS$0.12 / hour3,333 micro-credits
stt-realtime-diarization — speaker labels on realtime+US$0.12 / hour3,333 micro-credits

New accounts get 100 credits free (US$1.00, about 20 hours of pre-recorded audio). Packs: 1,000 credits = US$10, 5,000 = US$50, 25,000 = US$250. Each request reserves an estimate then settles the exact duration; failed requests are released and never charged. Buy credits and manage keys in the console.

Need something not covered here (batch pipelines, dedicated fleet, data residency, enterprise SSO)? Talk to us.