Skip to content
Documentation resources

Speech API / v1

Build with oruk

Transcribe live speech in 32 supported locales, or upload English audio for transcription, emotion, and speaking-style analysis. Start with the example below or try your audio without an account.

Built with Oruk: Reflect's mobile-app demo built with the public speech API in August 2026.

Production base URL
https://speech-api.oruk.ai
Current scope
Multilingual realtime + prerecorded English
On this page

01

Quickstart

Analyze an English recording with Resonance. Each recipe downloads the sample, sends it to the API, checks for errors, and prints the JSON response.

Recorded audio · Resonance

Transcript, 15 emotion labels, and 16 speaking-style labels. Stable, English.

Use the file recipe

Live audio · Realtime

Transcript tokens in 32 locales and phrase-level emotion scores. Preview.

Connect a live stream

1. Set your API key

Create an API key in your account with an active subscription or 7-day plan trial. You can also run the public demo and test a temporary key first. Replace the placeholder below in your terminal. Keep this key on your server.

Terminal · macOS / Linux
export ORUK_API_KEY='your-api-key'

2. Run a request

Sample: 1029_IEO_HAP_HI.wav (2.1 seconds). Each recipe uses this same recording.

Requires cURL 7.76+ and uuidgen (included with macOS; available in uuid-runtime on Ubuntu). Paste this into your terminal.

cURL
# Set ORUK_API_KEY to your secret API key first.
: "${ORUK_API_KEY:?Set ORUK_API_KEY first.}"
curl --fail --silent --show-error --location --max-time 30 --output '1029_IEO_HAP_HI.wav' 'https://oruk.ai/samples/emotional/1029_IEO_HAP_HI.wav' || exit 1

curl --fail-with-body --silent --show-error --max-time 180 'https://speech-api.oruk.ai/v1/audio/analysis' \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "X-Request-ID: $(uuidgen)" \
  --form-string 'model=oruk-resonance' \
  -F 'file=@"1029_IEO_HAP_HI.wav"'

3. Read the result

This excerpt contains the saved Resonance output for the selected recording. A new request also returns a result ID, timed segments, and audio usage; output can change with model updates. Scores are independent labels, so they do not sum to one. How to read scores.

Response excerpt and sample source
Saved response excerpt
{
  "object": "speech.result",
  "task": "analysis",
  "model": "oruk-resonance",
  "text": "It's 11 o'clock!",
  "duration": 2.06875,
  "emotions": [
    {
      "label": "frustrated",
      "score": 0.9688562154769897
    },
    {
      "label": "happy",
      "score": 0.9241418242454529
    }
  ],
  "styles": [
    {
      "label": "energetic",
      "score": 0.9967268705368042
    },
    {
      "label": "impatient",
      "score": 0.9473810195922852
    },
    {
      "label": "irritated",
      "score": 0.8933094143867493
    },
    {
      "label": "playful",
      "score": 0.7969253659248352
    }
  ]
}

CREMA-D recording, used in training and selected for this demo. It is not an evaluation result. Audio license.

To use your own audio, replace the sample download with a local file. Already have transcription? Use the emotion-only endpoint with your audio to get emotion scores without running transcription. Uploads support WAV, FLAC, MP3, M4A, OGG, and WebM, up to 30 MB and 60 minutes.

01A

Evaluate Fourier on the same recording

Evaluate Fourier when your workflow needs transcription and its native emotion output together. It runs those tasks in parallel and returns the shared speaking-style output. These examples download the same sample as the Resonance quickstart and select oruk-fourier. Set ORUK_API_KEY as above, then run one recipe. Both models consume one plan minute per minute of audio. Speaker diarization requires Resonance.

Requires cURL 7.76+ and uuidgen (included with macOS; available in uuid-runtime on Ubuntu). Paste this into your terminal.

cURL
# Set ORUK_API_KEY to your secret API key first.
: "${ORUK_API_KEY:?Set ORUK_API_KEY first.}"
curl --fail --silent --show-error --location --max-time 30 --output '1029_IEO_HAP_HI.wav' 'https://oruk.ai/samples/emotional/1029_IEO_HAP_HI.wav' || exit 1

curl --fail-with-body --silent --show-error --max-time 180 'https://speech-api.oruk.ai/v1/audio/analysis' \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "X-Request-ID: $(uuidgen)" \
  --form-string 'model=oruk-fourier' \
  -F 'file=@"1029_IEO_HAP_HI.wav"'

Compare outputs on recordings from your actual workflow. The sample is for integration testing and is not a model-quality comparison.

01B

Live transcription and emotion

Connect to WS /v1/realtime with model=oruk-realtime. Send mono PCM16 audio as binary frames. Live transcript tokens arrive immediately while a parallel stream emits text-aligned emotion scores at phrase boundaries. The model transcribes 32 locales out of the box and defaults to automatic language detection.

Node.js · streaming PCM16
import WebSocket from "ws"

const socket = new WebSocket(
  "wss://speech-api.oruk.ai/v1/realtime?model=oruk-realtime",
  "oruk-realtime",
  { headers: { Authorization: `Bearer ${process.env.ORUK_API_KEY}` } },
)

socket.on("open", () => {
  socket.send(JSON.stringify({
    type: "session.update",
    session: {
      model: "oruk-realtime",
      language: "auto",
      sample_rate: 16000,
      word_timestamps: true,
      phrase_emotions: true,
      phrase_silence_ms: 600,
      diarize: false, // true labels each phrase with a speaker; included in plan minutes
    },
  }))
})

