Short answer: the cheapest speech-to-text API for an EU startup is the candidate that passes your invoice-field accuracy gate at the lowest normalized cost for your actual audio. Don't choose from a public per-minute headline alone. Put every provider behind one small TypeScript interface, replay the same supplier recordings, reject transcripts that fail schema checks, and compare the cost of accepted results.
For an edtech SaaS, this is a weekly-shipping decision, not a research project. The concrete job here is awkward but real: a school administrator reads fields from a supplier invoice into a voice note, and the application turns that audio into structured invoice data. A cheap transcript that changes a VAT identifier, currency, date, or total creates review work. That failed result has no useful price per minute.
I would time-box the first pass to an afternoon. OpenAI, Deepgram, AssemblyAI, and Google Cloud can all enter the candidate set named in the question, but their names don't determine the winner. A current quote, the exact billing unit, EU processing requirements, and a fixed acceptance corpus do. I'm not sure which one will win for your microphones and supplier vocabulary until those inputs are measured; anyone certain without them is guessing.
What should an EU startup compare in speech-to-text API per-minute pricing?
Start with the denominator. A provider may quote audio duration, rounded units, model-specific units, or another billing basis. Do not translate those into a common number by intuition. Record each candidate's current commercial terms as data, then calculate one metric: cost per accepted invoice. This keeps a changing quote outside application code and makes the decision reproducible.
The acceptance gate matters more than the spreadsheet. Build a small corpus that represents the input you will actually receive: quiet office recordings, phone microphones, supplier names, invoice numbers, dates, decimal totals, currency codes, and VAT identifiers. Keep the source invoice beside each recording so expected fields are explicit. Twenty carefully chosen clips can expose more decision-relevant failures than hundreds of generic sentences, though that count is a starting point rather than a universal benchmark. Your mileage may vary.
Use a table with blanks, not invented precision:
| Candidate | Current billing unit | EU requirement met? | Accepted clips | Quoted corpus cost | Cost per accepted invoice |
|---|---|---|---|---|---|
| Candidate A | Verify from current terms | Yes / No | Test result | Test result | Derived |
| Candidate B | Verify from current terms | Yes / No | Test result | Test result | Derived |
| Candidate C | Verify from current terms | Yes / No | Test result | Test result | Derived |
| Candidate D | Verify from current terms | Yes / No | Test result | Test result | Derived |
That Yes / No column is deliberately strict. "Available in Europe" and "meets this application's EU data-handling requirement" are different questions, and a candidate's name establishes neither. Write down the requirement your counsel or customer contract gives you, request evidence, and make a failed requirement disqualifying rather than assigning it a soft score.
There is another trap. Audio length is only the input to the bill; accepted structured fields are the output that earns revenue. If one transcript needs a person to reopen the invoice while another passes automatically, their nominal per-minute numbers are not comparable. Count human review time separately, but don't manufacture an hourly saving claim. Your own support and operations data should supply that value.
The smallest portable implementation
The adapter needs fewer concepts than most SDK examples suggest. Accept bytes plus a MIME type. Return transcript text and provider usage in the provider's native unit. Keep invoice extraction and validation downstream, because coupling those steps to one transcription response makes the exit test much harder.
type AudioInput = {
bytes: Uint8Array;
mimeType: string;
};
type Transcript = {
text: string;
billedQuantity: number;
billedUnit: string;
};
interface SpeechToText {
transcribe(input: AudioInput): Promise<Transcript>;
}
type InvoiceFields = {
supplierName: string;
invoiceNumber: string;
invoiceDate: string;
currency: string;
total: string;
vatId?: string;
};
type ValidationResult =
| { accepted: true; fields: InvoiceFields }
| { accepted: false; reasons: string[] };
async function runInvoiceClip(
engine: SpeechToText,
input: AudioInput,
extract: (text: string) => Promise<InvoiceFields>,
validate: (fields: InvoiceFields) => ValidationResult,
): Promise<{ transcript: Transcript; result: ValidationResult }> {
const transcript = await engine.transcribe(input);
const fields = await extract(transcript.text);
return { transcript, result: validate(fields) };
}
This boundary is boring. Good. The one-person SaaS version of leverage is outsourcing undifferentiated transcription while retaining the tiny interface that preserves a future switch. Each commercial integration can map its own authenticated request and response into this contract. Application code never imports a provider-specific type.
The extraction function also stays separate for a less obvious reason: a transcript can be linguistically plausible and financially wrong. Validation should compare fields against business rules that do not depend on the provider. Require a parseable date, an allowed currency, a decimal total, and the identifiers your workflow needs. Return a local 422 from your application when submitted fields fail that contract. That is your API behavior, not a claim about any transcription service.
Do not silently retry every rejected transcript. A retry can create another billed operation while returning the same unacceptable text. Mark the reason, preserve the candidate and corpus-item identifiers, and let the test runner decide whether a retry belongs in the experiment. Production retry policy should distinguish transport failures from a completed transcript that fails invoice validation. Mixing them hides both quality and spend.
A compact runner can produce the comparison rows without knowing any public list price:
type Quote = {
costForUsage: (quantity: number, unit: string) => number;
};
type Trial = {
accepted: boolean;
billedQuantity: number;
billedUnit: string;
};
function summarize(trials: Trial[], quote: Quote) {
const accepted = trials.filter((trial) => trial.accepted).length;
const corpusCost = trials.reduce(
(sum, trial) =>
sum + quote.costForUsage(trial.billedQuantity, trial.billedUnit),
0,
);
return {
accepted,
corpusCost,
costPerAcceptedInvoice:
accepted === 0 ? null : corpusCost / accepted,
};
}
Notice what is absent: hard-coded vendor prices. Quotes change, contracts differ, and the task materials provide no verified current price figures. Keeping quote data in the test fixture prevents an old article or stale constant from becoming a procurement decision. It also makes rounding visible. Feed costForUsage the exact unit reported by the integration rather than assuming all quantities mean minutes.
The build log gate that changed the choice
The original question asks for the cheapest API, but the invoice scenario changes what "cheap" means. The gating artifact should be a versioned corpus plus expected fields, not a feature checklist. Give every recording a stable ID. Store the expected structured object. Run all candidates against the same immutable bytes, then save the raw transcript, extracted fields, validation reasons, usage quantity, quote version, and run timestamp.
One long example is worth spelling out. Imagine the source document contains supplier name, invoice number, invoice date, currency, total, and an optional VAT ID. The administrator reads those values aloud. The transcript then flows through extraction, and the validator checks the resulting object. A candidate gets one accepted result only when every required field matches the labeled object under your declared normalization rules. You might normalize harmless whitespace or a date representation, but you should not normalize away a changed digit. That policy belongs in version control because loosening it can make acceptance rise without transcription improving. The decision log should therefore pair every score with the corpus version and validator version. Otherwise a future rerun looks comparable when it isn't.
Ship the first adapter weekly if that is your cadence, but keep shadow comparison out of the customer path. A recorded, consented test corpus is easier to reason about than duplicating live audio to several processors. It also avoids turning a procurement experiment into an undeclared data-flow change. The exact retention and consent policy depends on your contracts and jurisdiction, so resolve it with the people responsible for those obligations before collecting the corpus.
Measure four operational outcomes: acceptance rate, cost per accepted invoice, review rate, and latency at the percentile your workflow cares about. Do not compress them into one weighted score on day one. A weighted score can bury a hard EU requirement or make a tiny nominal price difference cancel a serious review burden. Use hard gates first, then compare survivors.
Short wins matter.
What I would change at scale
At higher volume, I would add contract tests for every adapter, encrypted corpus storage with explicit retention, controlled concurrency, and an audit trail for quote changes. I would also separate a fast canary set from the full evaluation set. The canary catches interface drift during a regular deploy; the full set supports deliberate procurement reviews. Neither should contain customer audio unless that use is authorized.
The catch is that a portable adapter deliberately exposes only the common denominator. It is not suitable when the product depends on a provider-specific capability that cannot be represented without flattening useful information. In that case, keep the generic transcript path but expose the special capability through a clearly isolated extension, and accept that switching will require product work. Portability has a maintenance cost too: four integrations mean four authentication paths, response mappings, contract tests, and quote records. A solo founder may rationally keep only the current provider and one tested fallback.
Stick with a direct provider integration when one candidate has already passed the corpus, the exit test is documented, and maintaining simultaneous adapters would steal more revenue-producing hours than it protects. Run a scheduled bake-off only when volume, customer requirements, a contract renewal, or observed review work can change the decision. Constant comparison feels rigorous but can become infrastructure theater.
Price is the final comparison among candidates that clear the gates, not the opening argument. Request current terms for your region and workload, encode each billing rule, replay the corpus, and select the lowest cost per accepted invoice. Re-run before a material commitment. That answer is less satisfying than a static ranking, but it is honest, portable, and tied to the edtech job that has to work.
Top comments (0)