← Back to Resources

Voice metadata schema: a cross-vendor field reference

Andrew Muns
Andrew Muns
Director of AEO at Telnyx
August 27, 2026
Reviewed byEli MogulTechnical Writer at Telnyx

Ask five text-to-speech providers to describe the same voice and you get five different objects. One calls the language field Locale, another languageCodes, a third just language. One ships a typed list of speaking styles, the rest ship nothing comparable. Sample rate is a property of the voice for two of them and a parameter of the synthesis request for the others.

The names are the easy part. The harder problem is that fields which look equivalent, like locale, gender, style, age, and sample rate, often carry different guarantees and mean different things operationally. This page is a field-contract audit of how Telnyx, ElevenLabs, Azure, AWS Polly, and Google Cloud describe voice metadata today, and a conservative schema for reconciling them without inventing data.

TL;DR

  • The providers represent language with locale-like tags, but the contract is not uniform: Google explicitly returns BCP-47 in languageCodes, Azure and Polly commonly return language-region codes, ElevenLabs combines free-form labels with optional verified-language metadata, and Telnyx exposes a single normalized language string
  • Gender is the most common voice attribute, but it is neither semantically uniform nor guaranteed per voice: Polly uses a two-value field, Google returns an SSML enum whose NEUTRAL value is currently unsupported, and ElevenLabs derives it from an optional label
  • Azure is the strongest source of typed expressive metadata through StyleList and RolePlayList; ElevenLabs offers label-based discovery metadata; Polly and Google's basic voice objects expose no comparable per-voice style array
  • Native sample rate and requested output rate are separate concepts, and only Azure and Google report a native rate on the voice
  • Normalize conservatively: keep every raw provider value, canonicalize a language tag only when it validates, never derive an accent from a region, and separate invocable styles from descriptive labels

Why there is no standard

TTS metadata grew one vendor at a time. AWS Polly launched around a small fixed roster of voices, so its schema is a short enum: pick a Name, get a Gender and a LanguageCode. Azure built its catalog for a speech platform with expressive SSML, so its voice records carry style and role data that only make sense alongside that markup. ElevenLabs came up through voice cloning and a community voice library, so its schema is built around user-supplied labels and per-language verification. Google keeps its voice object minimal and pushes voice character into the model name.

Telnyx sits in front of most of these, exposing one list endpoint across its native models and a changing set of third-party providers. TTS Library, the site you are reading this on, then layers its own enrichment on top of that. Both are examples of the raw-versus-normalized split this schema formalizes, and neither is a standard the other providers follow.

Locale codes

Every provider represents language with a locale-like tag, but the contract behind that tag differs.

Provider Field What you actually get
Google languageCodes Documented BCP-47 tags, including macroregions like es-419 and cmn-tw
AWS Polly LanguageCode, AdditionalLanguageCodes ISO-based language-region codes such as en-US and en-GB-WLS; bilingual voices list extra codes
Azure Locale, SecondaryLocaleList Language-region strings such as zh-CN; multilingual voices list secondary locales
ElevenLabs labels.language plus verified_languages[] A free-form label (often a bare subtag) plus an optional, nullable array with per-language locale and accent
Telnyx language A single normalized string; the docs do not guarantee a full locale

A safe rule is to keep the provider's value as locale_raw, canonicalize it to BCP-47 casing only when it validates as a language tag, and treat language, region, script, and accent as independently nullable. A tag can legitimately be language-only. A region is not a reliable proxy for accent, and es-419 is a macroregion rather than a country. Script can matter for languages like Chinese (zh-Hans versus zh-Hant), though Google specifically advises omitting the script subtag in its voice-selection field because it infers script from the input text.

Do not derive an accent just because a region subtag is present.

Gender taxonomy

Gender is the most consistently available attribute, and it is still not something you can treat as one field.

Provider Field Values
AWS Polly Gender Female, Male
Azure Gender Female, Male, occasionally Neutral
Google ssmlGender SSML_VOICE_GENDER_UNSPECIFIED, MALE, FEMALE, NEUTRAL (Google documents NEUTRAL as not yet supported)
ElevenLabs labels.gender Free text from an optional label, usually male, female, or neutral
Telnyx gender A string in the list-voices response; no documented multi-value taxonomy

Google's value is an enum tied to SSML voice selection, which is why it is uppercase and carries an explicit unspecified state. ElevenLabs stores gender as a user-applied label, so casing and vocabulary are not guaranteed and the label can be missing.

A source-preserving normalization keeps both forms:

gender_normalized: female | male | neutral | unknown
gender_raw: string | null

TTS Library's own normalization layer (lib/normalizeGender.js) collapses upstream casing variants and also emits Non-binary where an upstream source provides it. Only emit non-binary when a real provider value backs it, not as a default.

Age

Most providers have no age field at all.

Provider Field Notes
ElevenLabs labels.age Optional, free-form; values like young, middle_aged, old appear in practice
MiniMax (via Telnyx) age Coarse labels such as adult, surfaced through the proxied catalog
Azure RolePlayList Expressive role-play personas, not demographic age (see below)
AWS Polly, Google none Age is implied by the voice, never labeled

Azure's RolePlayList is the field most often mistaken for an age model. It is a list of role-play personas the voice supports through Azure's expressive SSML, mixing age-coded, gendered, and narrative labels like YoungAdultFemale, OlderAdultMale, Boy, Girl, and Narrator. A voice that lists Boy is not necessarily a child voice in the catalog; it can perform that persona on request. Treat these as supported synthesis personas, not as the voice's inherent age.

If you need an age dimension across a catalog, plan to infer it for most voices rather than read it.

Style and emotion

This is where the catalogs diverge hardest, and where it is easy to overstate.

