Base URL https://api.echonova.ai

Developers

Code samples

Two complete programs per language, an asynchronous Text to Speech job from request to saved audio file and an asynchronous Speech to Text job from upload to transcript, plus the response envelope they all read.

Each program on this page runs from top to bottom with one edit: your API key. The first one turns a sentence into a saved audio file through an asynchronous Text to Speech job. The second uploads an audio file, waits for the asynchronous Speech to Text job, and prints the transcript. The quickstarts explain each call one at a time; this page puts them together. A short third program cleans a recording.

What you need

  • An API key with Access to all three services, or with no service restriction. See Get an API key.
  • The base URL. The callout at the top of this page shows the host these samples call. See Base URL.
  • Credits on the workspace. Each job is charged before it runs. See Credits.
  • cURL with jq, Python 3 with requests, or Node 18 or later.
  • For the second program, an audio file named audio.mp3 of up to 10 minutes and 150 MB. For WAV or OGG, change CONTENT_TYPE to audio/wav or audio/ogg.

Steps

Step 1: Read the envelope once

Every response, success or error, is one JSON object with the same four fields. A successful call returns HTTP 200, including POST calls, and the result is in data:

{
  "success": true,
  "statusCode": 200,
  "errorMessage": null,
  "data": {}
}

On an error, success is false, data is null, statusCode matches the HTTP status and errorMessage holds one string:

{
  "data": null,
  "statusCode": 402,
  "success": false,
  "errorMessage": "Insufficient subscription balance"
}

Each program below wraps its calls in one helper that checks success and returns data, so the rest of the code reads the result and nothing else. The programs stop on the first error. Retry requests safely shows the version that backs off and retries.

Step 2: Text to Speech, from request to audio file

The program lists the voices and takes the first one whose language is ar, starts an asynchronous job, polls it every 3 seconds until it is completed or failed, then downloads audioUrl to speech.wav. The link is signed, so the download sends no Authorization header.

set -eo pipefail
BASE_URL="https://api.echonova.ai"
AUTH="Authorization: Bearer YOUR_API_KEY"
TEXT="مرحبا بكم في إيكونوڤا"

# api METHOD PATH [curl options]: prints .data, or exits on an error envelope
api() {
  local method=$1 path=$2 body
  shift 2
  body=$(curl -s -X "$method" "$BASE_URL$path" -H "$AUTH" "$@")
  if [ "$(echo "$body" | jq -r '.success')" != "true" ]; then
    echo "error $(echo "$body" | jq -r '.statusCode'): $(echo "$body" | jq -r '.errorMessage')" >&2
    exit 1
  fi
  echo "$body" | jq '.data'
}

# 1. Pick an Arabic voice
VOICE_ID=$(api GET /v1/text-to-speech/voices \
  | jq -r '[.items[] | select(.language == "ar")][0].id')

# 2. Start the job
JOB_ID=$(api POST /v1/text-to-speech -H "Content-Type: application/json" \
  -d "$(jq -n --arg text "$TEXT" --arg voiceId "$VOICE_ID" '{text: $text, voiceId: $voiceId}')" \
  | jq -r '.jobId')
echo "job $JOB_ID"

# 3. Poll until it ends
while true; do
  JOB=$(api GET "/v1/text-to-speech/jobs/$JOB_ID")
  STATUS=$(echo "$JOB" | jq -r '.status')
  echo "$STATUS"
  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then break; fi
  sleep 3
done
if [ "$STATUS" = "failed" ]; then
  echo "failed: $(echo "$JOB" | jq -r '.error')" >&2
  exit 1
fi

# 4. Download the audio. The link is signed; send no Authorization header.
curl -s -o speech.wav "$(echo "$JOB" | jq -r '.audioUrl')"
echo "saved speech.wav"
echo "$JOB" | jq '{durationSeconds, creditsConsumed}'

The POST returns { "jobId", "status" } with status set to processing. The polled job object names its id id and adds text, voiceId, createdAt and creditsConsumed; audioUrl and durationSeconds appear when the job is completed, and error when it is failed. The file is WAV. See Text to Speech quickstart for each call on its own and Text to Speech voices and options for speed, cfg, numSteps and tashkeel.

Step 3: Speech to Text, from upload to transcript

The program sends audio.mp3 as the file part of a multipart/form-data request, polls the job every 5 seconds, and prints transcription and durationSeconds when it is completed. The Content-Type of the file part must start with audio/; each sample sets it explicitly.

set -eo pipefail
BASE_URL="https://api.echonova.ai"
AUTH="Authorization: Bearer YOUR_API_KEY"
AUDIO_FILE="audio.mp3"
CONTENT_TYPE="audio/mpeg"

# api METHOD PATH [curl options]: prints .data, or exits on an error envelope
api() {
  local method=$1 path=$2 body
  shift 2
  body=$(curl -s -X "$method" "$BASE_URL$path" -H "$AUTH" "$@")
  if [ "$(echo "$body" | jq -r '.success')" != "true" ]; then
    echo "error $(echo "$body" | jq -r '.statusCode'): $(echo "$body" | jq -r '.errorMessage')" >&2
    exit 1
  fi
  echo "$body" | jq '.data'
}

# 1. Upload the file and start the job
JOB_ID=$(api POST /v1/speech-to-text -F "file=@$AUDIO_FILE;type=$CONTENT_TYPE" | jq -r '.jobId')
echo "job $JOB_ID"

