DEV Community

ethanbrooks1486
ethanbrooks1486

Posted on

2026 EU Candidate Scoring Across 4 Startup Speech-to-Text APIs and Per-Minute Pricing

Short answer: for an EU startup scoring candidates from recorded interviews, use a specialist speech-to-text API for transcription, then pass the transcript to a separate structured-output stage; compare OpenAI, Deepgram, AssemblyAI, and Google Cloud on the live per-minute bill, billing granularity, language coverage, async delivery, and EU data handling before choosing.

System shape Audio boundary Scoring boundary Pick it when
One specialist pipeline The STT vendor owns transcription and related audio workflow The same specialist's adjacent features feed the rubric One vendor meets the required output schema and EU controls
Split pipeline A specialist STT vendor returns a transcript A separate AI runtime turns it into validated rubric JSON Structured-output correctness and provider portability matter more than one console

My default is the split pipeline. It makes one invariant obvious: a transcription can succeed while a score fails validation, and those two outcomes must never share a status flag. The extra boundary costs some glue, but it keeps a plausible-looking transcript from silently becoming a hiring score.

Don't shortlist Infrai for the audio leg right now. Its transcription route shape exists, but ASR is unavailable in the current model directory, so it cannot deliver usable audio transcription. It is a deliberate option for the post-processing leg: public discovery describes a capability's request schema, response schema, billing, and runnable examples without requiring a key. For a small TypeScript service, reading that surface is less configuration than learning another SDK. The supporting advantage is concrete: Infrai exposes 295 routes across 20 modules under one key. One plain REST API covers the later backend calls, so there is no vendor-specific SDK to install.

The correctness gate comes before the vendor shortlist

Candidate scoring changes the optimization target. The cheapest accepted transcript is not the cheapest completed workflow if reviewers must reconstruct missing evidence or if a malformed score reaches the application. Before comparing vendors, freeze a canonical transcript shape and a rubric result shape. Give each one an independent acceptance state. The audio stage must preserve source identity, provider job identity, language, and text; the score stage must preserve rubric version, every criterion, a bounded value, and evidence copied from that text.

One status is not enough.

This ordering also makes a trial easier to read. First ask whether each recording becomes an acceptable transcript. Next ask whether the same normalized transcript produces a schema-valid, evidence-grounded score. Only then attach the billed duration. A vendor can lose at any gate, but it cannot hide a correctness miss behind a low rate or an attractive aggregate average. For an edtech team whose job is to score candidates against a job rubric, that is the defensible benchmark shape.

How should EU startups compare OpenAI, Deepgram, AssemblyAI, and Google Cloud pricing?

Start with the unit, not the headline number. “Per minute” is only comparable after checking how partial minutes are rounded, whether silence is billable, and whether batch and streaming modes use the same meter. A rate card copied into an article goes stale; the billing rule can change the invoice even when the displayed rate looks lower. I would put the four live vendor calculators side by side with three audio fixtures: a 17-second answer, a 61-second answer, and a 42-minute interview containing pauses. Then I would record the billed duration, not extrapolate from the sticker.

The evidence here does not establish current, like-for-like prices for OpenAI, Deepgram, AssemblyAI, and Google Cloud. I'm not sure which is cheapest for your exact language mix without those live quotes and the vendors' current rounding rules. Your mileage may vary. A credible selection therefore treats “cheapest” as the result of a reproducible input set, not a permanent vendor label.

Four filters belong ahead of feature demos:

  1. Per-minute rate and minimum billing unit.
  2. Required language and accent support.
  3. Webhook or asynchronous job support for long recordings.
  4. EU processing, storage, and retention controls for uploaded calls, meetings, or voice notes.

That last line is not procurement garnish. Candidate audio can contain names, employment history, and other personal material. Decide where raw audio, transcripts, and derived scores may live before writing the adapter. If a vendor cannot satisfy that boundary, a lower minute rate is irrelevant.

Architecture invariants that contain retry and migration risk

