Base URL https://api.echonova.ai

Changelog

Changelog

The API version, what counts as a breaking change, and a dated list of every change to the v1 endpoints since their release.

This page is the record of what changed on the Developer API and when. Read it before you upgrade a client, and check it when a request that used to work starts to fail.

Version

There is one API version, v1, and it is part of every path: /v1/text-to-speech, /v1/speech-to-text and the endpoints under them. There is no version header. A change that cannot be made inside v1 under the policy below ships under a new prefix, with v1 kept running for a published period.

Breaking changes policy

A breaking change is one that makes a request that worked yesterday fail today, or changes the meaning of a response a client already reads.

Breaking Not breaking
Removing or renaming an endpoint, a request field, a response field or a webhook event. Adding an endpoint.
Making an optional request field required. Adding an optional request field.
Lowering a limit: fewer characters, shorter audio, a smaller file, fewer requests per minute. Raising a limit.
Changing the type of a field or the HTTP status of a response. Adding a field to a response or to a webhook body.
Changing the authentication scheme or the key format. Adding a webhook event type. It is sent only to webhooks that subscribe to it.
Changing an existing errorMessage string. Adding a new errorMessage for a new failure case.

Two properties of the API follow from this table. First, unknown fields in a JSON body or a query string are rejected with 400 and property <name> should not exist, so a client must not send a field before the entry below that introduces it. The two Speech to Text upload endpoints read named form fields and ignore any other form field. Second, a client should read the fields it needs from a response and ignore the rest, because new fields will appear without notice.

Breaking changes are listed below with their date.

Changes

2026-10-23

Added:

  • Both Speech to Text upload endpoints, POST /v1/speech-to-text and POST /v1/speech-to-text/sync, take an optional identify_speakers form field, true or false. The default is false, so a request that does not send it gets the same transcript as before. A value other than true, false, 1 or 0 gets 400 with identify_speakers must be true or false.
  • identifySpeakers, segments and speakers are now returned on the synchronous response and on the job returned by get and list. segments holds start, end, text and speaker (spk0, spk1, … or null); speakers holds id and talkTime. Jobs created before this date report false, [] and []. The job.completed webhook body is unchanged.
  • GET /v1/speech-to-text/jobs/{jobId}/export downloads a completed job as format=txt, srt or json, with speaker labels in lang=en or ar. The body is the file, not the envelope. It adds the messages format must be one of the following values: txt, srt, json, lang must be one of the following values: en, ar and Job is not completed, all 400. See Speech to Text formats and output.

2026-10-21

Breaking:

  • A jobId that is not 24 hexadecimal characters now gets 400 with Invalid identifier on the GET and DELETE job routes. DELETE answered 404 for it before, and a 12-character id answered 404.
  • A wrong HTTP method on an existing /v1 path now gets 405 with Method not allowed and an Allow header. It was 404.

Changed:

  • An unknown /v1 route now gets 404 with Route not found. It was Cannot GET ….

Added:

  • Every error on a /v1 path carries docs, including 413 and the 400 for a body that is not valid JSON.
  • The request log takes a requestId filter, and the Activity Logs tab has Find by request ID. Audio processed shows seconds under a minute.

2026-10-20

Added:

  • Activity and the request log now record every call to /v1 made with a known key, any method and any status. GET and DELETE calls get rows too. GET calls charge no credits.
  • A JSON body over 100 KB returns 413 with Request body is larger than 100 KB. A body that is not valid JSON returns 400 with Request body is not valid JSON. Neither has an Activity row, because the key is not checked yet.
  • A body sent to POST /v1/text-to-speech, POST /v1/text-to-speech/sync or a widget ticket with a Content-Type other than application/json returns 415 with Content-Type must be application/json.
  • The add-endpoint dialog shows a new webhook's signing secret once, with a Copy button.

Changed:

  • The per-IP limit now counts only requests the key check rejects. A valid key is never blocked by it and is limited per key only.
  • apiKeyName in spending.warning and spending.exhausted is now the key's name, not its id.

2026-10-15

Added:

  • Both Speech to Text upload endpoints, POST /v1/speech-to-text and POST /v1/speech-to-text/sync, take an optional numbers_as_digits form field, true or false. The default is true: spoken numbers come back as digits, as before. Send false to keep them as the words that were said. English transcripts are never converted either way.
  • numbersAsDigits is now returned on the synchronous response and on the job returned by get and list. Jobs created before this date report true.

2026-10-11

Added:

  • Sound Cleaning: POST /v1/sound-cleaning, GET /v1/sound-cleaning/jobs and GET /v1/sound-cleaning/jobs/{jobId}. The upload is synchronous, up to 10 minutes and 150 MB, in MP3, WAV, FLAC or OGG/Opus, and costs 1,000 credits per minute. See Sound Cleaning quickstart.
  • clean is a new value of a key's allowed services. A key with no service restriction can use Sound Cleaning at once. A key that lists services does not, until you Edit it in the portal and turn Sound Cleaning on; until then its Sound Cleaning calls get 403.