// Send mono signed 16-bit PCM chunks as binary frames while recording.
export function sendPcm16(chunk) {
  socket.send(chunk, { binary: true })
}

export function finish() {
  socket.send(JSON.stringify({ type: "input_audio_buffer.commit" }))
}

socket.on("message", (data) => {
  const event = JSON.parse(data.toString())
  if (event.type === "conversation.item.input_audio_transcription.delta") {
    process.stdout.write(event.delta)
  }
  if (event.type === "conversation.item.input_audio_transcription.completed") {
    console.log("\nfinal:", event.transcript)
  }
  if (event.type === "conversation.item.input_audio_emotion.completed") {
    console.log("phrase:", event.speaker, event.text, event.top_emotion)
  }
})
Phrase emotion · completed event
{
  "type": "conversation.item.input_audio_emotion.completed",
  "event_id": "event_...",
  "request_id": "req_...",
  "model": "oruk-realtime",
  "phrase_id": "phrase_1",
  "start": 0.0,
  "end": 3.3,
  "speaker": "speaker_0",
  "text": "I am genuinely excited",
  "top_emotion": { "label": "happy", "score": 0.72 },
  "emotions": [
    { "label": "happy", "score": 0.72 },
    { "label": "surprised", "score": 0.16 }
  ],
  "emotion_latency_ms": 34.2
}

Building a Python voice pipeline? The Oruk Pipecat adapter has a published release candidate with a runnable file-stream example and browser speech demo. It delivers transcript frames and separate phrase-emotion events.

Each PCM chunk enters transcription immediately. A separate acoustic path closes a phrase after phrase_silence_ms (600 ms by default; 200–2000 ms) or at phrase_max_ms (8 seconds by default; 1–15 seconds). The two paths run concurrently, so phrase analysis does not hold back token deltas.

Realtime returns a variable-length array of emotion labels and scores from the active acoustic model. Do not hard-code a seven-label list: a September 6 live check returned frustrated. Match asynchronous results by phrase_id and timestamps. If analysis fails for one phrase, the socket emits conversation.item.input_audio_emotion.failed and transcription continues. Set phrase_emotions: false to opt out.

Live speaker labels. Set diarize: true in session.update (default false) to run streaming diarization alongside the session. Every phrase event then carries a speaker field (speaker_0, speaker_1, … in order of first appearance, stable for the session). Speaker changes can split buffered phrases, and the socket also emits conversation.item.input_audio_speaker.started and …ended events with a timestamp as turns begin and end.

A phrase is attributed to the speaker holding most of its span; a phrase that lands before the diarization stream is warm carries speaker: null. Late speaker events can leave an already emitted phrase with mixed speech or the wrong speaker. Speaker labels do not separate overlapping voices into isolated audio. Measure transcription quality, turn accuracy, and event latency on representative recordings before relying on this stream. Without the flag speaker is always null.

Live diarization is included in plan minutes. The audio is processed for transcription, phrase emotion, and the optional speaker pass. See data handling for processing and retention details.

  • 8–96 kHz mono signed PCM16
  • Binary frames or base64 append events
  • Automatic or explicit language selection
  • Phrase-level emotion events
  • Optional live speaker labels (diarize: true)
  • 10 minute maximum session
  • Word timestamps and punctuation
  • One audio minute uses one plan minute

Preview

Resonance 2: emotion and speaking style

Analyze vocal expression with oruk-resonance-2. The model returns continuous scores for 15 emotions and 16 speaking styles. Six opposite pairs share signed axes, so both ends of a pair can never be selected together.

Access approval

Existing accounts keep access. New organizations need approval for Resonance-2, separate from plan activation. Request it from Model access or with the API below. Approval covers every API key in the organization. The homepage demo stays available without approval.

Request Resonance-2 access
curl --fail-with-body 'https://speech-api.oruk.ai/v1/models/oruk-resonance-2/access' \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"use_case":"Voice coaching for consenting participants in our application."}'

POST returns 202 while pending. GET on the same path returns status and approved. Inference returns 403 with model_access_required until approved; denied requests are not billed. Approval alone does not add credit or activate a plan. A Resonance-2 invitation code provides a seven-day trial on a self-serve tier, requires a credit card, and grants access immediately after completed checkout. The trial becomes a paid subscription unless canceled.

Same plans, same price

Same price as Resonance 1. One audio minute uses one minute from your existing speech understanding allowance, with the same overage rate. No plan change or separate add-on.

Use your existing Bearer API key. Monthly and annual plans share the same audio-minute accounting, trial allowance, and spending cap. Usage is measured by the second with a one-second minimum. Legacy metered accounts use the Resonance 1 affect rate, $0.0100 per audio minute.

File request (HTTP)

POST /v1/audio/resonance-2 accepts multipart file and optional model=oruk-resonance-2. Use WAV, FLAC, MP3, M4A, OGG, or WebM: 0.1–120 seconds, up to 30 MiB. Audio is decoded to mono 16 kHz. The query parameter regime=f1 is the default; regime=precision selects the more conservative label thresholds. Continuous scores stay the same.

Resonance 2 · cURL
curl --fail-with-body 'https://speech-api.oruk.ai/v1/audio/resonance-2?regime=f1' \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "X-Request-ID: $(uuidgen)" \
  -F model=oruk-resonance-2 \
  -F file=@clip.wav

