Base URL https://api.echonova.ai

Developers

Custom terminology

Send your own names, products and acronyms with a transcription and get the transcript back spelled your way, with every correction listed, near misses to review, and the same terms on a live stream.

Speech to Text writes what it hears. A bank name, a product or an acronym often comes out spelled the way it sounds: امريتس ان بي دي instead of Emirates NBD, or kay why see instead of KYC. Send your terms in the terminology field and the result carries a corrected copy of the transcript, with every change listed.

The words as heard are never lost. transcription stays exactly as it was transcribed; the corrected text is in terminology.corrected.

When to use it

  • Your callers say brand, product or place names that a general model spells its own way.
  • You need acronyms written as letters: KYC, IBAN, SWIFT.
  • The same name is said in Arabic and English, and you want one spelling.

Terminology does not change what the model hears. It corrects the text after transcription, so a word the model missed entirely cannot be added.

Quick example

Send terminology as a JSON string beside file and language. It works on POST /v1/speech-to-text/sync and POST /v1/speech-to-text.

curl -X POST "https://api.echonova.ai/v1/speech-to-text/sync" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@call.mp3;type=audio/mpeg" \
  -F "language=ar" \
  -F 'terminology={"keywords": ["KYC", {"term": "Emirates NBD", "variants": ["امريتس ان بي دي"]}]}'
import json
import requests

terminology = {
    "keywords": [
        "KYC",
        {"term": "Emirates NBD", "variants": ["امريتس ان بي دي"]},
        {"term": "SWIFT", "context_words": ["transfer", "تحويل"]},
    ]
}

with open("call.mp3", "rb") as audio:
    response = requests.post(
        "https://api.echonova.ai/v1/speech-to-text/sync",
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        files={"file": ("call.mp3", audio, "audio/mpeg")},
        data={"language": "ar", "terminology": json.dumps(terminology)},
    )

data = response.json()["data"]
print(data["transcription"])              # as heard
print(data["terminology"]["corrected"])   # spelled your way

The response adds a terminology object:

{
  "transcription": "حولت المبلغ عبر السويفت إلى امريتس ان بي دي أمس.",
  "terminology": {
    "raw": "حولت المبلغ عبر السويفت إلى امريتس ان بي دي أمس.",
    "corrected": "حولت المبلغ عبر الـSWIFT إلى Emirates NBD أمس.",
    "edits": [
      { "start": 16, "end": 23, "original": "السويفت", "replacement": "الـSWIFT", "stage": "phonetic", "score": 1 },
      { "start": 28, "end": 43, "original": "امريتس ان بي دي", "replacement": "Emirates NBD", "stage": "exact", "score": 1 }
    ],
    "nearMisses": [],
    "warnings": [],
    "mapperStatus": "applied",
    "mapperVersion": "3.0.0"
  }
}

With the asynchronous endpoint, the same object is on the job from GET /v1/speech-to-text/jobs/{jobId} once it completes, and in the job.completed webhook.

Keyword fields

terminology is {"keywords": [...]}. Each keyword is a plain string, or an object when you want more control.

Field Type What it does
term string, required The spelling you want in the output. A plain string keyword is the same as {"term": "..."}.
variants list of strings, up to 10 Ways the term is misheard that you have seen. Each one is matched exactly.
blocked list of strings, up to 20 Phrases that must never become this term, for example swift delivery for SWIFT.
context_words list of strings, up to 20 Words that, when one appears within eight words, let a less certain match apply.
needs_context boolean true applies the term only next to a context word or another keyword. Leave it out and it is decided for you when the term is an everyday word.
output_script latin, arabic, as_typed The script to write the term in. The term is always written as you typed it.

Terms, variants and other phrases are up to 100 characters each. Fields not in this table are ignored.

How matching works

Matching ignores case, Arabic hamza forms (أ إ آ), ى and ة, and diacritics. A match is always whole words and never crosses a comma or the end of a sentence.

stage What matched
exact The term itself, one of your variants, or an acronym spelled out (kay why see, كي واي سي → KYC).
phonetic Words that sound the same as the term, in either script.
fuzzy Words spelled close to the term.