Changed:

  • Limits per request: text on both Text to Speech endpoints is capped at 500 characters, and audio on both Speech to Text endpoints and on Sound Cleaning is capped at 10 minutes. The duration message on both Speech to Text endpoints is now Audio exceeds the 10-minute limit.
  • Credits changed unit. Text to Speech costs 1 credit per character of text, spaces and line breaks included; Speech to Text costs 100 credits per minute of audio, charged per second. See Credits.
  • creditsConsumed, creditsUsed and creditLimit keep their names and types; their values are in the new unit, whole credits (no decimals). Every key's Credit Limit was multiplied by 3,000 into the new unit, so a limit of 100 now reads 300,000.
  • Credits are whole numbers. Each charge rounds to a whole credit (half up, at least 1), so creditsConsumed is never a decimal; a 1 second Speech to Text call is 2 credits, not 1.666667. creditsUsed is a whole number too.

2026-10-06

Added:

  • Both Speech to Text upload endpoints, POST /v1/speech-to-text and POST /v1/speech-to-text/sync, take an optional language form field: auto, ar or en. The default is auto.
  • language is now returned on the synchronous response and on the job returned by get and list. Jobs created before this date report auto.

Breaking:

  • A language value that was ignored before, such as en-US or arabic, now gets 400 with language must be one of: auto, ar, en. Other extra form fields are still ignored.

2026-10-01

  • Credit amounts in /v1 responses and in the spending.warning and spending.exhausted webhooks are now decimal numbers with up to 6 decimal places. The field names are the same: creditsConsumed, creditsUsed and creditLimit. Parse them as floating-point numbers. percentageUsed is still an integer.
  • Credits are charged exactly. A charge is no longer rounded up to a whole credit and has no minimum.
  • 1 credit is now $0.095 of usage, and every service is priced at its provider cost plus 20%. See Credits.

2026-09-06

Breaking:

  • Both Text to Speech endpoints: the request fields creativity, exaggeration and stability were removed. A request that still sends one gets 400 with property creativity should not exist, or the same message for the other two.
  • Both upload endpoints: an upload over the size cap is now rejected with 413 and File too large while the body is being received. Before, it was rejected with 400 and Audio file exceeds the 50MB limit. after the whole file had been read.
  • Both upload endpoints: the duration rejection message changed. It was Audio exceeds the 60-second limit. Use the async endpoint for longer audio. on the synchronous endpoint and the same text with 1800-second on the asynchronous one. It is now Audio exceeds the 20-minute limit. and Audio exceeds the 1-hour limit.

Limits raised:

  • POST /v1/text-to-speech/sync: text from 500 to 5 000 characters. The asynchronous endpoint stays at 5 000, so both endpoints now share one cap. Earlier the same day the asynchronous cap was briefly 20 000; it is 5 000.
  • POST /v1/speech-to-text/sync: audio duration from 1 minute to 20 minutes.
  • POST /v1/speech-to-text: audio duration from 30 minutes to 1 hour.
  • Both upload endpoints: file size from 50 MB to 150 MB.

Added:

  • Both Text to Speech endpoints: the optional fields cfg (1 to 5, default 2), numSteps (16, 32, 64 or 128, default 32) and tashkeel (boolean, default false). speed (0.5 to 2, default 1) was kept. When a field is omitted the default applies, so a request without them behaves as before.
  • Webhooks: the job.completed and job.failed events are now delivered when an asynchronous job started with an API key ends. Before this date the two events could be subscribed to but were not delivered.

2026-06-08

Breaking:

  • Both upload endpoints: the duration rejection messages changed from Audio exceeds 30 minute limit (got <seconds>s) and Sync STT supports up to 60s. Use the async endpoint for longer audio. to Audio exceeds the 1800-second limit. Use the async endpoint for longer audio. and Audio exceeds the 60-second limit. Use the async endpoint for longer audio.
  • Both upload endpoints: a request with no file part is rejected with File is required instead of Audio file is required.

2026-06-07

Initial release of v1:

  • Text to Speech: POST /v1/text-to-speech with up to 5 000 characters, POST /v1/text-to-speech/sync with up to 500 characters, GET /v1/text-to-speech/voices, GET /v1/text-to-speech/jobs and GET /v1/text-to-speech/jobs/{jobId}.
  • Speech to Text: POST /v1/speech-to-text with up to 30 minutes of audio, POST /v1/speech-to-text/sync with up to 1 minute, both up to 50 MB, GET /v1/speech-to-text/jobs and GET /v1/speech-to-text/jobs/{jobId}.
  • Authentication with Authorization: Bearer and keys starting with ek_.
  • A rate limit of 100 requests per 60 seconds per key on each endpoint.
  • Request logs in the portal.
  • Webhooks with the spending.warning and spending.exhausted events.

FAQ

How do I pin my client to a version?

Every path starts with /v1, and that prefix is the version. There is no version header to send.

What happens if I keep sending a removed field?

400 with property <name> should not exist. Remove the field; the request then behaves as it did before the field existed.

Will a new response field break my client?

Not if the client reads the fields it needs and ignores the rest. Fields are added without notice; removing or renaming one is a breaking change and is listed here.