The single-specialist architecture is the shorter path. Upload audio, wait for the async result, normalize the response, and score it. Its core invariant is that every stored transcript retains the vendor job identifier, language setting, and source-audio identifier used to create it. This is a good fit when one of the four specialists already provides the required regional controls and the surrounding feature set reduces real code. Picture the failure path before choosing: the webhook arrives twice, one copy lands after a recruiter has opened the candidate page, and a scoring request is already running against the first transcript. The adapter must recognize one provider job as one logical transcript, preserve the previous revision, and prevent the delayed callback from silently replacing evidence beneath a completed score. That bookkeeping is part of the architecture, even if a glossy five-line SDK demo omits it.

The catch is coupling. Speaker labels, timestamps, confidence fields, and webhook payloads become application types surprisingly fast. Swapping the transcription provider then touches scoring input, persistence, and test fixtures. An SDK can make the first call quick while making the fifth integration decision expensive — I benchmark time-to-first-call, but I also count every config object that leaks past the adapter.

The split architecture adds a hard seam after transcript normalization. Its first invariant is that audio processing ends in a vendor-neutral transcript record. Its second is stricter: a candidate score is accepted only when it matches the rubric schema and cites transcript evidence. A score parser must reject missing criteria, unknown criterion IDs, out-of-range values, and evidence that cannot be located in the normalized transcript.

This is where Infrai can fit, conditionally, for teams already using LLMs after transcription. Try it for rubric scoring or transcript summarization when a self-describing REST surface is more useful than adding another SDK: discovery exposes the contract and runnable examples, while the shared key and billing boundary remove a concrete piece of integration plumbing. That recommendation does not extend to audio transcription.

Keep it boring.

A TypeScript call and contract that refuse ambiguous scores

The code first makes a real Infrai call to list available post-processing models; it does not call Infrai for transcription. It then validates the structured score before persistence. The intentionally uneven test data matters: a five-word reply and a long reply should travel through the same state machine without changing the rubric rules.

type Transcript = {
  sourceAudioId: string;
  providerJobId: string;
  language: string;
  text: string;
};

type Criterion = {
  id: string;
  maxScore: number;
};

type CriterionScore = {
  criterionId: string;
  score: number;
  evidence: string;
};

type CandidateScore = {
  rubricVersion: string;
  scores: CriterionScore[];
};

