# A first native Oruk request for a Hume migration

These Python and TypeScript examples make one native file-analysis request. They do not emulate Hume's schema, import Hume credentials or prove that your application can switch unchanged. Evaluate the same permitted recording and the decisions your application makes from it.

Use the bounded [batch runner](https://oruk.ai/examples/hume-batch-replay.py) and [operator guide](https://oruk.ai/examples/hume-migration-tools.md) for repeated files, durable progress and unresolved outcomes. The small examples below have no persistent execution ledger. **Run an example once, save its output and request ID, and reconcile any uncertain outcome before another attempt.**

## Choose the route

| Model | This example returns | Requirements and limits |
| --- | --- | --- |
| `oruk-resonance` | English transcript, timed segments and native emotion/style scores | File API permits 0.1–3,600 seconds and 30 MiB. These examples deliberately use a short mono 16 kHz WAV no larger than 4 MiB. Diarization is not enabled. |
| `oruk-spectra-2` | Transcript plus 15 emotion and 16 style scores for the whole clip; no word times, segments or speaker labels | Mono 16 kHz WAV, 45 ms–60 seconds, at most 4 MiB. Supports 25 transcription languages; this is not evidence of equal expression accuracy across those languages. |

Both use `POST https://speech-api.oruk.ai/v1/audio/analysis`. Set `ORUK_MODEL` explicitly to one of these two identifiers. Resonance 2 uses a different dedicated route and separate access approval; an active trial or plan does not by itself grant Resonance 2 access. Realtime uses phrase events on `/v1/realtime`. Neither is covered by these examples.

## Install a verified SDK

Python 3.10+:

```sh
python3 -m venv .venv
.venv/bin/python -m pip install https://oruk.ai/sdk/oruk-0.2.14-py3-none-any.whl
```

TypeScript with Node.js 22.23+:

```sh
npm install --save-exact https://oruk.ai/sdk/oruk-ai-sdk-0.2.14.tgz
```

These URLs pin the current 0.2.14 website distributions. Python 0.2.14 was subsequently published and byte-verified on PyPI on October 3, 2026; `python -m pip install oruk==0.2.14` installs the reviewed Python release. npm latest remains 0.2.10, so use the explicit 0.2.14 TypeScript mirror URL. Reviewed archive SHA-256 values:

- Python: `4b501aff199b452fffc2ed8b1586559efc7f2e56c1ec985967178e96ee006801`
- TypeScript: `bd344011e7ae0b811198bed567d74dd06c216f341d1d3c65a5d870a9d00fd5dc`

Use your secret manager to supply `ORUK_API_KEY` server-side. Never paste it into browser code, a URL or a support message. Select the model and create **one** request ID for the intended request:

```sh
export ORUK_MODEL=oruk-resonance
export ORUK_REQUEST_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
```

Keep that ID with the original audio hash, selected model and result. Generating a fresh ID after a lost response can repeat inference and charges. A completed generic or Spectra request ID does not provide saved-response retrieval.

Save the permitted original recording as `sample.wav`. For an initial non-customer recording, download the supplied [English conversation sample](https://oruk.ai/samples/conversations/03-scooter-charge.wav) to that name. Its SHA-256 is `ce2ee7f7fecea936816b46ee0de8ff718dacf02d94beffc4c83bdb7b80511e0c`. Verify the download before using it; the operator guide includes a corresponding manifest.

## Python

Save this as `hume-native.py` and run `.venv/bin/python hume-native.py`. Standard output contains the full result, including transcript and scores; keep it in your approved private storage.

<!-- executable-recipe: python -->
```python
import json
import math
import os
from pathlib import Path
import re
import sys
from uuid import UUID

from oruk import Oruk, OrukAPIError

NATIVE_LABELS = {
    "emotions": set("happy excited hopeful sad worried angry frustrated disappointed scared disgusted surprised embarrassed proud relieved neutral".split()),
    "styles": set("energetic passionate irritated warm playful sarcastic deadpan hesitant confident sincere skeptical tired formal casual impatient distracted".split()),
}

def finite(value):
    return type(value) in (int, float) and math.isfinite(value)

def valid_scores(rows, labels, complete=False):
    if not isinstance(rows, list) or not all(
        isinstance(row, dict) and isinstance(row.get("label"), str)
        and row["label"] in labels and finite(row.get("score"))
        and 0 <= row["score"] <= 1 for row in rows
    ):
        return False
    selected = {row["label"] for row in rows}
    return len(selected) == len(rows) and (not complete or selected == labels)

def valid_result(result, model):
    if not isinstance(result, dict) or result.get("object") != "speech.result" or result.get("model") != model or result.get("task") != "analysis":
        return False
    if not isinstance(result.get("id"), str) or not result["id"] or not isinstance(result.get("text"), str):
        return False
    if not finite(result.get("duration")) or result["duration"] <= 0:
        return False
    for field, labels in NATIVE_LABELS.items():
        if not valid_scores(result.get(field), labels, complete=model == "oruk-spectra-2"):
            return False
    if model == "oruk-resonance":
        segments = result.get("segments")
        if not isinstance(segments, list) or not segments:
            return False
        previous_end = 0.0
        for index, segment in enumerate(segments):
            if not isinstance(segment, dict) or type(segment.get("id")) is not int or segment["id"] != index:
                return False
            start, end = segment.get("start"), segment.get("end")
            if not finite(start) or not finite(end) or start < 0 or start + 1e-6 < previous_end or end <= start or end > result["duration"] + 1e-6:
                return False
            if not isinstance(segment.get("text"), str) or not all(
                valid_scores(segment.get(field), labels) for field, labels in NATIVE_LABELS.items()
            ):
                return False
            previous_end = end
    return True

def safe_error_code(value, key):
    return value if isinstance(value, str) and value != key and re.fullmatch(r"[a-z][a-z0-9_]{0,63}", value) else "unrecognized_error_code"

def main():
    key = os.environ.get("ORUK_API_KEY", "")
    model = os.environ.get("ORUK_MODEL", "")
    try:
        request_id = str(UUID(os.environ.get("ORUK_REQUEST_ID", "")))
    except ValueError:
        raise ValueError("Set ORUK_REQUEST_ID to the UUID for this intended request.") from None
    if not key or model not in {"oruk-resonance", "oruk-spectra-2"}:
        raise ValueError("Set ORUK_API_KEY and choose a supported ORUK_MODEL.")
    audio = Path("sample.wav")
    if not audio.is_file() or not 44 < audio.stat().st_size <= 4 * 1024 * 1024:
        raise ValueError("Provide the permitted short mono 16 kHz sample.wav, at most 4 MiB.")
    try:
        with Oruk(api_key=key, base_url="https://speech-api.oruk.ai", max_retries=0, compress_audio=False) as client:
            result = client.analyze(audio, model=model, request_id=request_id)
        if not valid_result(result, model):
            raise ValueError("Invalid native result.")
        print(json.dumps({"request_id": request_id, "result": result}, ensure_ascii=False, allow_nan=False))
        return 0
    except OrukAPIError as error:
        print(json.dumps({"status": error.status, "code": safe_error_code(error.code, key), "request_id": request_id,
                          "action": "Reconcile this attempt before any retry."}), file=sys.stderr)
    except Exception:
        print(json.dumps({"request_id": request_id, "action": "Outcome unknown. Check Usage before any retry."}), file=sys.stderr)
    return 1

try:
    sys.exit(main())
except ValueError as error:
    print(str(error), file=sys.stderr)
    sys.exit(2)
```

## TypeScript

Save this as `hume-native.mts` and run `node --experimental-strip-types hume-native.mts`. It calls the same endpoint with the original file bytes and no automatic retries.

<!-- executable-recipe: typescript -->
```typescript
import { readFile, stat } from 'node:fs/promises'
import { Oruk, OrukApiError } from '@oruk-ai/sdk'

const nativeLabels = {
  emotions: 'happy excited hopeful sad worried angry frustrated disappointed scared disgusted surprised embarrassed proud relieved neutral'.split(' '),
  styles: 'energetic passionate irritated warm playful sarcastic deadpan hesitant confident sincere skeptical tired formal casual impatient distracted'.split(' '),
}
const finite = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value)
function validScores(value: unknown, labels: string[], complete = false): boolean {
  if (!Array.isArray(value) || !value.every(row => row && typeof row.label === 'string' &&
      labels.includes(row.label) && finite(row.score) && row.score >= 0 && row.score <= 1)) return false
  const selected = new Set(value.map(row => row.label))
  return selected.size === value.length && (!complete || selected.size === labels.length)
}
function validResult(value: unknown, model: string): boolean {
  if (!value || typeof value !== 'object') return false
  const result = value as Record<string, unknown>
  if (result.object !== 'speech.result' || result.model !== model || result.task !== 'analysis' ||
      typeof result.id !== 'string' || !result.id || typeof result.text !== 'string' ||
      !finite(result.duration) || result.duration <= 0) return false
  if (!Object.entries(nativeLabels).every(([field, labels]) =>
    validScores(result[field], labels, model === 'oruk-spectra-2'))) return false
  if (model === 'oruk-resonance') {
    if (!Array.isArray(result.segments) || !result.segments.length) return false
    let previousEnd = 0
    for (const [index, segment] of result.segments.entries()) {
      if (!segment || typeof segment !== 'object' || !Number.isInteger(segment.id) || segment.id !== index ||
          !finite(segment.start) || !finite(segment.end) || segment.start < 0 ||
          segment.start + 1e-6 < previousEnd || segment.end <= segment.start ||
          segment.end > result.duration + 1e-6 || typeof segment.text !== 'string' ||
          !Object.entries(nativeLabels).every(([field, labels]) => validScores(segment[field], labels))) return false
      previousEnd = segment.end
    }
  }
  return true
}
const safeErrorCode = (value: unknown, key: string) => typeof value === 'string' && value !== key &&
  /^[a-z][a-z0-9_]{0,63}$/.test(value) ? value : 'unrecognized_error_code'

async function main() {
  const apiKey = process.env.ORUK_API_KEY
  const model = process.env.ORUK_MODEL
  const requestId = process.env.ORUK_REQUEST_ID?.toLowerCase()
  if (!apiKey || (model !== 'oruk-resonance' && model !== 'oruk-spectra-2')) {
    throw new Error('Set ORUK_API_KEY and choose a supported ORUK_MODEL.')
  }
  if (!requestId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(requestId)) {
    throw new Error('Set ORUK_REQUEST_ID to the UUID for this intended request.')
  }
  const file = await stat('sample.wav')
  if (!file.isFile() || file.size <= 44 || file.size > 4 * 1024 * 1024) {
    throw new Error('Provide the permitted short mono 16 kHz sample.wav, at most 4 MiB.')
  }
  const bytes = await readFile('sample.wav')
  const client = new Oruk({ apiKey, baseUrl: 'https://speech-api.oruk.ai', maxRetries: 0 })
  try {
    const result = await client.analyze({ file: new Blob([new Uint8Array(bytes)], { type: 'audio/wav' }), filename: 'sample.wav', model, requestId })
    if (!validResult(result, model)) throw new Error('Invalid native result.')
    console.log(JSON.stringify({ request_id: requestId, result }))
  } catch (error) {
    console.error(JSON.stringify({ request_id: requestId,
      ...(error instanceof OrukApiError ? { status: error.status, code: safeErrorCode(error.code, apiKey) } : {}),
      action: error instanceof OrukApiError ? 'Reconcile this attempt in Usage before any retry.' :
        'Outcome unknown. Check Usage before any retry.' }))
    process.exitCode = 1
  }
}

main().catch(() => {
  console.error('Setup failed. Check the required environment and permitted sample.wav; no credentials are printed.')
  process.exitCode = 2
})
```

## Interpret and reconcile

Native label names, counts, time scope and calibration differ from Hume. Clip scores are independent values in [0,1], not a probability distribution. Do not copy Hume thresholds, fill absent labels with zero or infer a person's inner state. Keep the original recording identity and evaluate the customer's actual task on held-out audio.

The examples check the returned model, task and model-specific result before reporting success. Resonance returns selected labels; its style arrays may be empty. Its timed segments are validated separately. Spectra-2 must return all 15 emotion and 16 style scores. A malformed or incomplete 2xx response is an unknown outcome, even if the SDK returns an empty object. Keep your original request ID; the scripts never replay the request to repair a result.

HTTP diagnostics retain the status and a bounded error code, without printing the server's free-text message. Unknown or malformed codes become `unrecognized_error_code`; reconcile those with support using your request ID.

One audio minute consumes one shared speech-understanding plan minute, with a one-second minimum. Trial allowance, normal extra-usage rates and spending caps apply. `usage` reference-cost fields are not the subscription invoice. Check [current plans](https://oruk.ai/pricing) and the account's Usage record; provider costs for an EVI stack are separate.

| Result | Next step |
| --- | --- |
| 400 / 413 / 422 | Fix the input or selected contract after inspecting the rejection. Preserve the failed request record. |
| 401 / 403 | Check key validity, organization/model approval and current permissions; paying for a plan does not repair all access failures. |
| 402 | Resolve the actual trial/plan/credit limit shown by the account. Do not rotate keys to evade it. |
| 409 | Preserve the request ID, exact bytes and selected model. Pending, completed and conflicting IDs have different meanings; no saved result is promised on these routes. |
| 429 | Respect `Retry-After` and reduce concurrency. Spectra `upload_busy` is pre-dispatch; `model_busy` closes the rejected attempt. Inspect the exact error before deciding whether another attempt is valid. |
| Network error, 5xx or invalid result | Completion and billing may be unknown. Check Usage/support with the same ID. Do not generate a fresh ID or automatically rerun the script. |

These examples intentionally disable SDK retries. They do not grant access, override quotas, export Hume data, validate customer outcomes or implement an EVI voice agent. Continue with the [migration plan](https://oruk.ai/hume-migration) and [export/cutover guide](https://oruk.ai/guides/hume-evi-migration).