Azure ships a typed StyleList per voice, with values such as cheerful, angry, sad, excited, friendly, whispering, newscast, customerservice, and narration-relaxed. Each style is something you then invoke through the mstts:express-as SSML tag. This is the clearest example of a per-voice, invocable style field in any of the five catalogs.

ElevenLabs has no equivalent style enum, but it does carry style-adjacent discovery metadata: its list and shared-voice endpoints filter on use_cases (narrative_story, conversational, characters), descriptives (free-text descriptors like calm or raspy), and the labels map. Those help you find a voice, but they are semi-structured discovery hints, not a guaranteed synthesis-control vocabulary.

AWS Polly and Google's basic voice-list objects expose no comparable per-voice style array. Expressiveness on those platforms comes from engine or model choice (Polly's generative engine, Google's newer promptable and controllable voices) and from SSML, rather than from a tag on the voice record. Telnyx's list-voices response is intentionally compact and carries no style field, though individual Telnyx models document expressive controls (Telnyx Ultra exposes emotion, speed, and volume).

For a unified object, split what the draft schema would have called styles[] into three fields so an ElevenLabs descriptor is never mistaken for an invocable Azure style:

styles_supported: string[]     # explicit, invocable style names (Azure StyleList)
descriptors: string[]          # search and discovery labels, not contractual controls
expressive_controls: string[]  # e.g. ssml, prompt, speed, pitch, emotion

Sample rate

Sample rate is metadata on some platforms and a request parameter on others, and the two are worth modeling separately.

Provider Native rate on the voice Requested output rate
Azure SampleRateHertz, 24000 or 48000 (high-fidelity models report 48000) X-Microsoft-OutputFormat header; resampled if it differs from the model
Google naturalSampleRateHertz (commonly 24000) AudioConfig.sampleRateHertz; Google converts and warns on quality if it differs
AWS Polly none SampleRate on SynthesizeSpeech: mp3 and ogg_vorbis accept 8000, 16000, 22050, 24000, 44100, 48000; pcm accepts 8000 or 16000. Standard defaults to 22050, neural, long-form, and generative to 24000
ElevenLabs none output_format on the synthesis request encodes both codec and rate, for example mp3_44100_128 or pcm_16000
Telnyx none in the list response Chosen per synthesis call, codec dependent

Store native_sample_rate_hz only when the catalog explicitly provides one. Model supported_output_formats[] and requested_sample_rate_hz separately, because output selection is a request-level, codec-dependent choice. Azure and Google document conversion when the requested rate differs from the native rate; do not assume the same behavior for every provider.

Provider-specific extensions

Fields that only one provider has. Keep these in an extensions object rather than forcing them into the core schema:

Provider Extension fields
AWS Polly SupportedEngines (standard, neural, long-form, generative)
Azure VoiceType, Status (GA or Preview), WordsPerMinute, SecondaryLocaleList
ElevenLabs category (premade, cloned, professional, generated), fine_tuning, verified_languages
Telnyx and MiniMax model name and quality tier, hosted flag

A proposed integration schema

This is a schema for your normalization layer, not a contract any provider publishes. It separates four kinds of information: what the provider claimed (*_raw), a canonical identifier derived only when it validates, an inference you made and can explain (*_source), and a synthesis capability.

Field group Treatment Why
Locale locale_raw plus nullable locale_bcp47, language, region, script Do not convert a free-form label or partial code into a canonical locale silently
Accent accent_raw, accent_source, optional accent_normalized A region is not a reliable proxy for accent
Gender gender_raw plus gender_normalized Preserves optional and free-text upstream values
Age age_label_raw, age_normalized, role_play[] Azure role-play is a capability, not an age field
Style styles_supported[], descriptors[], expressive_controls[] Separates invocable controls from discovery labels
Sample rate native_sample_rate_hz, requested_sample_rate_hz, supported_output_formats[] Catalog metadata is not per-request configuration
Engine engine, model_id, supported_engines[] "Tier" is not one shared cross-vendor concept
Extensions extensions{} The right home for lossless provider-specific data

A defensible core object:

{
  "voice_id": "aws.polly.Joanna",
  "provider": "aws_polly",
  "provider_voice_id": "Joanna",
  "name": "Joanna",

  "locale_raw": "en-US",
  "locale_bcp47": "en-US",
  "language": "en",
  "region": "US",
  "script": null,

  "accent_raw": null,
  "accent_normalized": null,
  "accent_source": null,

  "gender_raw": "Female",
  "gender_normalized": "female",

  "age_label_raw": null,
  "age_normalized": null,
  "role_play": [],

  "styles_supported": [],
  "descriptors": [],
  "expressive_controls": ["ssml"],

  "native_sample_rate_hz": null,
  "supported_output_formats": [],
  "supported_engines": ["standard", "neural"],

  "extensions": {}
}

Telnyx and TTS Library as normalization layers

Telnyx's catalog endpoint, GET https://api.telnyx.com/v2/text-to-speech/voices, returns a compact object with provider, name, voice_id, language, and gender, across its native models and a changing set of third-party providers. Telnyx's public materials advertise more than 3,900 voices across more than 80 languages; treat the exact provider set and count as time-sensitive.

TTS Library layers additional enrichment on that catalog: an accent classification, resolved language_name and country_name from a locale map, and a normalized gender. You can see the enriched shape at /developers and query it at /api/voices. It is one concrete instance of the raw-plus-normalized pattern above, not a reference the other providers implement.

The takeaway holds across all five: fields that look equivalent often differ in provenance and operational meaning. Normalize conservatively, keep the raw values, and record how every inference was made.

For background on the technology these voices come from, see what is TTS and what is neural TTS.