> For the full page index, fetch https://echonova.ai/llms.txt

# Authentication

The Authorization header, the API key format, the checks every request passes, and each authentication error with its cause and fix.

Every Developer API request is authenticated with an API key sent as a bearer
token. This page is for the engineer wiring the header into a client and for
anyone reading a `401` or `403` off the wire.

## Header

Send the key in the `Authorization` header with the `Bearer` scheme:

```http
Authorization: Bearer YOUR_API_KEY
```

The header is read strictly. All three rules must hold or the request is
rejected before the key is checked.

| Rule                    | Detail                                                                          |
| ----------------------- | ------------------------------------------------------------------------------- |
| Two parts               | The value is the scheme, one space, then the token. Extra spaces or parts fail. |
| Scheme is `Bearer`      | Case-sensitive. `bearer` and `Token` fail.                                      |
| Token starts with `ek_` | Any other prefix fails, including an empty token.                               |

## Key format

A key is the string `ek_` followed by 64 URL-safe base64 characters, 67
characters in all. The portal shows the full secret once, at creation, and
afterwards displays only the first 8 characters. A key belongs to the
workspace it was created in; see
[Base URL](/docs/developers/base-url). To create one, see
[Get an API key](/docs/developers/get-an-api-key).

## Code samples

:::tabs

```bash title="cURL"
curl https://api.echonova.ai/v1/text-to-speech/voices \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```python
import requests

response = requests.get(
    "https://api.echonova.ai/v1/text-to-speech/voices",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(response.json())
```

```javascript
const response = await fetch('https://api.echonova.ai/v1/text-to-speech/voices', {
  headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
console.log(await response.json());
```

:::

## What the API checks

The checks run in this order on every request. The first one that fails ends
the request.

| Order | Check                                    | On failure |
| ----- | ---------------------------------------- | ---------- |
| 1     | The header follows the three rules above | `401`      |
| 2     | A key with this secret exists            | `401`      |
| 3     | The key has not been deleted             | `401`      |
| 4     | The key's **Status** is enabled          | `401`      |
| 5     | The workspace's account is not closed    | `401`      |
| 6     | The workspace has a subscription         | `403`      |
| 7     | The subscription is active or on trial   | `403`      |
| 8     | The key may call this service            | `403`      |

Check 8 applies only to a key whose services are restricted. The portal
will not save a key without at least one service. A key made before this
change with no service selected can still call every service, so open it and
choose its services. A restricted key is
checked by path: every `/v1/text-to-speech` route, including the voice list,
needs Text to Speech access, every `/v1/speech-to-text` route needs Speech
to Text access, and every `/v1/sound-cleaning` route needs Sound Cleaning
access.

A request that passes all eight checks updates the key's **Last Used** date
in the portal.

## Errors

Every `401` carries the same message, so the body alone does not tell you
which of the five `401` causes applied. Work through them in order.

| Status | `errorMessage`                                                 | Cause                                                                                               | Fix                                                                                 |
| ------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `401`  | `Unauthorized`                                                 | No `Authorization` header, a scheme other than `Bearer`, or a token that does not start with `ek_`. | Send `Authorization: Bearer ek_...` exactly as shown above.                         |
| `401`  | `Unauthorized`                                                 | No key matches the token. The secret was mistyped, or it belongs to a different workspace.          | Compare the first 8 characters with the portal list. Use a key from this workspace. |
| `401`  | `Unauthorized`                                                 | The key was deleted.                                                                                | Create a new key.                                                                   |
| `401`  | `Unauthorized`                                                 | The key is disabled.                                                                                | Enable it from the switch on its row under **Developers**, **API Keys**.            |
| `401`  | `Unauthorized`                                                 | The account that owns the key is closed.                                                            | Use a key from an account that is open.                                             |
| `403`  | `Subscription not found`                                       | The workspace that owns the key has no subscription.                                                | Sign in to the portal for that workspace and check that it has a plan.              |
| `403`  | `Subscription expired or inactive`                             | The subscription is in a state other than active or trial.                                          | Renew the subscription, then retry.                                                 |
| `403`  | `This API key does not have access to Text-to-Speech services` | The key is restricted and Text to Speech is not among its services.                                 | **Edit** the key and grant Text to Speech, or use another key.                      |
| `403`  | `This API key does not have access to Speech-to-Text services` | The key is restricted and Speech to Text is not among its services.                                 | **Edit** the key and grant Speech to Text, or use another key.                      |
| `403`  | `This API key does not have access to Sound Cleaning services` | The key is restricted and Sound Cleaning is not among its services.                                 | **Edit** the key and grant Sound Cleaning, or use another key.                      |

The error body has the same shape as every other response:

```json
{
  "data": null,
  "statusCode": 401,
  "success": false,
  "errorMessage": "Unauthorized"
}
```

> [!WARNING]
> Never put an API key in code that runs on a user's device: a browser bundle,
> a mobile app, a desktop app or a public repository. Anyone who extracts it
> can spend your workspace's credits until you delete the key. Keep the key on
> your own server and call the API from there.

## FAQ

### Why does every 401 carry the same message?

`Unauthorized` covers the five `401` causes in the table. Check the header
format first, then compare the first 8 characters of your secret with the
portal list, then check the key's **Status**.

### Does a disabled key get 401 or 403?

`401`. The `403` responses are about the workspace's subscription and the
key's services, and are reached only by a key that is enabled.

### Can I send the key somewhere other than the Authorization header?

No. A query parameter, a cookie or a custom header is ignored, and the
request gets `401`.

### Does a rejected request update Last Used?

No. **Last Used** moves only when a request passes all eight checks.

## Related

- [Get an API key](/docs/developers/get-an-api-key)
- [Base URL](/docs/developers/base-url)
- [Rate limits](/docs/developers/rate-limits)
- [Credits](/docs/developers/credits)
- [Developer API overview](/docs/developers/overview)