This POST returns one clip-level affect result after the upload. It does not return a transcript, word timings, speaker diarization, or streaming events. The separate WebSocket route below accepts audio while it is being recorded. Keep using oruk-resonance for transcription and unified analysis. SDK 0.2.10 has no dedicated Resonance 2 helper; use an HTTP request for this contract.

Download the Resonance 2 OpenAPI schema.

Stream audio over WebSocket

Connect to wss://speech-api.oruk.ai/v1/audio/resonance-2/stream with Authorization: Bearer $ORUK_API_KEY in the upgrade request. Use a server-side WebSocket client that supports headers. This route does not accept an API key in the URL or a browser authentication subprotocol; keep your key on your server. The public microphone demos use a separate server-authenticated connection.

Wait for type: ready with sample_rate: 16000, encoding: float32le, and idempotency: shared_http_ws. Send mono 16 kHz float32 little-endian PCM as binary frames, without a WAV header. Each frame holds 1–16,000 finite samples (at most 64,000 bytes). The buffer holds up to 120 seconds. Audio frames alone do not request inference; send a JSON commit to analyze a buffered range. Sample offsets start at zero; end_sample is exclusive.

Commit the first two seconds of buffered audio
{
  "type": "commit",
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "start_sample": 0,
  "end_sample": 32000,
  "regime": "f1",
  "scope": "live"
}

Generate a new stable request ID for each analysis. Use regime: f1 (default) or precision. Public API commits can select 0.1–120 seconds; scope is optional and accepts live or clip. For live updates, use rolling windows of up to 20 seconds, then commit the full clip when recording stops. Public commits are not capped at 20 seconds by scope: live. The website demos have tighter limits: 20 seconds per live window and 30 seconds per full clip.

A successful commit returns {"type":"result","id":"…","result":{…},"timings_ms":{…}}. The result has the same fields, window aggregation, and label thresholds as HTTP. Errors use type: error with id, status, and error. These are expression snapshots of the committed range, without a transcript, word timestamps, diarization, or automatic speech-boundary events.

Each new commit uses the same billing rules as an HTTP request, including the one-second minimum. Repeated analyses of overlapping ranges count separately. Authorization, model access, and available usage are checked live for every commit. After a lost response, use the original API key and the same ID, decoded PCM, and regime over WebSocket or HTTP; set HTTP X-Request-IDto the commit ID. A completed result can be replayed for 24 hours without a second charge, while a revoked or expired key remains invalid. Only 409 request_in_progressmeans the original analysis is still pending; a conflicting payload must not be retried as a fresh paid request automatically.

Send {"type":"reset"} between clips to clear the audio buffer and restart sample offsets at zero. A session allows at most 256 commits, 10 minutes total, and 30 seconds without a received message. Resetting does not reset these limits. SDK 0.2.10 has no dedicated Resonance 2 streaming helper; use this WebSocket protocol directly. Upload streaming avoids resending the buffered audio at each commit; response time still includes admission, inference, result storage, and network travel.

Read continuous scores

Each axis ranges from −1 to +1. Negative values favor the first label; positive values favor the second. Zero means no preference between them. For example, sad__happy: -0.8 gives sad a score of 0.8 and happy a score of 0. This is an 80% dominance score, not an 80% probability of a person being sad.

Six signed emotion and style axes
Axis key−1+1
sad__happysadhappy
worried__relievedworriedrelieved
disappointed__hopefuldisappointedhopeful
hesitant__confidenthesitantconfident
tired__energetictiredenergetic
formal__casualformalcasual

The remaining 19 labels range independently from 0 to 1: excited, angry, frustrated, scared, disgusted, surprised, embarrassed, proud, neutral, passionate, irritated, warm, playful, sarcastic, deadpan, sincere, skeptical, impatient, distracted. Scores do not sum to one. The selected labels list applies the chosen thresholds and may be empty; the model never forces a top label.

Response fields

scores · axes · unipolar · labels
All 31 label scores, six signed axes, 19 independent scores, and selected label/score pairs.
duration · window_count · aggregation
Measured seconds and the number of nonoverlapping 20-second windows. Longer clips average window logits before calibration; aggregation is mean_window_logits.
model · model_revision · calibration_revision · regime
Model identity, pinned artifact revisions, and the selected threshold regime.
abstained · abstention_reason
Exact digital silence returns zero scores, no labels, and digital_silence. This is not a general voice-activity detector.
id · object · task · usage
Request identity, speech.affect.result, affect, and measured/billable seconds. Reference cost fields are not the subscription invoice.

Keep the same X-Request-ID, audio, and regime when retrying a request. Completed results can be replayed for 24 hours without a second charge; changing the audio or regime with the same ID returns 409. Back off on 429 or 503 and retry.

The model reads acoustic expression without requiring a transcript or language code. Performance varies by language and recording conditions; language-independent accuracy is not established. Scores describe how speech sounds, not a person’s inner state.

To inspect the evaluation, download the 546 recorded responses and replay the diagnostic. The package includes calibration thresholds, the protocol, and an offline scorer. It uses acted recordings of one sentence from development data, with unaudited training overlap. It is not an independent evaluation of the current API on new audio.

Orukeet · Preview

Fast transcription with optional tasks

Set model=oruk-orukeet on POST /v1/audio/transcriptions. Use your existing Oruk organization key. Every plan includes an Orukeet allowance at $0.00045/audio minute ($0.027/hour), alongside the other-model minute allowance. Selected tasks use the same Orukeet allowance at their listed rates; extra usage shares your spending cap. Audio is measured to the sample; only the final usage amount is rounded up to one microdollar.

