Base URL https://api.echonova.ai

Developers

Synchronous and asynchronous calls

Which of the two call styles fits a request, how a job moves through its four statuses, and how to poll for the result.

Text to Speech and Speech to Text each expose the same two call styles. A synchronous call holds the connection open and returns the result in its response. An asynchronous call returns a job id at once and does the work in the background; you read the result from the job endpoint or receive it through a webhook. This page explains what each style does, when to use it, and how a job moves from start to finish.

The two styles

Synchronous Asynchronous
Text to Speech POST /v1/text-to-speech/sync POST /v1/text-to-speech
Speech to Text POST /v1/speech-to-text/sync POST /v1/speech-to-text
Returns jobId and status, with the result: audioUrl or transcription, durationSeconds and creditsConsumed. jobId and status only.
Connection Open until the result is ready. Closed as soon as the job is accepted.
status in the response completed processing
Where the result is In the response. On GET .../jobs/{jobId}, or in a job.completed webhook.
Job events None. Synchronous calls never emit a webhook event. job.completed or job.failed.
tashkeel Applied. Accepted, and the text is synthesised as written. Use the synchronous endpoint for diacritics.

The request body is the same for both Text to Speech endpoints, and the multipart/form-data upload is the same for both Speech to Text endpoints. The reference pages list every field: Generate speech synchronously, Generate speech from text, Transcribe audio synchronously and Transcribe audio file.

What both styles share

  • The charge comes first. The API prices the request, charges the workspace, and only then does the work. If the balance cannot cover it, the call is rejected with 402 and Insufficient subscription balance before anything runs. See Credits.
  • Every call creates a job. A synchronous call creates the job already completed; an asynchronous call creates it pending. Both appear in GET /v1/text-to-speech/jobs or GET /v1/speech-to-text/jobs, newest first, and both can be read back from GET .../jobs/{jobId}. Any key in the workspace with access to that product can read the job.
  • A failure is refunded. When a synchronous call fails, you get an error response and the charge is refunded. When an asynchronous job fails, its status becomes failed, error holds the reason, and the charge is refunded.
  • The same envelope. Every response is { "success", "statusCode", "errorMessage", "data" } and every successful call, including POST, returns HTTP 200. See Developer API overview.

Choosing a style

Use the synchronous endpoint when all of these hold:

  • You need the result inside the same request, for example to answer a chat message or to play a prompt on a call.
  • The input is within the limit: audio of 10 minutes or less for Speech to Text, 500 characters for Text to Speech. Both endpoints of each service share the limit, so input size never forces the choice.
  • Your client can keep an HTTP connection open for as long as the work takes. The API sets no timeout of its own on a synchronous call, so set your client timeout with the input size in mind.

Use the asynchronous endpoint in every other case:

  • Batches. Submit many jobs, then poll them or let webhooks tell you when each one is done.
  • Clients with a short HTTP timeout, or work you do not want to block on.
  • Anything where you want a job.completed or job.failed event delivered to your own endpoint instead of holding a connection.

Both styles cost the same credits for the same input. The choice is about how you receive the result, not about price.

Match the style to who is waiting. A person waiting on a reply wants one round trip; a batch wants a free connection and a webhook.

Recommended

  • A chat reply: POST /v1/text-to-speech/sync, result in the response.
  • Five hundred recordings overnight: POST /v1/speech-to-text for each, with a webhook on job.completed and job.failed.

Less effective

  • A chat reply: POST /v1/text-to-speech, then a poll loop that holds the user for the same time plus a polling interval.
  • Five hundred recordings overnight: POST /v1/speech-to-text/sync in parallel with a 30-minute client timeout on every connection.

Job lifecycle

A job has four statuses: pending, processing, completed and failed. An asynchronous job passes through them in this order:

POST accepted
     |
     v
  pending  -->  processing  -->  completed
                    |
                    +--------->  failed
Status What it means What the job object holds
pending The job is charged and created, and is waiting to be accepted for processing. The POST returns only after the job leaves this state, so the only place you can see it is a job list fetched while the POST is still in flight. No result fields.
processing The job is being worked on. Text to Speech: no audioUrl and no durationSeconds. Speech to Text: durationSeconds holds an estimate read from the file, and transcription is missing.
completed The result is ready. Text to Speech: audioUrl and durationSeconds. Speech to Text: transcription and durationSeconds as measured.
failed The job failed, or it timed out. The credits are refunded. error with the reason. creditsConsumed still shows the amount that was charged and then refunded.

