# Voices

`GET | POST | DELETE /v1/voices` — The roster, your cloned voices and the public voice catalog — everything speech accepts.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: Free (cloning included) — speech is billed per use

Everything `/v1/audio/speech` accepts as a `voice`: the roster (`kind: "roster"`) and your clones (`kind: "cloned"` — pass the clone's `id`). Characters speak too: use `char:<id>` instead.

**`POST /v1/voices` clones a voice** from audio samples (multipart: `name` + one or more audio `files`, 1GB total) — free, synchronous, returns the clone ready to use. `DELETE /v1/voices/{id}` removes a clone. The paid part is the speech that uses it.

**The public voice catalog.** A clone goes public only when its owner publishes it (`POST /v1/voices/{id}/publish`) with `consent: true` — their attestation that it is their own voice or that they have the speaker's permission. Anyone can then pass its `id` as the `voice` of `/v1/audio/speech`, billed as usual; the owner is not paid. `GET /v1/voices/public` lists the catalog: `sort=trending` is the popularity algorithm — distinct people who generated with the voice (1 point) or listened to it (0.25) each day, halved every 3 days, plus 2·ln(1 + likes) and a newcomer boost — one person counts once a day and the owner never counts. `popular` = most used, `liked`, `new`.

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/voices` | List the roster and your clones. |
| `POST` | `/v1/voices` | Clone a voice from audio samples (multipart: `name` + one or more audio `files`, 1GB total). Free and synchronous. |
| `DELETE` | `/v1/voices/{id}` | Delete a clone — characters using it fall back to the default voice. |
| `GET` | `/v1/voices/public` | The public voice catalog — voices their owners shared, usable by anyone. |
| `POST` | `/v1/voices/{id}/publish` | Submit one of your cloned voices to the public catalog — attested, free. It joins the catalog once our team approves it (`review`: `pending` → `approved` or `rejected`, readable on GET /v1/voices). Publishing again keeps the verdict unless the name or description changes. |
| `DELETE` | `/v1/voices/{id}/publish` | Take your voice out of the catalog (popularity waits for a re-publish). |
| `POST` | `/v1/voices/{id}/like` | Like a public voice (idempotent). |
| `DELETE` | `/v1/voices/{id}/like` | Take a like back (idempotent). |
| `POST` | `/v1/voices/{id}/report` | Report a public voice — an impersonation, a voice used without consent, anything against the acceptable use policy. |

## GET /v1/voices

List the roster and your clones.

### Example request

```bash
curl https://eroq.ai/v1/voices \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/voices", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/voices", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "voices": [
    { "id": "aria", "name": "Aria", "kind": "roster", "gender": "female" },
    { "id": "b7c2…", "name": "My narrator", "kind": "cloned", "previewUrl": "https://…", "public": true, "review": "pending", "reviewNote": null }
  ]
}
```

## POST /v1/voices

Clone a voice from audio samples (multipart: `name` + one or more audio `files`, 1GB total). Free and synchronous.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | Clone name, up to 80 characters. |
| `files` | file | yes | One or more audio samples (wav or mp3), 1GB total. Thirty clean seconds is usually enough. |

### Example request

```bash
curl https://eroq.ai/v1/voices \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -F name="My narrator" \
  -F files=@sample1.mp3 \
  -F files=@sample2.mp3
```

```javascript
import { readFile } from 'node:fs/promises'

const form = new FormData()
form.append('name', 'My narrator')
form.append('files', new Blob([await readFile('sample1.mp3')]), 'sample1.mp3')
form.append('files', new Blob([await readFile('sample2.mp3')]), 'sample2.mp3')

const res = await fetch('https://eroq.ai/v1/voices', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
  body: form,
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/voices",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    data={"name": "My narrator"},
    files=[("files", open("sample1.mp3", "rb")), ("files", open("sample2.mp3", "rb"))],
)
print(res.json())
```

```go
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)
	form.WriteField("name", "My narrator")
	part0, _ := form.CreateFormFile("files", "sample1.mp3")
	file0, _ := os.Open("sample1.mp3")
	io.Copy(part0, file0)
	file0.Close()
	part1, _ := form.CreateFormFile("files", "sample2.mp3")
	file1, _ := os.Open("sample2.mp3")
	io.Copy(part1, file1)
	file1.Close()
	form.Close()

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/voices", &buf)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", form.FormDataContentType())

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "id": "b7c2…", "name": "My narrator", "voiceId": "v_8f3a…",
  "labels": {}, "previewUrl": "https://store.eroq.ai/…/preview.mp3",
  "createdAt": "2026-09-11T14:02:11Z"
}
```

## DELETE /v1/voices/{id}

Delete a clone — characters using it fall back to the default voice.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/voices/VOICE_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/voices/VOICE_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/voices/VOICE_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true }
```

## GET /v1/voices/public

The public voice catalog — voices their owners shared, usable by anyone.

### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sort` | string | no | `trending` (default, the popularity algorithm), `popular` (most used), `liked` or `new`. |
| `q` | string | no | Search the name, the description and the tags. |
| `gender` | string | no | Filter: `female`, `male` or `neutral`. |
| `language` | string | no | Filter by language code: `en`, `fr`, `es`, `de`, `it`, `pt`, `nl`, `pl`, `ru`, `ja`, `ko`, `zh`, `ar`. |
| `limit` | integer | no | Voices to return, 1–100. Default 40. |
| `offset` | integer | no | Pagination offset. Default 0. |

### Example request

```bash
curl "https://eroq.ai/v1/voices/public?sort=trending&q=value&gender=female&language=en&limit=20&offset=20" \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/public?sort=trending&q=value&gender=female&language=en&limit=20&offset=20', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/voices/public?sort=trending&q=value&gender=female&language=en&limit=20&offset=20", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/voices/public?sort=trending&q=value&gender=female&language=en&limit=20&offset=20", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "voices": [
    {
      "id": "3f1c…", "name": "Midnight Mira", "description": "Low, slow, a smile in every line.",
      "tags": ["Sultry", "Soft"], "gender": "female", "language": "en", "author": "mira",
      "uses": 128, "likes": 41, "plays": 390, "liked": false, "mine": false,
      "publishedAt": "2026-09-27T12:00:00Z"
    }
  ],
  "total": 1, "sort": "trending"
}
```

## POST /v1/voices/{id}/publish

Submit one of your cloned voices to the public catalog — attested, free. It joins the catalog once our team approves it (`review`: `pending` → `approved` or `rejected`, readable on GET /v1/voices). Publishing again keeps the verdict unless the name or description changes.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `consent` | boolean | yes | Must be `true`: you attest that this is your own voice, or that you have the explicit permission of the person it belongs to, and that it imitates no celebrity or public figure. |
| `language` | string | yes | Language code (see GET /v1/voices/public). |
| `gender` | string | no | `female`, `male` or `neutral`. |
| `tags` | string[] | no | Up to 5, e.g. `Soft`, `Sultry`, `Deep`, `Calm`, `Narration`, `ASMR`. |
| `description` | string | no | One line, up to 240 characters. |
| `name` | string | no | Rename it on the way out, up to 80 characters. |

### Example request

```bash
curl https://eroq.ai/v1/voices/VOICE_ID/publish \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "language": "en",
  "gender": "female",
  "tags": [
    "Sultry",
    "Soft"
  ],
  "description": "Low, slow, a smile in every line.",
  "consent": true
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID/publish', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "language": "en",
    "gender": "female",
    "tags": [
      "Sultry",
      "Soft"
    ],
    "description": "Low, slow, a smile in every line.",
    "consent": true
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/voices/VOICE_ID/publish",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "language": "en",
        "gender": "female",
        "tags": [
            "Sultry",
            "Soft"
        ],
        "description": "Low, slow, a smile in every line.",
        "consent": True
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "language": "en",
  "gender": "female",
  "tags": [
    "Sultry",
    "Soft"
  ],
  "description": "Low, slow, a smile in every line.",
  "consent": true
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/voices/VOICE_ID/publish", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "voice": {
    "id": "3f1c…", "name": "Midnight Mira", "public": true, "author": "mira",
    "description": "Low, slow, a smile in every line.", "tags": ["Sultry", "Soft"],
    "gender": "female", "language": "en", "uses": 0, "likes": 0, "plays": 0,
    "published_at": "2026-09-27T12:00:00Z", "review": "pending"
  }
}
```

## DELETE /v1/voices/{id}/publish

Take your voice out of the catalog (popularity waits for a re-publish).

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/voices/VOICE_ID/publish \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID/publish', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/voices/VOICE_ID/publish", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/voices/VOICE_ID/publish", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true, "public": false }
```

## POST /v1/voices/{id}/like

Like a public voice (idempotent).

### Example request

```bash
curl -X POST https://eroq.ai/v1/voices/VOICE_ID/like \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID/like', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post("https://eroq.ai/v1/voices/VOICE_ID/like", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/voices/VOICE_ID/like", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "liked": true, "likes": 42 }
```

## DELETE /v1/voices/{id}/like

Take a like back (idempotent).

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/voices/VOICE_ID/like \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID/like', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/voices/VOICE_ID/like", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/voices/VOICE_ID/like", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "liked": false, "likes": 41 }
```

## POST /v1/voices/{id}/report

Report a public voice — an impersonation, a voice used without consent, anything against the acceptable use policy.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | yes | What is wrong, 3–500 characters. |

### Example request

```bash
curl https://eroq.ai/v1/voices/VOICE_ID/report \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Imitates a celebrity"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/voices/VOICE_ID/report', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "reason": "Imitates a celebrity"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/voices/VOICE_ID/report",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "reason": "Imitates a celebrity"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "reason": "Imitates a celebrity"
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/voices/VOICE_ID/report", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true }
```

---
Canonical: https://eroq.ai/docs/voices · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