Create an account, start your trial, and create your API key. Hobby includes up to 20,000 transcription minutes each month; the standard trial includes a quarter of its allowance. Set ORUK_API_KEY in your environment, then run:

Orukeet transcription quickstart
curl --fail --location --output sample.wav \
  https://oruk.ai/samples/oruk-quickstart.wav
curl --fail-with-body https://speech-api.oruk.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -F model=oruk-orukeet \
  -F file=@sample.wav

For smaller uploads, use lossless FLAC. The direct REST endpoint is https://orukeet-direct.oruk.ai/v1/audio/transcriptions; it accepts the same key and fields. Reuse connections and send the model field before the file. For live capture, use the WebSocket protocol below.

Optional transcription task flags
FieldResultAdded $/minute
emotion_detection=true15 emotion scores and labels from vocal delivery.0.0080
diarize=trueSpeaker labels and transcripts for each turn.0.0040

All flags default to false. num_speakers accepts 1–32 with diarize=true. Optional tasks require response_format=json (default) or verbose_json; plain text is available for transcription alone. Unsupported fields, duplicate fields, and invalid values are rejected.

Responses have object: audio.transcription, text, duration, and usage. Emotion detection adds emotion_detection with labels and all 15 scores. Diarization adds speaker-tagged segments with turn transcripts and a speakers list. All timestamps use seconds. Transcription alone does not return word timestamps.

English audio, at most 60 seconds and 4 MiB per complete multipart request. WAV, FLAC, MP3, M4A, OGG, and WebM are accepted. Use mono 16 kHz PCM16 WAV or FLAC to avoid format conversion. The default organization limit is eight active requests or recordings. Optional tasks add processing time; a failed requested task fails the request and releases its reservation without a charge.

WebSocket protocol

Preconnect to wss://orukeet-direct.oruk.ai/v1/audio/transcriptions/stream. Server clients can use a Bearer key; browsers use a single-use token from POST /v1/client-token. Tokens expire after 60 seconds. Keep your long-lived API key on your server.

Authenticated browser streaming
// On your authenticated application server, using the user's Oruk key:
const response = await fetch('https://speech-api.oruk.ai/v1/client-token', {
  method: 'POST', headers: { Authorization: 'Bearer ' + process.env.ORUK_API_KEY },
});
if (!response.ok) throw new Error(await response.text());
const { token, url } = await response.json();
// Return only token and url to that authenticated browser session.

// In the browser (mono 16 kHz PCM16 frames from your audio capture pipeline):
const socket = new WebSocket(url, ['orukeet.pcm.v1', 'auth.' + token]);
socket.onmessage = ({ data }) => {
  const event = JSON.parse(data);
  if (event.type === 'ready') socket.send(JSON.stringify({
    type: 'configure', options: { emotion_detection: true, diarize: true }
  }));
  if (event.type === 'configured') {
    // Start sending PCM16 ArrayBuffers, each no larger than 64 KiB.
    // When the utterance ends: socket.send(JSON.stringify({ type: 'commit' }));
  }
  if (event.type === 'transcript') console.log(event.text); // text before optional tasks
  if (event.type === 'final') console.log(event); // all requested tasks and usage
  if (event.type === 'error') console.error(event.code, event.message);
};

Wait for ready, optionally configure, and then send little-endian mono 16 kHz PCM16 binary frames. Send commit after the final sample. There are no incremental word hypotheses. With optional tasks enabled, transcript precedes the complete final. Send clear to discard buffered audio. A session can handle successive utterances, expires after ten minutes, and closes after 75 seconds without input.

Each recording reserves enough allowance or overage budget for 60 seconds at its selected task rate when audio begins, then charges its actual duration. clear and failed requests release that reservation. Use a fresh request_id in each configure event, or let the server generate one. REST accepts X-Request-ID; duplicates return 409 and do not create another charge.

HTTP 401 means an invalid or revoked key, 402 means an inactive plan or exhausted allowance/spending limit, 413 means an audio limit, 422 means invalid options, and 429 means capacity is full. Honor Retry-After. A streaming capacity error preserves buffered audio; retry commit after retry_after_ms. Reconnect after other terminal errors. A connection lost after processing may still be charged; inspect account usage before sending the same audio again.

Model and pricing · Benchmark methodology

02

Authentication

Send a bearer key on inference and model-catalog requests. Keys are shown only once. Realtime WebSockets accept the same bearer header; browser clients can send oruk-api-key.<key> as a WebSocket subprotocol. Never put a key in a URL.

Authorization

Authorization: Bearer oruk_live_...

Idempotency

Send a unique X-Request-ID for each inference. Reusing one returns 409 and does not create another debit.

03

Endpoints

All inference endpoints accept file and an optional model field as multipart/form-data.

EndpointTaskOutputDefault model
POST /v1/audio/transcriptionsTranscriptionEnglish transcriptoruk-resonance
POST /v1/audio/emotionsEmotionMultilabel emotion scoresoruk-resonance
POST /v1/audio/stylesStyleMultilabel speaking-style scoresoruk-resonance
POST /v1/audio/affectAffectEmotion and style labelsoruk-resonance
POST /v1/audio/analysisAnalysisTranscript, labels, segments, and tagged textoruk-resonance
POST /v1/audio/proficiencyProficiency0–1 word and recording scores, legacy CEFR, fluency, transcriptoruk-proficiency-1
WS /v1/realtimeRealtime speechLive tokens + phrase emotionoruk-realtime