# 2. Poll until it ends
while true; do
  JOB=$(api GET "/v1/speech-to-text/jobs/$JOB_ID")
  STATUS=$(echo "$JOB" | jq -r '.status')
  echo "$STATUS"
  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then break; fi
  sleep 5
done
if [ "$STATUS" = "failed" ]; then
  echo "failed: $(echo "$JOB" | jq -r '.error')" >&2
  exit 1
fi

# 3. Read the transcript
echo "$JOB" | jq -r '.transcription'
echo "$JOB" | jq '{durationSeconds, creditsConsumed}'

While the job is processing, durationSeconds holds an estimate read from the file and transcription is absent. When it is completed, both hold their final values. The job also carries language and numbersAsDigits, and, when it was made with identify_speakers=true and has speaker data, timed segments and speakers; without them those two are empty lists. To get subtitles, download the job from GET /v1/speech-to-text/jobs/{jobId}/export?format=srt. See Speech to Text quickstart for each call on its own and Speech to Text formats and output for the accepted inputs.

Step 4: Sound Cleaning, upload and download

Sound Cleaning is synchronous, so there is no polling. The program uploads noisy.wav (10 minutes or less, 150 MB or less), reads outputUrl from the response and saves the cleaned WAV as cleaned.wav. The Content-Type of the file part must match the extension.

set -eo pipefail
BASE_URL="https://api.echonova.ai"
AUTH="Authorization: Bearer YOUR_API_KEY"

RESPONSE=$(curl -sS -X POST "$BASE_URL/v1/sound-cleaning" -H "$AUTH" -F "file=@noisy.wav;type=audio/wav")
if [ "$(echo "$RESPONSE" | jq -r '.success')" != "true" ]; then
  echo "$RESPONSE" | jq -r '"\(.statusCode) \(.errorMessage)"' >&2
  exit 1
fi
curl -sS -o cleaned.wav "$(echo "$RESPONSE" | jq -r '.data.outputUrl')"
echo "$RESPONSE" | jq -r '.data.creditsConsumed'

See Sound Cleaning quickstart for the tuning fields and the limits.

What happens next

  • Jobs are listed under your key, one list per service: GET /v1/text-to-speech/jobs, GET /v1/speech-to-text/jobs and GET /v1/sound-cleaning/jobs, newest first. Each carries the creditsConsumed it was charged.
  • speech.wav is on disk. The audioUrl it came from expires; read the job again for a fresh link if you need the file later.
  • The transcript was printed to standard output. Store it yourself; the API keeps it on the job, and only there.
  • If either job ended failed, its error was printed and its credits were refunded. Run the program again.

Limits

Limit Value Applies to
text length 1 to 500 characters POST /v1/text-to-speech
Audio duration 10 minutes POST /v1/speech-to-text
File size 150 MB POST /v1/speech-to-text
Content-Type of the file part Must start with audio/ POST /v1/speech-to-text
Time in processing 10 minutes, then marked failed within 15 minutes of the POST Both jobs
Requests 100 per 60 seconds per key per endpoint Every call, including polls

Troubleshooting

The program prints a status code and a message and stops

The helper raised on an error envelope. The number is statusCode and the text is errorMessage. 401 Unauthorized means the key: check that YOUR_API_KEY was replaced and that the key is enabled. 402 Insufficient subscription balance means the workspace cannot cover the job; nothing ran. 403 with does not have access to means the key restricts services. See Authentication.

The program prints failed with an error message

The job ended failed. The message names the reason; it is AI server generation failed or AI server transcription failed when no reason was given, and Job timed out — AI server did not respond when the job stayed processing for more than 10 minutes. The credits were refunded. Run the program again.

The voice lookup finds nothing

No active voice has language set to ar, so next() in Python raises StopIteration, voices.find in Node returns undefined, and the shell VOICE_ID is null. List the voices and pick an id by hand; see List available voices.

400 Unsupported file type: application/octet-stream. Please upload an audio file.

The file part was sent without an audio/ content type. Keep the CONTENT_TYPE value in the sample; do not remove the type= from the cURL -F argument or the type from the Node Blob.

The audio download fails

The signed link has expired. Call GET /v1/text-to-speech/jobs/{jobId} again; every call returns a fresh audioUrl. Download it at once.

429 ThrottlerException: Too Many Requests

Several copies of the program share one key and their polls add up to more than 100 requests per 60 seconds on the job endpoint. Wait for Retry-After seconds, then use a longer interval or one key per process. See Rate limits.

FAQ

Where are the synchronous versions?

On the quickstarts. POST /v1/text-to-speech/sync returns audioUrl in the same response and POST /v1/speech-to-text/sync returns transcription, with no polling. See Text to Speech quickstart and Speech to Text quickstart, and Synchronous and asynchronous calls to choose between them.

Why check success instead of the HTTP status?

Both work. Every error response has success set to false and a statusCode equal to the HTTP status, so a client can key off either. The envelope check is one line and gives you errorMessage in the same step.

Can I skip the voices call?

Yes. Set voiceId to an id you already know. The voice must be active; an unknown or inactive id returns 404 Voice not found.

Can I receive the result without polling?

Yes. Register a webhook for job.completed and job.failed. The job.completed event carries audioUrl or transcription, so no further call is needed. See Configure a webhook and Webhook events.

Do the programs retry?

No. They stop on the first error. See Retry requests safely for the polling loop that honours Retry-After and backs off on 5xx.