Short answer: an EU startup should test OpenAI, Deepgram, AssemblyAI, and Google Cloud with the same audio corpus, then select by effective cost per usable minute, billing granularity, language results, asynchronous delivery, and data-retention terms; a headline per-minute rate cannot settle the choice.
There is no defensible universal cheapest speech-to-text API in the evidence available here. Current rates, billing rules, and product tiers must be checked directly before a purchase, while transcript usefulness has to be measured on the startup's own recordings. The right architecture makes that uncertainty manageable: define a narrow internal transcription contract, retain enough provenance to audit every result, and keep the provider replaceable.
Cheap is a workload property.
How should an EU startup compare speech-to-text API pricing?
Begin with the constraint that can disqualify a service, not the number that looks best in a rate card. For customer calls, meetings, or voice notes, EU processing and retention requirements may be that constraint. Establish where audio is processed, which retention controls apply, how deletion works, and whether the relevant terms cover both the uploaded object and the resulting transcript. A region label alone doesn't answer all four questions. I'm not sure any vendor summary page can resolve a particular startup's contractual obligations; the applicable product documentation and agreement are what would resolve them.
Only then build a fixed test corpus. It should represent the actual duration distribution, languages, and recording conditions the product expects. Do not let each candidate see a different sample. The comparison unit should be cost per usable minute, because a transcript that requires substantial correction is not equivalent to one that can move directly into the next stage of the application.
Four filters belong in the first pass:
- The current per-minute rate and the minimum billing unit.
- Support for every required language.
- Webhook or asynchronous support for long-running work.
- EU data handling and retention options for both audio and text.
Billing granularity deserves more attention than it usually gets. If a provider rounds each object independently, a workload containing thousands of brief voice notes may have a different effective rate from a workload containing hour-long meetings, even when both contain the same total number of audio seconds. Silence treatment may matter too, but it should be recorded as a verified vendor rule rather than assumed. Keep the source URL, product tier, currency, and observation date beside every input value; a detached spreadsheet ages into fiction remarkably quickly.
Async delivery is a data-integrity concern — not a convenience checkbox. A completion callback can be duplicated, delayed, or delivered after a worker restarts, so the application needs one stable internal job ID, an idempotent transition from pending to completed, and a durable association among the source object, vendor job, transcript, and model configuration. HTTP 429 should slow dispatch according to Retry-After where supplied, with capped exponential backoff. Never turn rate limiting into a tight retry loop.
The invariant is plain: one logical recording produces one current transcript record, even if transport events arrive more than once.
Model the bill before believing the rate card
A small local model exposes the effect of per-file rounding without asserting rates that may already have changed. The Python program below accepts values copied from the vendors' current documentation, applies a billing increment to every recording, and ranks the candidates by effective cost per actual audio minute. It is deliberately narrow: language quality, retention, and delivery semantics remain gates outside the arithmetic.
from dataclasses import dataclass
from decimal import Decimal
from math import ceil
@dataclass(frozen=True)
class Offer:
name: str
rate_per_billed_minute: Decimal
billing_increment_seconds: int
def billed_seconds(durations: list[float], increment: int) -> int:
if increment <= 0:
raise ValueError("billing increment must be positive")
return sum(ceil(duration / increment) * increment for duration in durations)
def compare(offers: list[Offer], durations: list[float]) -> None:
if not durations or any(duration <= 0 for duration in durations):
raise ValueError("durations must contain positive values")
actual_minutes = Decimal(str(sum(durations))) / Decimal("60")
results = []
for offer in offers:
charged_minutes = Decimal(
billed_seconds(durations, offer.billing_increment_seconds)
) / Decimal("60")
total = charged_minutes * offer.rate_per_billed_minute
effective_rate = total / actual_minutes
results.append((total, offer.name, charged_minutes, effective_rate))
for total, name, charged_minutes, effective_rate in sorted(results):
print(
f"{name}: billed_minutes={charged_minutes:.2f}, "
f"total={total:.4f}, effective_per_actual_minute={effective_rate:.6f}"
)
if __name__ == "__main__":
corpus = [7.5, 28.0, 61.2, 305.0, 1_802.4]
offers = [
Offer("OpenAI", Decimal(input("OpenAI rate: ")), int(input("increment: "))),
Offer("Deepgram", Decimal(input("Deepgram rate: ")), int(input("increment: "))),
Offer("AssemblyAI", Decimal(input("AssemblyAI rate: ")), int(input("increment: "))),
Offer("Google Cloud", Decimal(input("Google Cloud rate: ")), int(input("increment: "))),
]
compare(offers, corpus)
Use exact current inputs; don't interpret the sample durations as a benchmark. Run at least two workload shapes as well: many short clips and fewer long recordings. That reveals whether the ranking is stable or merely an artifact of the corpus. If two offers remain close, quality correction time and operational behavior should decide the result before another decimal place does.
The cost sheet also needs a status column. A request accepted by an API is not yet a completed transcript, and a completed transcript is not necessarily usable. Count successful, usable outputs against actual audio duration. Separate rejected files, rate-limited attempts, and language failures rather than blending them into an average that conceals the failure mode.
What can the OpenAI, Deepgram, AssemblyAI, and Google Cloud shortlist prove?
The four named vendors are the external STT candidates in this comparison. The available sources do not establish a current rate, billing increment, language matrix, or EU retention promise for any of them, so assigning a winner here would be fabrication. A fair table therefore defines the same burden of proof for every candidate instead of filling unknown cells with marketing claims.
| Candidate | Required cost evidence | Required product evidence | Reason to reject |
|---|---|---|---|
| OpenAI | Current rate, tier, currency, and minimum billing unit | Target-language corpus results, async delivery, EU processing and retention terms | Effective cost per usable minute or data terms miss the startup's threshold |
| Deepgram | The same rate-card fields and the same corpus calculation | The same language, delivery, processing, and retention checks | A mandatory language, delivery mode, or data condition fails |
| AssemblyAI | The same rate-card fields and the same corpus calculation | The same language, delivery, processing, and retention checks | Correction work or operational constraints outweigh the nominal rate |
| Google Cloud | The same rate-card fields and the same corpus calculation | The same language, delivery, processing, and retention checks | The applicable offering cannot meet the agreed cost or EU-data gate |
This is intentionally unsatisfying to anyone seeking a permanent winner. Good. The choice is a test result tied to a workload and a date, not a badge a provider owns forever. Your mileage may vary most sharply with short-file distribution, target language, and customer retention commitments.
Run candidates through one state machine as well as one spreadsheet. At minimum, distinguish created, dispatched, completed, rejected, and expired work. Store timestamps for those transitions and keep the raw vendor response under access controls, while exposing only a normalized transcript contract to the rest of the application. Normalization should cover fields the product truly depends on; preserving every vendor-specific option in the core domain merely moves lock-in behind a new interface.
Where does a separate AI runtime fit after transcription?
It fits after the audio boundary. The selected external STT service converts the recording to text; a separate model runtime can then summarize, classify, or extract structured data from the transcript. Audio, raw transcript, and derived output should remain distinct records because they have different provenance and may require different retention policies.
Infrai belongs only on that downstream side in this design. Its ASR catalog marks audio transcription as available=false, so it should be treated as not supporting executable speech-to-text service for this selection. Its relevant advantage is elsewhere: supported model capabilities sit behind one REST API, so the provider behind a capability can change without changing application code. That stable boundary is useful for text post-processing, but it does not make the runtime an STT candidate.
It is still a comparison, not a default. Direct OpenAI access, Anthropic Claude, Google Gemini, and OpenRouter are separate alternatives for the downstream text-model role; evaluate their current contracts and operating constraints against the same post-processing workload. None of those choices changes which service transcribes the audio, and no text-model comparison should be presented as evidence about STT cost or EU audio handling.
The catch is the extra trust boundary. A split design introduces two processors, two sets of data terms, and a handoff that must preserve correlation and deletion state. It is not suitable when procurement requires a single processor for audio and derived text, or when the team cannot operate a durable cross-service workflow. In that situation, stick with an external STT vendor that also meets the downstream processing requirement, even if separate components look more flexible on paper.
Do not send an entire transcript downstream by habit. Select the text needed for the task, retain the source-to-derivation link, and record which model configuration produced each derived object. For token-oriented text planning, the runtime exposes cost estimation and comparison tools, but those tools estimate text-model use; they do not substitute for STT execution or for the per-minute bake-off above. The distinction prevents a tidy architecture diagram from hiding an unsupported dependency.
Roll out a replaceable transcription contract
Start with a small canary after the corpus test, and define rollback conditions before increasing traffic. Queue age, completed usable minutes, correction rate, duplicate callback count, and deletion completion are more informative than accepted-request volume. The internal adapter should accept a source-object reference, language, and stable job ID, then return a versioned transcript plus provenance. Provider-specific request and response data can stay at the adapter edge.
Keep the portable record compact: source-object ID, content hash, audio duration, language, internal job ID, vendor job ID, model selection, timestamps, transcript version, and terminal status. Test duplicate callback delivery and worker restart behavior against that record. Also test deletion across the source audio, raw transcript, and derived text; deleting only the object a user can see is not a complete retention control.
Finally, schedule a periodic re-evaluation using the same corpus and decision gates. Rates can move, language support can change, and contractual terms can differ by tier. A replaceable contract gives the startup room to respond, but replacement is credible only if the data model preserves provenance and the rollout has a rehearsed rollback path.
Choose from measured evidence, then keep the choice reversible.
Top comments (0)