Skip to content

Numbers as digits

The problem. Speech models write numbers as words: “one thousand three hundred fifty five dollars twenty eight cents”. Your application needs $1,355.28 to show an order, fill a form or check a payment. With numerals, the transcript arrives with digits.

Where it works. Every model.

{ "numerals": true }

Each program streams a 16-bit WAV file and prints what comes back. Put your API key in the GLADIA_API_KEY environment variable, then run python numerals.py meeting.wav.

numerals.py
# pip install gladiaio-sdk requests
import os
import sys
import threading
import wave
import requests
from gladiaio_sdk import GladiaClient, LiveV2ConnectSessionOptions
API_URL = os.environ.get("GLADIA_API_URL", "https://eu1.api.gladia.io")
API_KEY = os.environ["GLADIA_API_KEY"]
with wave.open(sys.argv[1]) as wav: # a 16-bit PCM WAV file
rate, channels = wav.getframerate(), wav.getnchannels()
audio = wav.readframes(wav.getnframes())
config = {
"encoding": "wav/pcm",
"sample_rate": rate,
"bit_depth": 16,
"channels": channels,
"model": "solaria-3",
"language_config": {
"languages": [
"en"
]
},
"numerals": True
}
client = GladiaClient(api_key=API_KEY, api_url=API_URL)
# `numerals` is a Gladia Enterprise option the Python SDK does not send yet
# (gladiaio/sdk#61): create the session over HTTP, then let the SDK stream it.
created = requests.post(f"{API_URL}/v2/live", headers={"x-gladia-key": API_KEY}, json=config, timeout=10)
created.raise_for_status()
info = created.json()
session = client.live_v2().connect_session(LiveV2ConnectSessionOptions(id=info["id"], url=info["url"]))
ended, errors = threading.Event(), []
@session.on("message")
def on_message(message):
if message.type == "transcript" and message.data.is_final:
print(message.data.utterance.text)
session.on("error", errors.append)
session.once("ended", lambda _: ended.set())
step = rate * channels * 2 // 10 # 100 ms of audio per chunk
for start in range(0, len(audio), step):
session.send_audio(audio[start : start + step]) # live audio: send each chunk as it is captured
session.stop_recording()
ended.wait(timeout=60)
sys.exit(1 if errors or not ended.is_set() else 0)
{
"type": "transcript",
"data": {
"is_final": true,
"utterance": {
"text": "The total is $1,355.28 for 42 items.",
"text_raw": "The total is one thousand three hundred fifty five dollars twenty eight cents for forty two items."
}
}
}

text has the digits; text_raw keeps the words as spoken.

spoken written
“forty two” 42
“one thousand three hundred fifty five dollars twenty eight cents” $1,355.28
“mille trois cent cinquante-cinq euros vingt-huit” (French) 1 355,28 €
“call me at nine fifteen” call me at 9:15
“rendez-vous à neuf heures quinze” (French) rendez-vous à 9h15
“um halb zehn” (German) um 9:30
“a las nueve y cuarto” (Spanish) a las 9:15

Clock times are written the way the language writes them, with no extra setting.

Phone numbers are written in their country’s format and dates the way the language writes them. Nothing to add: "numerals": true is enough wherever the turn router runs (the default), and your messages keep their shape (no turn-router label is added to the finals).

spoken written
“zéro six douze trente-quatre cinquante-six soixante-dix-huit” (French) 06 12 34 56 78
“two one two five five five zero one two three” (212) 555-0123
“plus thirty three six twelve thirty four fifty six seventy eight” +33 6 12 34 56 78
“march twelfth twenty twenty six” March 12, 2026
“march twelfth twenty twenty six” (with "region": "GB") 12 March 2026
“le douze mars deux mille vingt-six” (French) le 12 mars 2026
“am zwölften märz” (German) am 12. März
“el primero de mayo” (Spanish) el 1 de mayo
“il primo maggio” (Italian) il 1º maggio

How it works. The turn router reads each final transcript and tells what its numbers are: an amount, a phone number, a date, a clock time, a count. Numerals then writes each one for what it is:

  • a phone number the speaker has finished is written in the national format of its country. A spoken country code (“plus thirty three”) gives the international format. A number that no country’s numbering plan knows stays as digits;
  • a phone number still being dictated keeps its digits as said, the pauses’ commas removed: it is formatted once the speaker has given all of it;
  • a date is written the language’s way (English, French, German, Spanish, Italian, Portuguese, Dutch);
  • a clock time, a date, a rank or a phone number is never written as an amount.

Without the turn router ("turn_router": false, or a host that does not run it), numerals writes numbers, clock times and amounts only: phone numbers and dates stay as plain digits. Digits alone cannot tell a phone number from an order number, an account number or a code, and formatting those as phone numbers would change them.

Phone numbers and dates follow a country. Set it with region, an ISO 3166-1 alpha-2 code in capitals:

{ "numerals": { "region": "CH" } }
region “zéro six douze trente-quatre cinquante-six soixante-dix-huit”
omitted (French) 06 12 34 56 78
CH 061 234 56 78

Without region, the countries where the language is spoken apply, in this order: English US, GB, CA, AU, IE, NZ, IN, ZA; French FR, BE, CH, CA, LU; German DE, AT, CH; Spanish ES, MX, AR, CO, CL, PE; Italian IT, CH; Portuguese PT, BR; Dutch NL, BE. A phone number is formatted for the first of these countries whose numbering plan knows it as dialled. A region that is not two capital letters is refused with a 400.

  • The conventions of the language spoken. Amounts take that language’s separators and currency position: set the language (language_config.languages) when you know it, or the detected one applies.
  • Partials too. Partial transcripts show digits as well, and may change while the number is still being said. Phone numbers and dates are formatted in the final transcripts only.
  • Phone numbers and dates need the turn router. It runs by default; with "turn_router": false they stay as plain digits.
  • Numbers said in pieces. “my card is 4970… 1234…” stays in one utterance thanks to the number hold, on by default. With turn_router: false, each piece may come as its own final.
  • Vocabulary is kept. A custom vocabulary phrase made of number words (“Seven Eleven”) stays as you listed it.
  • Search and storage. Store text for display and text_raw if you match on spoken words.
  • With PII redaction, numbers that are personal data (card, phone, account numbers) are redacted after the conversion.
symptom cause fix
amounts use the wrong separators another language was detected set language_config.languages
a long number is split over several finals turn_router: false, or a pause over 2 s keep the number hold on
a product name with numbers was converted it is not in your vocabulary add it to custom vocabulary
a phone number or a date stays as plain digits the turn router is off ("turn_router": false) remove "turn_router": false
a phone number is in another country’s format the language’s first country was used set numerals.region
a phone number stays as digits with the turn router on no country’s numbering plan knows it as dialled, or the speaker had not finished it check the digits and numerals.region