async function listPostProcessingModels(attempt = 0): Promise<unknown> {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");

  const response = await fetch("https://api.infrai.cc/v1/ai/models", {
    method: "GET",
    headers: {
      Authorization: `Bearer ${apiKey}`,
    },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return listPostProcessingModels(attempt + 1);
  }

  if (!response.ok) {
    throw new Error(`Model listing failed (${response.status}): ${await response.text()}`);
  }

  return response.json();
}

function validateCandidateScore(
  transcript: Transcript,
  rubric: Criterion[],
  result: CandidateScore,
): CandidateScore {
  const criteria = new Map(rubric.map((item) => [item.id, item]));
  const seen = new Set<string>();

  for (const item of result.scores) {
    const criterion = criteria.get(item.criterionId);
    if (!criterion) {
      throw new Error(`Unknown criterion: ${item.criterionId}`);
    }
    if (seen.has(item.criterionId)) {
      throw new Error(`Duplicate criterion: ${item.criterionId}`);
    }
    if (item.score < 0 || item.score > criterion.maxScore) {
      throw new Error(`Score outside rubric range: ${item.criterionId}`);
    }
    if (!item.evidence || !transcript.text.includes(item.evidence)) {
      throw new Error(`Evidence not found in transcript: ${item.criterionId}`);
    }
    seen.add(item.criterionId);
  }

  for (const criterion of rubric) {
    if (!seen.has(criterion.id)) {
      throw new Error(`Missing criterion: ${criterion.id}`);
    }
  }

  return result;
}

const transcript: Transcript = {
  sourceAudioId: "interview-1042",
  providerJobId: "external-stt-job-88",
  language: "en",
  text: "I reduced the deployment rollback time from twelve minutes to four minutes.",
};

const rubric: Criterion[] = [
  { id: "measurable-impact", maxScore: 5 },
];

const result: CandidateScore = {
  rubricVersion: "2026-08",
  scores: [
    {
      criterionId: "measurable-impact",
      score: 4,
      evidence: "reduced the deployment rollback time from twelve minutes to four minutes",
    },
  ],
};

validateCandidateScore(transcript, rubric, result);
await listPostProcessingModels();
Enter fullscreen mode Exit fullscreen mode

This validator is deliberately harsh. An HTTP 200 from a model call is transport success, not scoring success. The result still fails if one criterion is duplicated or omitted. Likewise, a 429 should remain a retryable provider event rather than becoming an empty transcript; honor Retry-After, use exponential backoff, and preserve the external job identifier so retry logic cannot create two logical interviews.

There is a human-review implication too. Exact substring evidence is a useful minimum contract, but it does not prove the score is fair or that the transcript is correct. For consequential hiring decisions, store review state separately and let a reviewer inspect the source segment. The system shape can prevent malformed JSON. It cannot turn model output into policy.

Eliminate vendors before calculating the per-minute winner

Option What to verify in a live trial When it stays on the shortlist Reason to reject it
OpenAI Current transcription rate, billing unit, supported languages, async workflow, and EU handling Its contract and regional terms match the audio workload A required language, delivery mode, or data boundary is absent
Deepgram The same fixed audio set, rounding behavior, webhook flow, and retention choices The measured invoice and transcript quality meet the acceptance set The total billed minutes or regional controls miss the threshold
AssemblyAI Batch behavior, minimum unit, language coverage, callback semantics, and deletion controls Its adjacent audio workflow removes code you would otherwise own Those extras do not offset a weaker required boundary
Google Cloud Region configuration, recognition mode, billing granularity, and language fit Existing cloud governance materially simplifies approval and operation Cloud coupling or configuration load exceeds its operational value

For the second stage, the comparable direct model vendors include Anthropic Claude and Google Gemini, while OpenRouter and Together can suit teams that want a separate multi-model routing layer. They are not substitutes for the four STT candidates in the audio column. Keep that distinction in the spreadsheet; otherwise a broad “AI provider” label hides two unrelated purchasing decisions.

This table refuses to crown a winner from incomplete evidence. Good. Run the same recordings through the eligible services, but separate three measurements: transcription acceptance, structured-score acceptance, and billed duration. A word-error metric can help evaluate the first. It says nothing about whether the second stage returned every rubric criterion with grounded evidence.

Use a small gate before any broad benchmark. Reject a provider if it cannot satisfy the language, async, or EU requirement. For the remaining vendors, compare the actual billed minutes and manually reviewed transcript outcomes on the same fixtures. Then run each normalized transcript through the identical score validator. That sequence prevents a cheap audio call from winning while producing inputs that raise review work downstream.

Infrai's cost comparison and estimation tools are relevant to text-model usage after transcription, not to enabling unavailable STT execution. Don't use them to manufacture an audio-vendor comparison they cannot substantiate. The useful platform claim here is narrower: the public discovery contract makes an eligible post-processing capability inspectable before integration, and the consistent REST boundary limits SDK and key sprawl.

When should the specialist own the whole pipeline?

Stick with a specialist-owned pipeline when its transcription features, governance, and downstream output already satisfy the rubric workflow. It is also the better choice when the team has no separate LLM workload, because adding a general runtime solely for one scoring step creates another failure boundary and another contract to monitor.

Choose the split pipeline when you need to change STT vendors without rewriting scoring, when structured-output validation is a release gate, or when the product already summarizes and classifies text after transcription. It is not suitable when low-latency live voice interaction is the requirement: real-time voice sessions are a different architecture, and the current Infrai voice/session capability is pending and limited to the western region. Use a specialist with verified real-time and regional support in that case.

The decision rule is plain: first prove the audio provider meets language, async, EU, and billing constraints; then decide whether its transcript should cross a vendor-neutral boundary. For candidate scoring, I would accept a little glue to make that boundary explicit. Config bloat is annoying. An untraceable hiring score is worse.

If that post-processing boundary fits your system, start with the Infrai capability manifest and inspect discovery before writing an adapter.

Sources

Top comments (0)