score runs from 0 to 1. Exact and phonetic matches apply. A fuzzy match applies when it is close enough, or when it is a little less close but one of your context_words, or another keyword, is nearby. Short acronyms such as IT or API match exactly only, so they never replace an everyday word.

Arabic prefixes stay on the word: السويفت becomes الـSWIFT, and بالكي واي سي becomes بالـKYC. Digits, e-mail addresses and URLs are never changed.

Read the result

Field What it holds
raw The transcript as heard: the same text as transcription.
corrected raw with your terms applied. Outside the edits it is identical to raw.
edits Every replacement, in order. start and end are character offsets in raw, end-exclusive.
nearMisses Close matches that were not applied. See below.
warnings Anything that limited the mapping, each with a code and a detail.
mapperStatus applied, no_keywords, or skipped when the mapping could not run. corrected then equals raw.
mapperVersion The version of the matcher that answered.

Use the offsets to highlight corrections in your own interface: raw.slice(edit.start, edit.end) is always edit.original.

Without the terminology field the response has no terminology key at all, and the endpoint behaves exactly as before.

Downloads stay as heard

GET /v1/speech-to-text/jobs/{jobId}/export builds TXT, SRT and JSON from transcription and its timed segments, so downloads keep the words as heard. Take terminology.corrected from the job for the corrected text.

Near misses

A near miss is a match the service was not sure enough to apply, for example اونلاين بانكنق for Online Banking at a score of 0.81. It is listed in nearMisses with the words heard, the candidate term and a score, and the transcript is left unchanged there.

Nothing is remembered between requests. To have a near miss fixed next time, show it to a person, and when they confirm it, add the heard words to that term's variants in the list you send.

Live streams

On wss://api.echonova.ai/v1/speech-to-text/stream, send a terminology message at any time. It applies to every segment after it:

{
  "type": "terminology",
  "terminology": { "keywords": ["KYC", { "term": "Emirates NBD", "variants": ["امريتس ان بي دي"] }] }
}

The server answers {"type": "terminology.set", "active": true}, and each transcript message then carries a terminology object for its segment, with offsets into that segment's text. Send it again to replace the list, or with "terminology": null to clear it.

A list that is not valid gets an error message with code validation_failed and is ignored; the session continues with the terms it had. The message must fit in one 64 KiB frame, which holds a few hundred keywords.

Limits

Limit Value
Keywords 500 per request
Field size 256 KB on the file endpoints; one 64 KiB frame on a stream
Term, variant, phrase 100 characters
Variants 10 per keyword
Blocked and context words 20 each per keyword

Terminology is free: the request costs the same with or without it.

Errors

A terminology field that cannot be read answers 400 before anything is transcribed or charged.

Status errorMessage Fix
400 terminology must be a JSON object with a keywords list Send a JSON string such as {"keywords": ["KYC"]}.
400 each terminology keyword needs a term of 1 to 100 characters Give every keyword a term of 1 to 100 characters.
400 terminology variants, blocked and context_words must be lists of text of up to 100 characters; at most 10 variants and 20 of the others Send lists of short phrases within the limits.
400 terminology needs_context must be true or false, and output_script latin, arabic or as_typed Fix the value, or leave the field out.
400 terminology allows at most 500 keywords Send the terms this audio is likely to contain.
400 terminology must be at most 256 KB Send a shorter list.

If the matching itself fails or takes too long, the transcription still completes: mapperStatus is skipped, corrected equals raw, and warnings says why.

FAQ

Does terminology make transcription slower?

Barely. A list of a few thousand keywords on a five-minute call adds well under a second.

Can I store a list and refer to it by name?

No. Send the full list with every request. Keep it in your own system and add confirmed near misses to it over time.

Why did a term not apply?

Check nearMisses first: the match may have been too uncertain. Then add the heard words as a variant, or add context_words so a close match can apply when they are nearby.

Is the Speech to Text playground the same feature?

Yes. The playground's Terminology option sends the same field and shows the corrected transcript with each change highlighted.