Catalog and health routes: GET /v1/models, GET /v1/pricing, GET /livez, and GET /readyz.

04

Playground

Test any endpoint without leaving this page. Requests are sent straight from your browser to the API with your key, and the matching cURL command updates as you change the request.

05

API reference

Every inference endpoint takes a multipart/form-data body with a required file and an optional model, and returns one speech.result envelope. Resonance 2 has a dedicated speech.affect.result contract above. Orukeet returns audio.transcription as described above. The full machine-readable contract is in the OpenAPI schema.

POST/v1/audio/transcriptionsTranscription

Returns an English transcript with time-ordered segments and word timings.

With model=oruk-orukeet, this endpoint returns audio.transcription with text, duration, and task usage. Word timestamps are unavailable; diarize=true adds speaker turns. See the Orukeet contract and task rates.

file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-resonance.
diarize
Optional with oruk-resonance. Set true to label speakers; segments become speaker turns with a speaker field. Pass num_speakers to cap the count. Included in plan minutes. See Speaker diarization.
POST/v1/audio/emotionsEmotion

Returns multilabel emotion scores per acoustic segment from Resonance without running transcription. The response carries no transcript text. One minute of audio uses one plan minute, just like unified analysis.

Emotion only, no transcription

Use this endpoint when your application needs emotion scores without words. The audio goes through the Resonance encoder and affect head and stops there: the transcription decoder is never invoked, so no words are decoded, the response has no text, words, or language. One audio minute uses one plan minute, exactly as unified analysis does. Combine with diarize=true for per-speaker emotion without a transcript. Use /v1/audio/analysis when you also want the words.

curl https://speech-api.oruk.ai/v1/audio/emotions \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -F file=@call.wav -F model=oruk-resonance
file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-resonance.
diarize
Optional with oruk-resonance. Set true to label speakers; segments become speaker turns with a speaker field. Pass num_speakers to cap the count. Included in plan minutes. See Speaker diarization.
POST/v1/audio/stylesStyle

Returns multilabel speaking-style scores per acoustic segment. The response intentionally omits transcript text.

file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-resonance.
diarize
Optional with oruk-resonance. Set true to label speakers; segments become speaker turns with a speaker field. Pass num_speakers to cap the count. Included in plan minutes. See Speaker diarization.
POST/v1/audio/affectAffect

Returns emotion and speaking-style scores without transcript text.

file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-resonance.
diarize
Optional with oruk-resonance. Set true to label speakers; segments become speaker turns with a speaker field. Pass num_speakers to cap the count. Included in plan minutes. See Speaker diarization.
POST/v1/audio/analysisAnalysis

Returns an English transcript, emotion labels, speaking-style labels, and a tagged transcript from one request.

file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-resonance.
diarize
Optional with oruk-resonance. Set true to label speakers; segments become speaker turns with a speaker field. Pass num_speakers to cap the count. Included in plan minutes. See Speaker diarization.
POST/v1/audio/proficiencyProficiency

Preview: continuous-pilot-v2 returns word and recording pronunciation scores on 0–1 with accuracy, fluency and prosody. Original CEFR fields and the 0–5 score remain compatible. Read continuous_check for pronunciation validity and the top-level check for CEFR validity and billing. Subscription accounts use plan minutes.

file
Required. WAV, FLAC, MP3, M4A, OGG, or WebM audio.
model
Optional. Defaults to oruk-proficiency-1.
transcript
Optional. A transcript of the audio. When omitted, oruk transcribes the recording first and includes the text in the response. See Proficiency for the response shape.
speech.result envelope
{
  "id": "speech_...",          // unique result ID
  "object": "speech.result",
  "task": "analysis",          // transcription | emotion | style | affect | analysis
  "model": "oruk-resonance",
  "text": "...",               // transcript tasks only
  "tagged_text": "...",        // transcript with inline affect tags
  "language": "en",
  "duration": 2.84,            // measured seconds
  "emotions": [{"label": "happy", "score": 0.94}],
  "styles":   [{"label": "warm",  "score": 0.81}],
  "segments": [                // time-local outputs for longer audio
    {
      "id": 0, "start": 0.0, "end": 2.84,
      "text": "...", "tagged_text": "...",
      "words": [{"word": "...", "start": 0.0, "end": 0.4, "confidence": 0.99}],
      "emotions": [], "styles": [],
      "speaker": null           // "speaker_0", ... when diarize=true
    }
  ],
  "diarized": false,           // true when segments are speaker turns
  "speakers": [],              // speaker labels when diarize=true
  "usage": {
    "audio_seconds": 2.84,
    "billable_seconds": 2.84
  }
}

Transcript fields are only populated by transcription, analysis, and proficiency; emotion, style, and affect intentionally omit transcript text.

Every response reports measured duration, billable duration, estimated cost, and pricing version in usage. Proficiency adds a proficiency object and a check object. Its reference usage may carry billing_unit: "request"; subscription accounts use plan minutes.

Both subscription allowances reset monthly, including on annual plans. Other-model audio uses a one-second minimum. Orukeet measures actual duration and rounds each request to one microdollar. Optional tasks use its allowance; excess usage is billed monthly within the shared spending cap. Usage cost fields show consumption, not the final invoice.

06

Reading emotion and style labels