Three rules hold for every job:

  • A job ends once. A job cannot go from completed to failed, or be completed twice.
  • A job cannot stay processing forever. A job that has been processing for longer than 10 minutes is marked failed with error set to Job timed out — AI server did not respond; the status changes within 15 minutes of the POST. The credits are refunded. This timeout sends no job.failed webhook, so a poll loop, or a fallback poll after 15 minutes of silence, is the way to learn about it.
  • A rejection at submission is reported twice. If the job is refused when the POST submits it, the job is marked failed, the credits are refunded, and the POST itself returns an error, most often 503 with Speech generation is temporarily unavailable. Please try again. or Transcription is temporarily unavailable. Please try again.. The failed job stays in your job list.

A synchronous call has a shorter story: the job appears as completed when the result is ready, and if the call fails no job is created at all. You get the error response and the refund.

Polling a job

Read the job with GET /v1/text-to-speech/jobs/{jobId} or GET /v1/speech-to-text/jobs/{jobId}, using the jobId from the POST response. Stop when status is completed or failed.

curl https://api.echonova.ai/v1/text-to-speech/jobs/JOB_ID \
  -H "Authorization: Bearer YOUR_API_KEY"

The job object names its id id, while the POST that started it returned jobId. The fields:

Field Text to Speech job Speech to Text job
id Always Always
status Always Always
createdAt Always Always
creditsConsumed Always Always
text Always Not present
voiceId Always Not present
durationSeconds When completed Always; an estimate until completed
audioUrl When completed Not present
transcription Not present When completed
error When failed When failed
language Not present Always
numbersAsDigits Not present Always

audioUrl is a signed link to a WAV file, and it expires. Every GET of the job returns a fresh link, so poll again rather than storing the URL. Fetch the file with a plain GET and no Authorization header.

Two things to keep in mind while polling:

  • Every poll counts against the rate limit of 100 requests per 60 seconds per key on that endpoint. Wait a few seconds between polls. See Rate limits.
  • Give the loop a deadline. Every job reaches completed or failed within 15 minutes of entering processing.

The full pattern, with backoff on 429 and 5xx, is on Retry requests safely.

Webhooks instead of polling

When an asynchronous job ends, the API sends job.completed or job.failed to every enabled webhook in the workspace that subscribes to that event. job.completed carries the result itself, audioUrl for Text to Speech and transcription for Speech to Text, so you do not need to call the job endpoint afterwards. job.failed carries error. Synchronous calls never emit either event, and neither does the 10-minute timeout. See Configure a webhook and Webhook events.

Limits

Limit Value Applies to
text length 1 to 500 characters POST /v1/text-to-speech and POST /v1/text-to-speech/sync
Audio duration 10 minutes POST /v1/speech-to-text/sync and POST /v1/speech-to-text
File size 150 MB Both Speech to Text upload endpoints
Time in processing 10 minutes, then marked failed within 15 minutes of the POST Asynchronous jobs
Requests 100 per 60 seconds per key per endpoint Every endpoint, including polls

FAQ

Does the asynchronous endpoint accept longer input?

No. Both Text to Speech endpoints take up to 500 characters, and both Speech to Text endpoints take up to 10 minutes of audio.

Can I get diacritics on an asynchronous job?

No. tashkeel takes effect on POST /v1/text-to-speech/sync only. The asynchronous endpoint accepts the field and synthesises the text as written.

Does a synchronous call show up in my job list?

Yes, already completed, with the same creditsConsumed, audioUrl or transcription as the response.

Why did my job end as failed with no webhook?

It timed out after more than 10 minutes in processing, and the timeout sends no event. Poll the job when 15 minutes have passed without one.

Is the result kept after the job ends?

Yes. GET .../jobs/{jobId} returns the transcript, or a fresh audioUrl, until you delete the job with DELETE .../jobs/{jobId} or close your account. Any key in the workspace with access to that product can delete its jobs. A job of another workspace answers 404.