https://api.echonova.aiDevelopers
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.completedandjob.failed, plus one poll of the job if no event has arrived 15 minutes after thePOST.
Less effective
- A poll loop per job, running from the moment the
POSTreturns 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.