Labels describe how speech sounds. Emotion labels include happy and frustrated; speaking-style labels include warm and hesitant. They describe vocal delivery, not a speaker's actual feelings, intentions, or personality.

Scores run from 0 to 1
A higher score means stronger model support for that label. Labels are scored independently and can overlap; they do not sum to one. A score of 0.8 is not “80% happy,” an intensity measurement, or an 80% probability of someone's inner state.
Selection uses a threshold for each label
The file models return labels that meet their model-specific thresholds. These thresholds select annotations; they do not make every score a calibrated probability. There is no single cutoff shared by all labels or releases.
An emotion is always selected
For emotion, affect, and analysis requests, the highest-scoring emotion is returned if none reaches its threshold. That fallback may have a low score. Speaking style has no such fallback, so an empty styles array is a valid result.
Use segments to locate a label
The overall file result uses the highest returned segment score for each label, not an average or the fraction of the recording with that emotion. Read segment timestamps and replay the audio to understand where the signal occurred.

Worked example: a recording score is a maximum

Suppose a file response returns frustrated in two segments with the scores below. These invented numbers explain the response contract; they are not a measured recording or benchmark result.

Illustrative scores for one returned emotion label
Response locationfrustrated score
Segment from 0 to 6 seconds0.61
Segment from 6 to 10 seconds0.87
Recording summary0.87

The summary keeps max(0.61, 0.87) = 0.87. It does not report an average, 87% of the audio, or a probability of frustration. To review the strongest returned signal, replay the 6–10 second segment. A label absent from a segment’s returned array has no exposed score there; do not invent a zero. Speaking styles can return an empty array when no label meets its threshold.

Run this response-reading workflow on your own recording.

Emotion

15 labels

Select a label to explore.

Speaking style

16 labels

Select a label to explore.

07

Speaker diarization

This section covers Resonance. Orukeet also supports diarize=true with subscription task pricing and its own response format; see Orukeet diarization.

Add diarize=true to any file endpoint with model=oruk-resonance to find out who said what, and how. Speaker diarization estimates the turns; Resonance then transcribes and labels the audio in each estimated turn. Attribution can be wrong, especially when people interrupt or speak at the same time. Streaming speaker labels are available live on the realtime socket with diarize: true.

cURL
curl https://speech-api.oruk.ai/v1/audio/analysis \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "X-Request-ID: $(uuidgen)" \
  -F "file=@support-call.wav" \
  -F "model=oruk-resonance" \
  -F "diarize=true"
Diarized response excerpt · illustrative structure
{
  "id": "speech_9b2f...",
  "object": "speech.result",
  "task": "analysis",
  "model": "oruk-resonance",
  "text": "Hi, thanks for calling support, what seems to be the problem today? Honestly I'm pretty frustrated. ...",
  "duration": 19.29,
  "diarized": true,
  "speakers": ["speaker_0", "speaker_1"],   // in order of first appearance
  "segments": [                             // one segment per speaker turn
    {
      "id": 0, "speaker": "speaker_0", "start": 0.08, "end": 4.16,
      "text": "Hi, thanks for calling support, what seems to be the problem today?",
      "emotions": [{"label": "worried", "score": 0.89}],
      "styles":   [{"label": "casual", "score": 0.84}]
    },
    {
      "id": 1, "speaker": "speaker_1", "start": 4.84, "end": 10.40,
      "text": "Honestly I'm pretty frustrated. My order was supposed to arrive on Monday ...",
      "emotions": [{"label": "disgusted", "score": 0.83}],
      "styles":   [{"label": "skeptical", "score": 0.89}, {"label": "formal", "score": 0.85}]
    }
  ],
  "usage": {
    "audio_seconds": 19.287,
    "billable_seconds": 19.287
  }
}
diarize
true or false (default). Works with Resonance transcriptions, emotions, styles, affect, and analysis. Models without diarization support return 400 diarization_unsupported.
num_speakers
Optional requested count, 1–32. Set it when you know the count (a two-party call); leave it unset to let the diarizer decide. The provider may return fewer speakers when the recording does not contain enough evidence.
segments[].speaker
speaker_0, speaker_1, … Labels are stable within a response only; they do not identify people across recordings. Segment start / end are the bounds of the speaker's speech region. A speaker's run is split into a new segment at pauses longer than 1 s and at 20 s, so a monologue comes back as utterance-sized segments with the same speaker. The file result assigns one speaker at a time. It does not recover separate audio streams from overlapping voices. Short backchannels ("Yeah", "Mm-hmm") can be missed, merged into another turn, or assigned to the wrong speaker.
speakers
Top-level list of speaker labels in order of first appearance; diarized is true on every diarized response. Top-level emotions and styles still summarise the whole recording; use the segments for per-speaker affect.

Diarization is included in plan minutes. A minute of diarized audio uses one plan minute, with duration reported in usage. The speaker pass runs as a job before Resonance and adds latency that depends on the recording and capacity. Measure representative audio for your application. If the diarizer is unavailable the request fails with 503 diarization_upstream_failed rather than silently returning unlabelled segments; retry with the same X-Request-ID or resend without the flag. With diarize=true, audio is processed in an additional speaker-labeling pass. See the security page for processing and retention details.

08

Word and recording proficiency

POST /v1/audio/proficiency with model=oruk-proficiency-1 returns continuous pronunciation scores for each word and the recording, plus accuracy, fluency and prosody components. The new model is continuous-pilot-v2. Its scale is 0 for lower proficiency to 1 for higher proficiency. A score is a regression estimate against human ratings, not a probability, percentile or likelihood of sounding native.

