Base URL https://api.echonova.ai

Developers

Latency best practices

How to get results back sooner and waste fewer requests, using the sync and async endpoints, the rate limit, the job timeout and the webhooks as they behave.

The time between your request and a usable result is spent in four places: your connection to the API, the work itself, the wait between the work finishing and you learning about it, and the download of the result. The API gives you a choice at each step. This page explains which choice is faster and why, using the API's limits and timings.

Pick the call style by size, not by habit

Each service has a synchronous endpoint that holds the connection until the result is ready, and an asynchronous one that answers at once with a jobId. The synchronous call is the shortest path for one result: one request, one response, no second call. It is capped by input size.

Endpoint Cap on input What you get back
POST /v1/text-to-speech/sync 500 characters of text jobId, status, audioUrl, durationSeconds, creditsConsumed
POST /v1/speech-to-text/sync 10 minutes of audio, 150 MB jobId, status, transcription, durationSeconds, language, numbersAsDigits, creditsConsumed
POST /v1/text-to-speech 500 characters of text jobId and status
POST /v1/speech-to-text 10 minutes of audio, 150 MB jobId and status

Both Text to Speech endpoints share the 500-character cap, and both Speech to Text endpoints share the 10-minute cap, so the asynchronous one does not buy you longer input. What it buys is a free connection: your process is not held open while the work runs, and a long transcription cannot hit a timeout in your own HTTP stack. Use the synchronous call when you need the result in the same request, for example to answer a user who is waiting. Use the asynchronous call when the result can arrive later, or when you have many items to process. The two styles are compared in full on Synchronous and asynchronous calls.

For text longer than 500 characters, split at sentence boundaries and send the parts as separate asynchronous jobs. Each one comes back with its own audioUrl, and you join the files on your side.

Let the result come to you

An asynchronous job moves through pending and processing and ends as completed or failed. You can learn about the end in two ways:

Way Delay after the job ends Requests it costs
A job.completed or job.failed webhook Seconds. The delivery is sent when the job ends, with a 10-second timeout per attempt, and retried after 5 and 10 seconds if it fails. None against your key.
Polling GET .../jobs/{jobId} Up to one polling interval. One per poll, counted against the rate limit.

The webhook body carries the result itself: audioUrl for Text to Speech, transcription for Speech to Text, and durationSeconds for both. You do not need to call the job endpoint after the event arrives. Register an endpoint on Configure a webhook and verify each delivery as on Verify a webhook signature.

Recommended

  • A webhook on job.completed and job.failed, plus one poll of the job if no event has arrived 15 minutes after the POST.

Less effective

  • A poll loop per job, running from the moment the POST returns until the status changes.

If you must poll, do the arithmetic against the rate limit first. A key may make 100 requests per 60 seconds on each endpoint, and the job endpoint is one endpoint whatever the jobId. Poll at a fixed interval of 3 seconds for Text to Speech and 5 seconds for Speech to Text, never under 1 second; the full pattern is on Retry requests safely. At 3 seconds one job costs 20 requests per minute, so five jobs polled at once on one key fill the limit; at 5 seconds it is 12 per minute and eight jobs. Beyond that, lengthen the interval, spread the jobs over several keys, or switch to webhooks. Stop polling as soon as status is completed or failed; polling faster does not finish the job sooner.

Poll the job when no event comes

A job that stays in processing for more 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. This timeout sends no job.failed webhook, so a client that only listens for events never hears about it. Set your own deadline: if neither event has arrived 15 minutes after the POST, fetch the job once and read its status.

Reuse the connection

Every request to the API is over HTTPS, and a new connection pays for a TCP handshake and a TLS handshake before the first byte of your request is sent. Keep one HTTP client per process and let it reuse connections. In Python, create one requests.Session() and call it for every request. In Node, the built-in fetch keeps connections alive by default; do not create a new agent per call. A tight loop that polls with a fresh connection each time spends more time in handshakes than in the API.

Cache the voice list

GET /v1/text-to-speech/voices returns every active voice in one response, with no paging and no filters, so there is nothing to gain from calling it per request. Fetch it when your process starts, keep it in memory, and filter on language locally. Refresh it when a generation call answers 404 with Voice not found, which is what the API says when a voice has been deactivated since you cached the list. The fields are on Text to Speech voices and options.

Send audio the API can measure

The two upload endpoints read the file's duration from its metadata before any work runs, both to apply the 10-minute cap and to price the call. A file whose duration cannot be read is not rejected: it is accepted and priced from its size instead, at one second per 16 000 bytes, rounded up. Whether that is more or less than the real length depends on the file's bitrate, and the charge is not corrected afterwards. Write files with a proper header so the charge matches the audio, and prefer a compressed container: a smaller file spends less time in transfer.

Download the audio once

audioUrl is a signed link to the audio file, and the link expires. Download the file when you receive the link, store it on your side, and serve it from there. Do not embed the link in a page or a message that will be opened later. If a link has expired, GET /v1/text-to-speech/jobs/{jobId} returns a fresh one on every call.

Give each system its own key

The rate limit is counted per API key on each endpoint, not per workspace. One busy batch job on a shared key can push an interactive service on the same key into 429. Create one key per system that calls the API, so that each has its own 100 requests per 60 seconds, and so that a limit on one key does not delay the others. Your plan sets how many active keys a workspace may have; see Get an API key.

FAQ

Is the asynchronous endpoint faster?

No. The work takes the same time either way. The asynchronous endpoint frees your connection and lets you run many jobs at once; the synchronous endpoint gives you the result in one round trip.

Does polling more often finish the job sooner?

No. It only spends your 100 requests per minute faster. The job ends when the work ends.

How long can I wait for a synchronous call?

There is no server-side cap on a synchronous call. Set your client timeout from the input size: a 10-minute file takes longer than a 10-second one.

Is the 15-minute deadline a guarantee?

Yes. A job that stays processing for more than 10 minutes is marked failed within 15 minutes of the POST, so a job is never processing for longer than that.