Skip to content

    Voices and languages

    Kokoro ships a fixed set of designed voices — it does not clone a specific real person from a sample. Voices are selected by name, and several map to OpenAI voice aliases for drop-in compatibility.

    Each voice name encodes its language and gender through a two-letter prefix, for example af_heart (American English, female) or bm_george (British English, male).

    PrefixLanguageGender
    af_ / am_American Englishfemale / male
    bf_ / bm_British Englishfemale / male
    ef_ / em_Spanishfemale / male
    ff_ / fm_Frenchfemale / male
    hf_ / hm_Hindifemale / male
    if_ / im_Italianfemale / male
    jf_ / jm_Japanesefemale / male
    pf_ / pm_Brazilian Portuguesefemale / male
    zf_ / zm_Mandarin Chinesefemale / male

    The default voice is af_heart (configurable with DEFAULT_VOICE). List the voices available in your image at runtime:

    Terminal window
    curl http://localhost:8880/v1/audio/voices
    # {"voices":[{"id":"af_heart","name":"af_heart"}, ...]}

    Requests may use the standard OpenAI voice names, which are mapped to designed voices:

    OpenAI nameMaps to
    alloyam_adam
    ashaf_nicole
    coralbf_emma
    echoaf_bella
    fableaf_sarah
    onyxbm_george
    novabf_isabella
    sageam_michael
    shimmeraf_sky

    The pipeline language is resolved in this order: an explicit lang_code on the request, then DEFAULT_VOICE_CODE if set, then the first letter of the voice name. So af_heart implies language a (American English) unless you override it.

    Common codes: a (American English), b (British English), z (Mandarin Chinese). Other designed-voice languages are driven by their voice prefix. Use /dev/phonemize to inspect how text is converted for a given language.

    The voice field accepts a single voice or a combination. Combine with +, subtract with -, and weight individual voices with name(weight):

    import requests
    # Equal 50/50 mix
    requests.post("http://localhost:8880/v1/audio/speech", json={
    "input": "Hello world!", "voice": "af_bella+af_sky", "response_format": "mp3",
    })
    # Weighted 2:1 mix (about 67% / 33%) — weights normalize automatically
    requests.post("http://localhost:8880/v1/audio/speech", json={
    "input": "Hello world!", "voice": "af_bella(2)+af_sky(1)", "response_format": "mp3",
    })

    Weights are normalized to sum to 1 by default (VOICE_WEIGHT_NORMALIZATION=true). With normalization disabled, the raw weights are applied as-is. Unknown voices, empty operands (a leading/trailing +/-), or malformed weights return 400.

    When ALLOW_LOCAL_VOICE_SAVING=true, you can save a combination as a reusable voicepack (returns a .pt file). This is disabled by default and returns 403 when off:

    requests.post("http://localhost:8880/v1/audio/voices/combine",
    json="af_bella(2)+af_sky(1)")