The same response retains the original CEFR band, 0–5 CEFR score, fluency fields, metadata and validity check. Existing CEFR clients can keep their request and parsing code. CEFR is produced independently by fluency_cefr-v2-final; it is not a conversion of the new 0–1 score.

Send a recording

Use your server-side Bearer key and multipart file. Supply the words actually spoken in transcript, or omit it for automatic Resonance transcription. A supplied transcript skips ASR and becomes the alignment reference; a mismatched prompt can invalidate the pronunciation result. WAV, FLAC, MP3, M4A, OGG and WebM are supported, up to 30 MB and 600 seconds. Use at least five seconds of audible English and start with short prompted recordings. For the legacy CEFR estimate, 30–60 seconds of spontaneous speech is recommended.

cURL · replace the file and transcript with your recording
curl --fail-with-body https://speech-api.oruk.ai/v1/audio/proficiency \
  -H "Authorization: Bearer $ORUK_API_KEY" \
  -H "X-Request-ID: $(uuidgen)" \
  -F "file=@speaking-sample.wav" \
  -F "model=oruk-proficiency-1" \
  -F "transcript=The exact words spoken in your recording"
Live test response excerpt · rounded; first two of ten words shown
{
  "model": "oruk-proficiency-1",
  "text": "WHAT ABOUT THAT LETTER YOU WERE SPEAKING OF AT BREAKFAST",
  "duration": 5.34,
  "proficiency": {
    "model_version": "continuous-pilot-v2",
    "score": 0.8226,
    "status": "scored",
    "components": {
      "accuracy": 0.8302,
      "fluency": 0.8276,
      "prosody": 0.7953
    },
    "continuous_check": {
      "status": "scored",
      "reason": null
    },
    "continuous_metadata": {
      "speech_seconds": 5.02,
      "word_count": 10,
      "transcript_used": true
    },
    "legacy_model_version": "fluency_cefr-v2-final",
    "cefr": "B2",
    "cefr_score": 3.064,
    "confidence": 0.6069,
    "fluency": "intermediate",
    "fluency_score": 6.49,
    "speech_seconds": 4.4,
    "word_count": 10,
    "transcript_used": true,
    "words": [
      {
        "text": "what",
        "start_s": 0.0,
        "end_s": 0.64,
        "score": 0.9466,
        "status": "scored"
      },
      {
        "text": "about",
        "start_s": 0.8,
        "end_s": 1.2,
        "score": 0.9282,
        "status": "scored"
      }
    ]
  },
  "check": {
    "status": "scored",
    "billable": true,
    "reason": null
  }
}

Read the two scores separately

FieldMeaning
proficiency.scoreRecording pronunciation score, 0–1, or null when unavailable.
proficiency.words[]Word text, start_s/end_s in seconds, 0–1 score and scored/unscorable status. Keep null scores as null.
proficiency.componentsRecording accuracy, fluency and prosody on 0–1; null without a full recording score.
proficiency.continuous_checkPronunciation validity: scored, insufficient_audio or unavailable. No billing decision.
proficiency.continuous_metadataAligned speech_seconds, word_count and transcript_used (true only for a caller-supplied reference).
proficiency.cefr / cefr_scoreOriginal CEFR band A1–C2 and 0–5 band index. cefr_probs and confidence concern this band, not the pronunciation score.
proficiency.fluency / fluency_scoreOriginal low/intermediate/high category and 0–10 fluency score; distinct from components.fluency.
check / usageOriginal CEFR validity and authoritative usage. legacy_check repeats the same validity result.

proficiency.status is scored, partial_word_scores or unscorable. Show available word scores even when the recording score is null; never substitute zero. The original top-level proficiency metadata keeps its prior meaning: speech_seconds uses voice-activity detection, word_count uses legacy tokenization, and transcript_used includes automatic transcripts.

Validity and billing

The top-level check.status retains the old API contract: scored is valid; low_confidence is valid but the top CEFR band has less than 0.5 probability; insufficient_audio is invalid and free. Invalid means less than five seconds of audio, or fewer than four detected speech seconds and fewer than six transcript words. Numeric legacy estimates remain present for compatibility even on invalid checks; do not use those as grades. Collect a longer sample for a low-confidence CEFR result.

A valid CEFR check can coexist with partial or unavailable pronunciation scores and still consumes usage. Both models are covered by one request; there is no second charge for the new scorer. Subscription accounts use plan minutes; existing metered accounts retain their per-check terms. Read check.billable, usage and your account terms. Reused X-Request-ID values return 409. If the additive scorer fails, CEFR can still be returned with continuous_check.status set to unavailable.

How the model works

A CTC aligner locates reference words in the audio. The frozen Resonance encoder runs once per window; final-layer and layer-8 features are pooled over each word and combined with local acoustic measurements, expected pronunciation phones and word context. Two contextual heads predict word scores; separate continuous heads predict the recording score and its components. Training gives rare, weaker score bands more weight. It does not cap high scores or force a target output distribution.

The original acoustic and utterance-fluency model independently predicts CEFR using the same audio and transcript. Pronunciation windows are at most 30 seconds; longer recordings use word-count-weighted aggregates of complete windows. Unknown pronunciations, unreliable alignments or words crossing window boundaries can produce null or partial results. This long-form aggregation has not been separately validated against human ratings.

Evidence and limits

On the reused SpeechOcean762 benchmark (15,931 words from 125 Mandarin-L1 speakers), V2 reduced weak/intermediate word MAE from 0.3043 to 0.1883 and the mean MAE across five score bands from 0.2219 to 0.1671. Overall word MAE increased from 0.0770 to 0.1283. Recording MAE stayed 0.0687 across 2,496 recordings. These are human-rating regression errors, not transcription word error rates; the benchmark had already been inspected, so this is not independent validation.

This preview targets short English read speech. Validate on your own speakers, first languages and recording conditions before setting product thresholds. Accent classification, accentedness, phone-level diagnostics, grammar and vocabulary assessment are not outputs of the new model. Expected phones are inputs; do not derive accent strength as 1 − score. CEFR bands are estimates, not language certificates.

Python and Node.js examples · Complete response schema

09

Models

Resonance 2

oruk-resonance-2

Choose Resonance 2 for emotion and speaking-style scores without transcription. Six signed axes keep opposite labels mutually exclusive, alongside 19 independent scores.

Preview

Read the Resonance 2 API reference

Orukeet

oruk-orukeet

Choose Orukeet for fast English dictation and short recordings. Use native Oruk keys with a subscription, REST uploads, or audio streaming.

Preview

Explore Orukeet and its API

Resonance

oruk-resonance

Start here for prerecorded English speech. Use one request for transcript, emotion, and speaking style; add speaker labels for calls with multiple people.

Stable

Run the Resonance quickstart

Fourier

oruk-fourier

Evaluate Fourier when your workflow needs transcription and its native emotion output together. It runs those tasks in parallel and returns the shared speaking-style output.

Stable

Run the Fourier quickstart

Realtime

oruk-realtime

Choose Realtime for live PCM audio, transcript tokens in 32 locales, and phrase-level emotion events over WebSocket.

Preview

Connect a live stream

Proficiency 1

oruk-proficiency-1

Choose Proficiency 1 for word-level pronunciation feedback, recording scores and legacy CEFR estimates from English speech.

Preview

Run the proficiency example

10

Pricing

Resonance 2: Same price as Resonance 1. One audio minute uses one minute from your existing speech understanding allowance, with the same overage rate. No plan change or separate add-on.

Every plan includes all speech models. Hobby is $9/month, Builder $49/month, and Production $199/month. Resonance, Fourier, Realtime, and Proficiency share 250, 2,500, or 20,000 monthly audio minutes, respectively, including supported diarization. Each plan also includes a separate Orukeet allowance, starting at up to 20,000 transcription minutes on Hobby. Orukeet uses $0.00045/minute ($0.027/hour); optional tasks draw from its allowance at their published rates.

Resonance, Fourier, Realtime, and Proficiency subscription plans
SubscriptionMonthly priceSpeech understanding minutesAdditional minute
Hobby$9250$0.020
Builder$492,500$0.015
Production$19920,000$0.012

Standard self-serve plans start with a 7-day free trial: a card is required, $0 is charged today, and you can cancel before the trial ends. Promotional offers show their own terms at signup.

Both subscription allowances reset monthly, including on annual plans. Other-model audio uses a one-second minimum. Orukeet measures actual duration and rounds each request to one microdollar. Optional tasks use its allowance; excess usage is billed monthly within the shared spending cap. Usage cost fields show consumption, not the final invoice. Compare subscriptions and Enterprise options.

Resonance, Fourier, Realtime, and Proficiency use the shared audio-minute allowance, including speaker diarization and proficiency. Response excerpts show audio duration and omit reference cost fields. If a response contains rate_per_minute_usd or estimated_cost_usd, those fields are not your subscription invoice. Use your plan's included minutes and overage rate; the account usage page and invoice show actual billing.

Compare plan-minute usage for a combined analysis request and separate requests.

11

Audio

  • WAV, FLAC, MP3, M4A, OGG, and WebM
  • Mono or stereo input
  • Common sample rates accepted
  • English in the file API; 32 locales in realtime
  • Resonance 1 and Fourier: 30 MB / 60 minutes
  • Resonance 2: 30 MiB / 120 seconds
  • Proficiency 1: 30 MB / 600 seconds
  • Orukeet: 4 MiB maximum complete request
  • Orukeet: 60 second maximum duration

12

Errors and retries

Errors use one stable envelope and include the request ID. Retry 429, 500, 502, 503, and 504 with exponential backoff and jitter. Do not automatically retry other 4xx responses.

For Orukeet, HTTP 402 means an inactive plan or insufficient allowance and overage budget. Check your plan and limits before retrying. The plan errors below apply to subscription models; the Orukeet contract covers its REST and streaming errors.

402 plan_required · plan_inactive

The organization has no plan, or its plan is not being served (payment failed, cancelled). Start or fix the plan at /account/plan.

402 trial_minutes_exhausted · plan_minutes_exhausted · overage_cap_reached

The period's minutes are used. Trials stop at the trial minutes; paid plans continue into overage unless it is off or the monthly cap is reached.

409 duplicate_request_id

Use a new ID for a new inference. Failed requests release their ID.

Error response
{
  "error": {
    "type": "invalid_request_error",
    "code": "audio_decode_failed",
    "message": "Audio could not be decoded.",
    "param": "file",
    "request_id": "req_6fc1..."
  }
}

Ready to make a request?

Choose a subscription, then create your API key. Every plan includes Orukeet.