# Guia de Integração — W Studio API (Text-to-Speech, Música e Imagens)

> **Para o cliente:** entregue este arquivo inteiro para qualquer assistente de IA
> (ChatGPT, Claude, Gemini, Copilot, etc.) e peça: *"Implemente a integração
> com esta API no meu sistema, na linguagem X"*. Este documento é
> autossuficiente — contém tudo que a IA precisa: URL base, autenticação,
> todos os endpoints, formatos de request/response, webhooks, erros e
> exemplos de código prontos.

---

## INSTRUÇÕES PARA A IA (leia primeiro)

Você vai integrar a **W Studio API**, uma API REST de geração de áudio
(text-to-speech), de música (MiniMax AI Music), de imagens
(Image Studio) e de clonagem de voz. Regras essenciais:

1. **Base URL:** `https://wstudios.app`
2. **Autenticação:** toda requisição aos endpoints `/api/v1/*` exige o header
   `Authorization: Bearer <API_KEY>`, onde `<API_KEY>` tem o formato
   `wsv_live_...`. O usuário vai te fornecer a chave dele; use um placeholder
   `WSV_API_KEY` no código e instrua o usuário a colocar a chave real numa
   variável de ambiente (nunca hardcode a chave no código-fonte).
3. **Fluxo típico (áudio):** (a) listar os modelos em `GET /api/v1/models`; (b)
   listar vozes em `GET /api/v1/voices?provider=...` para escolher um
   `voice_id`; (c) gerar áudio em `POST /api/v1/tts`; (d) baixar em
   `GET /api/v1/audio/{id}` ou receber via webhook.
4. **Fluxo típico (clonagem de voz):** (a) enviar o MP3 em
   `POST /api/v1/voice-clone` (multipart, com `voice_name`, `language_tag` e,
   no `minimax-fast`, `gender_tag`); (b) guardar o `voice_id` retornado;
   (c) confirmar em `GET /api/v1/voices?provider=clone-fast`; (d) usar esse
   `voice_id` em `POST /api/v1/tts`. Detalhes na seção 3.13.
4. **Crédito:** cada chamada de geração consome créditos da conta do dono da
   chave. Trate o erro `402` (saldo insuficiente) explicitamente.
5. **Rate limit:** 10 requisições a cada 90 segundos por chave. Trate `429`.
6. **Música:** gere em `POST /api/v1/music` com o **MiniMax AI Music** — envie
   `idea` (descrição da música) ou `lyrics` (+ `tags` de estilo), e
   `instrumental: true` se for sem voz. Acompanhe em `GET /api/v1/audio/{id}`
   (a cada ~15 segundos) e baixe quando o status for `done`.
   Detalhes na seção 3.11.
   Detalhes na seção 3.11.
7. **Novidades automáticas (importante):** toda resposta `/api/v1/*` pode trazer
   o campo `notices` (e os headers `X-WStudios-API-Changelog` e
   `X-WStudios-API-News`) com as novidades da API que essa chave ainda não viu —
   cada novidade aparece UMA vez. Quando o campo vier, mostre um aviso dentro do
   SEU sistema oferecendo o recurso novo (é assim que o usuário final fica sabendo
   que ganhou um recurso sem precisar ler documentação). O histórico completo
   está em `GET /api/v1/changelog` (seção 3.12).
8. **Não invente endpoints nem campos.** Use apenas os documentados abaixo.
9. Gere código limpo, com tratamento de erro, timeout e leitura da chave a
   partir de variável de ambiente. Pergunte ao usuário apenas o que faltar
   (ex.: linguagem/framework alvo).

---

## 1. Visão geral

- **O que faz:** converte texto em áudio usando seis opções (`edge`, `capcut`,
  `voice`/`elevenlabs`, `elevenlabs-v3`, `minimax` e `minimax-beta`) e gera
  imagens via Image Studio (6 modelos).
- **Aliases aceitos no provider:** `11labs`, `eleven-labs`, `elevenlabs-ultra`
  → `voice`/`elevenlabs`; `elevenlabs-v3` (modelo V3);
  `minimax-ultra`, `minimax-ai-ultra` → `minimax-beta`.
- **Protocolo:** HTTP REST, corpo em JSON, respostas em JSON (exceto downloads
  de áudio/imagem, que retornam bytes binários).
- **Não há SDK obrigatório nem instalação.** Basta um cliente HTTP na
  linguagem do usuário.

---

## 2. Autenticação

Envie a API key em **todas** as chamadas `/api/v1/*`:

```
Authorization: Bearer wsv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Alternativa (apenas quando não for possível enviar header, ex.: tag `<audio>`
no navegador): query string `?api_key=wsv_live_...`. Prefira sempre o header.

- A chave é pessoal e amarrada a uma conta (e ao saldo de créditos dessa conta).
- Guarde a chave como segredo (variável de ambiente / secrets manager).
- Sem header válido → `401`. Chave revogada → `401`.

---

## 3. Endpoints

Base URL: `https://wstudios.app`

| Método | Caminho | Descrição |
|--------|---------|-----------|
| GET | `/api/v1/account` | Dados da conta + saldo de créditos |
| GET | `/api/v1/models` | As seis opções/modelos disponíveis |
| GET | `/api/v1/voices` | Catálogo de vozes; use filtros por provider |
| POST | `/api/v1/tts` | Gera áudio a partir de texto |
| GET | `/api/v1/audio/{id}` | Baixa o áudio gerado (bytes MP3) |
| GET | `/api/v1/history` | Histórico de gerações |

### 3.1 GET /api/v1/account

Resposta `200`:
```json
{
  "id": 42,
  "email": "cliente@exemplo.com",
  "display_name": "João",
  "credits": 1500,
  "plan": "premium",
  "rate_limit": "unlimited",
  "created_at": "2026-06-01T12:00:00.000Z"
}
```

Contas `premium` não têm limite de chamadas da API nem fila por quantidade de
gerações simultâneas. Elas continuam usando créditos e todas as validações de
segurança e de provedor. Contas `standard` têm limite de 10 chamadas a cada
90 segundos por API key.

### 3.2 GET /api/v1/models

Descubra os providers/modelos antes de listar vozes. O endpoint retorna as
seis opções: Microsoft Edge, CapCut, ElevenLabs Ultra, ElevenLabs V3,
MiniMax Fast e MiniMax Ultra.

```http
GET /api/v1/models
Authorization: Bearer wsv_live_xxx
```

Resposta resumida:
```json
{
  "success": true,
  "models": [
    { "model_id": "edge-neural", "provider": "edge", "name": "Microsoft Edge Neural" },
    { "model_id": "capcut-ultra", "provider": "capcut", "name": "CapCut Ultra" },
    { "model_id": "eleven_multilingual_v2", "provider": "voice", "name": "ElevenLabs Ultra" },
    { "model_id": "eleven_v3", "provider": "elevenlabs-v3", "name": "ElevenLabs V3" },
    { "model_id": "speech-2.6-hd", "provider": "minimax", "name": "MiniMax Fast" },
    { "model_id": "generate-max", "provider": "minimax-beta", "name": "MiniMax Ultra" }
  ]
}
```

### 3.3 GET /api/v1/voices

Resposta `200`:
```json
{
  "voices": [
    { "id": "pt-BR-FranciscaNeural", "name": "Francisca", "provider": "edge", "language": "pt-BR", "gender": "female", "preview_url": "https://..." },
    {
      "id": "ID_DE_GERACAO_WSTUDIOS",
      "catalog_voice_id": "ID_OFICIAL_ELEVENLABS",
      "name": "Voz premium",
      "provider": "voice",
      "model_id": "eleven_multilingual_v2",
      "language": "en-US",
      "locale": "en-us",
      "gender": "female",
      "accent": "American",
      "category": "professional",
      "description": "Descrição disponível da voz",
      "use_case": "narration",
      "preview_url": "https://..."
    }
  ]
}
```
Use o campo `id` como `voice_id` na geração. O `provider` do item indica qual
`provider` enviar no `POST /api/v1/tts`. Use `?provider=edge`, `?provider=capcut`,
`?provider=minimax` ou `?provider=minimax-beta` para respostas menores.

Para `?provider=voice` (ou `?provider=elevenlabs`), a resposta contém todas
as vozes do ElevenLabs Ultra que estão disponíveis para gerar áudio no W
Studios. O `id` é sempre o ID pronto para enviar no TTS; `catalog_voice_id`
é o ID oficial da biblioteca, útil para o seu sistema identificar e atualizar
o catálogo sem precisar conhecer IDs internos de geração.

Vozes clonadas aparecem no provider MiniMax correspondente apenas para a
conta dona da API key (ou quando o clone é global). Quando disponível, o
`preview_url` do clone é uma URL assinada e temporária; reproduza-a de
imediato e consulte novamente o catálogo se expirar.

#### Biblioteca ElevenLabs V3

Como essa biblioteca possui milhares de vozes, consulte-a com paginação e
filtros. A chave e os endereços internos do provedor nunca são expostos:

```http
GET /api/v1/voices?provider=elevenlabs-v3&page=1&page_size=100&language=pt&gender=female&search=narracao
Authorization: Bearer wsv_live_xxx
```

A resposta inclui `voices`, `pagination` e `pricing`. Cada item devolve
`provider: "elevenlabs-v3"`; envie esse mesmo valor no POST de TTS. O campo
`preview_url` aponta para o proxy protegido da W Studio.

> **Os IDs das vozes podem mudar.** Não copie um ID de ElevenLabs/`voice` para
> sempre. Consulte `GET /api/v1/voices` ao iniciar a integração e atualize seu
> cache periodicamente. Envie sempre o `id` e o `provider` do mesmo item. Um ID
> antigo recebe `400` com `code: "VOICE_ID_OUTDATED"` e não consome créditos.

### 3.4 POST /api/v1/tts

Corpo da requisição:
```json
{
  "text": "Olá, isto é um teste.",
  "voice_id": "pt-BR-FranciscaNeural",
  "provider": "edge",
  "language": "pt-BR",
  "speed": 1.0
}
```

| Campo | Tipo | Obrigatório | Regras |
|-------|------|-------------|--------|
| `text` | string | sim | 1 a 200.000 caracteres (Edge: até 1.000.000) — aceita o roteiro inteiro |
| `voice_id` | string | sim | `id` vindo de `/api/v1/voices` |
| `provider` | string | sim | Use exatamente o valor retornado por `/api/v1/voices`: `voice`, `elevenlabs-v3`, `edge`, `minimax`, `capcut` ou `minimax-beta` (`elevenlabs` continua aceito como alias legado) |
| `model_id` | string | não | Se informado, use o `model_id` do mesmo item de `/api/v1/models`; combinações de outro provider retornam `400` |
| `language` | string | não | BCP-47 (ex.: `pt-BR`, `en-US`). Default: derivado da voz |
| `speed` | number | não | 0.5 a 1.5. Default: 1.0 |

Para `elevenlabs-v3`, use o modelo público `eleven_v3` retornado em
`GET /api/v1/models`. Controles como `stability`, `similarity_boost`, `style`,
`use_speaker_boost` e `with_transcript` continuam disponíveis; o limite
publicado para esse modelo é de 5.000 caracteres.

Resposta `200` — a tarefa pode já estar pronta:
```json
{
  "id": "957bde6f-d03b-4b98-be9a-8293e3817169",
  "status": "done",
  "url": "/generated/957bde6f-d03b-4b98-be9a-8293e3817169.mp3",
  "duration_seconds": 4.2,
  "credits_used": 28,
  "credits_remaining": 99972
}
```

Resposta `200` — tarefa assíncrona:
```json
{
  "id": "957bde6f-d03b-4b98-be9a-8293e3817169",
  "status": "doing",
  "url": null,
  "duration_seconds": 0,
  "credits_used": 100,
  "credits_remaining": 99900
}
```
Quando `status` é `doing`, **não** repita o `POST`. Use webhook (seção 5) ou
faça polling em `GET /api/v1/audio/{id}` (que devolve `202` enquanto processa).

### 3.5 GET /api/v1/audio/{id}

- `200 OK` → corpo é o binário do áudio, `Content-Type: audio/mpeg`. Salve em arquivo.
- `202 Accepted` → ainda processando (`{ "status": "doing" }`). Aguarde e tente de novo.
- `404 Not Found` → id inexistente ou não pertence à sua conta.

- `409 Conflict` — a geração falhou. Campos existentes `status`, `error`, `code` e `retryable` preservados; acrescentados `reason`, `provider` (nome público) e `refunded`.
  Exemplo: `{"status":"failed","error":"A geração de áudio falhou no provedor.","code":"GENERATION_FAILED","reason":"Motivo informado pelo provedor","provider":"capcut","refunded":false,"retryable":true}`.
  `reason` é string ou null; `refunded` só é true após confirmação do estorno.
  Pare o polling dessa tarefa: falha é terminal. `retryable` diz respeito a uma NOVA geração, nunca a repetir indefinidamente a consulta da tarefa falhada.
- `502 Bad Gateway` — falha real ao obter o áudio do upstream, não falha terminal de geração.

Mudança de outubro/2026: falhas terminais antes usavam HTTP 502; agora usam HTTP 409. Integrações que dependiam especificamente de 502 devem tratar também 409/`status=failed`. Endpoints, autenticação e campos antigos não foram removidos. Estornos novos usam log negativo `Estorno: geração falhou (<provedor>)`; falhas históricas sem estorno não são reembolsadas ao consultar a API.

### 3.6 GET /api/v1/history

Query: `?limit=50&offset=0` (limit 1–200). Use `?kind=image` para histórico do
gerador de fotos (itens retornam `model_id` e `prompt_preview`). Resposta `200`:
```json
{
  "items": [
    {
      "id": 151,
      "task_id": "957bde6f-...",
      "provider": "edge",
      "voice_id": "pt-BR-FranciscaNeural",
      "text_preview": "Olá, isto é um teste.",
      "credits_used": 1,
      "duration_ms": 902,
      "status": "done",
      "created_at": "2026-06-12T19:48:54.905Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

### 3.11 POST /api/v1/music

Gera música com o nosso motor **MiniMax AI Music**. Assíncrono, com o mesmo fluxo do TTS.

```http
POST https://wstudios.app/api/v1/music
Authorization: Bearer wsv_live_xxx
Content-Type: application/json

{
  "idea": "Sertanejo universitário animado sobre estrada e liberdade",
  "title": "Estrada"
}
```

**Duas formas de pedir a mesma música:**

| Forma | Campos | Exemplo |
|-------|--------|---------|
| Por descrição | `idea` (1 a 500 caracteres) e, se quiser, `instrumental: true` | `{ "idea": "Rock leve instrumental ao amanhecer", "instrumental": true }` |
| Com letra | `lyrics` (até 5000 caracteres) + `tags` (estilo, até 1000) | `{ "lyrics": "[verse]\n...", "tags": "sertanejo, 120 bpm" }` |

Campos aceitos:

| Campo | Obrigatório | Regra |
|-------|-------------|-------|
| `idea` | um dos dois | descrição da música, até 500 caracteres |
| `lyrics` | um dos dois | letra, até 5000 caracteres |
| `tags` | não | estilo/estrutura (ex.: `"sertanejo, 120 bpm"`) |
| `instrumental` | não | `true` = sem voz; não pode ir junto com `lyrics` |
| `title` | não | nome da faixa |
| `vocal_gender` | não | `"m"` ou `"f"` |
| `provider` | não | aceito por compatibilidade; o padrão é `"minimax"` |
| `model` | não | aceito por compatibilidade |
| `receive_url` | não | URL de callback (o webhook do painel continua valendo) |

**Resposta (assíncrona):**

```json
{
  "id": "task_1734567890_abc",
  "status": "doing",
  "provider": "minimax",
  "versions": 1,
  "url": null,
  "credits_used": 2500,
  "credits_remaining": 7500,
  "has_srt": false,
  "srt_url": null
}
```

Depois acompanhe em `GET /api/v1/audio/{id}` a cada ~15 segundos (o limite é 10 requisições a
cada 90 segundos por chave — polling mais rápido recebe `429`; quem tem webhook cadastrado é
avisado assim que termina, no evento `music.completed`). Quando o status for `done`, o mesmo
endpoint devolve o **arquivo de áudio**. Cada envio entrega as faixas geradas pela música.

**Custo: 2.500 créditos por envio.** Falha no provedor devolve os créditos automaticamente.

### 3.12 GET /api/v1/changelog

Lista as novidades da API (mais recentes primeiro) e permite marcar como
lidas. Serve para o seu sistema avisar o usuário final quando um recurso novo
entra no ar, sem depender de você ler documentação.

```http
GET https://wstudios.app/api/v1/changelog
Authorization: Bearer wsv_live_xxx
```

```json
{
  "success": true,
  "latest": "2026-10-01-avisos-automaticos",
  "total": 1,
  "unseen": ["2026-10-01-avisos-automaticos"],
  "items": [
    {
      "id": "2026-10-01-avisos-automaticos",
      "date": "2026-10-01",
      "type": "feature",
      "title": "Novo: geração de música com MiniMax AI Music na API",
      "summary": "A rota POST /api/v1/music agora aceita provider: \"minimax\".",
      "message": "Texto completo explicando a novidade...",
      "docs_url": "/api-docs",
      "action_suggestion": "Ative a opção \"MiniMax AI Music\" na sua interface de música."
    }
  ]
}
```

**Como o aviso chega sozinho (mecanismo `notices`):**

1. Toda resposta `/api/v1/*` inclui o campo `notices` na **primeira** chamada
   feita depois da publicação de uma novidade por aquela chave (aparece uma vez
   só — depois fica registrado e não volta).
2. Os headers abaixo acompanham toda resposta, mesmo as que não têm JSON
   (por exemplo download de áudio):

| Header | Significado |
|--------|-------------|
| `X-WStudios-API-Changelog` | id da novidade mais recente (ex.: `2026-10-01-avisos-automaticos`) |
| `X-WStudios-API-Changelog-Date` | data da novidade |
| `X-WStudios-API-Changelog-Title` | título curto (ASCII) |
| `X-WStudios-API-Changelog-URL` | `/api/v1/changelog` |
| `X-WStudios-API-News` | quantas novidades não vistas vieram nesta resposta |

3. Para marcar tudo como lido sem exibir nada (ex.: depois de mostrar o aviso ao
   usuário): `GET /api/v1/changelog?ack=1`.

**Sugestão de implementação no seu sistema:** guarde o valor de
`X-WStudios-API-Changelog` que você já mostrou; quando mudar, busque
`GET /api/v1/changelog`, mostre o `title` + `message` num aviso/modal do seu
produto e ofereça o recurso novo. O `action_suggestion` diz o que ativar.

---

### 3.13 POST /api/v1/voice-clone — Clonagem de voz

Cria uma voz clonada a partir de um áudio de exemplo. A voz fica disponível
**imediatamente** em `GET /api/v1/voices` e pode ser usada em `POST /api/v1/tts`
pelo `voice_id` retornado. Não é preciso usar o painel web: a clonagem é
totalmente programável, inclusive em lote.

**Dois modelos:**

| `model` | Nome comercial | Áudio aceito | `gender_tag` | Tempo típico |
|---------|----------------|--------------|--------------|--------------|
| `minimax-fast` | MiniMax AI Music Fast | MP3 até **20 MB** | **obrigatório** (`male`/`female`) | 10–40 s |
| `minimax-ultra` | MiniMax AI Ultra | MP3 até **5 MB** | opcional | 30 s – 3 min |

O servidor **normaliza o áudio para os primeiros 10 segundos** automaticamente
(repetindo em loop se o arquivo for menor que 10 s). Envie o trecho mais limpo
possível: **uma única voz**, sem música de fundo, sem ruído e sem outras pessoas
falando.

```http
POST https://wstudios.app/api/v1/voice-clone
Authorization: Bearer ***
Content-Type: multipart/form-data

file=@amostra.mp3
voice_name=Minha Voz 01
language_tag=Portuguese
gender_tag=female
model=minimax-fast
```

```bash
curl -X POST "https://wstudios.app/api/v1/voice-clone" \
  -H "Authorization: Bearer ***" \
  -F "file=@amostra.mp3" \
  -F "voice_name=Minha Voz 01" \
  -F "language_tag=Portuguese" \
  -F "gender_tag=female" \
  -F "model=minimax-fast"
```

**Campos:**

| Campo | Obrigatório | Regra |
|-------|-------------|-------|
| `file` | sim | MP3 (multipart, campo `file`) |
| `voice_name` | sim | 2 a 80 caracteres |
| `language_tag` | sim | **nome completo do idioma** (ver lista abaixo) |
| `gender_tag` | sim no `minimax-fast` | `male` ou `female` |
| `model` | não | `minimax-fast` (padrão) ou `minimax-ultra` |

**Resposta (200):**

```json
{
  "id": "2705228",
  "model": "minimax-fast",
  "voice_id": "2705228",
  "clone_code": "MMX-ABC116AB",
  "voice_name": "Minha Voz 01",
  "language_tag": "Portuguese",
  "gender_tag": "female",
  "preview_text": "Olá! Tudo bem? Eu sou a sua voz clonada...",
  "preview_url": "https://.../preview.mp3",
  "status": "completed"
}
```

Use o `voice_id` retornado direto no TTS:

```bash
curl -X POST "https://wstudios.app/api/v1/tts" \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"text":"Olá, esta é a minha voz clonada.","provider":"minimax","voice_id":"2705228"}'
```

**Idiomas aceitos em `language_tag`** (nome completo, com maiúscula):

`English`, `Chinese`, `Spanish`, `French`, `German`, `Italian`, `Portuguese`,
`Polish`, `Russian`, `Ukrainian`, `Czech`, `Slovak`, `Croatian`, `Serbian`,
`Bulgarian`, `Dutch`, `Romanian`, `Swedish`, `Norwegian`, `Afrikaans`,
`Catalan`, `Greek`, `Lithuanian`, `Latvian`, `Vietnamese`, `Indonesian`,
`Malay`, `Tagalog`, `Swahili`, `Hindi`, `Japanese`, `Korean`, `Danish`,
`Finnish`, `Hungarian`, `Slovenian`.

**Boas práticas para clonar em lote (várias vozes):**

1. Chame `POST /api/v1/voice-clone` uma vez por voz, **em sequência** (não em
   paralelo — o provedor processa uma por vez).
2. Guarde o `voice_id` e o `voice_name` retornados no seu banco. É por esse id
   que o TTS vai usar a voz depois.
3. Depois de clonar, confirme com `GET /api/v1/voices?provider=clone-fast`
   (ou `clone-ultra`) e procure `is_clone: true` — assim você mostra a lista
   atualizada para o usuário final.
4. Nome repetido não é bloqueado, mas dificulta a escolha na interface: use
   algo único (ex.: `Cliente 042 - Narrador`).

**Custos e limites:**

- A clonagem **não consome créditos** hoje.
- Não há limite de quantidade de vozes clonadas por conta.
- Erros comuns: `400` (arquivo faltando, formato diferente de MP3, idioma
  inválido, `gender_tag` ausente no fast), `402` (saldo insuficiente para usar
  no TTS depois), `503` (motor em manutenção).

**Diferença em relação ao limite de 1.500 caracteres:** esse limite existe
apenas no modelo ElevenLabs Ultra e vale para **geração de áudio**, não para
clonagem. A clonagem não tem relação com ele.


## 4. Custo em créditos

Custo base por **100 caracteres** de texto, alinhado ao site:

| Provider | Créditos / 100 chars |
|----------|----------------------|
| `edge` (Edge Microsoft Fast) | 70 |
| `minimax` (Minimax Fast) | 90 |
| `voice` / `elevenlabs` (Eleven Labs - Ultra Qualidade) | 100 |
| `elevenlabs-v3` | 85 (15% de desconto) |
| `capcut` (Capcut Ultra Qualidade) | 85 |
| `minimax-beta` (Minimax - Ultra Qualidade) | 130 |

Fórmula base: `custo = max(1, ceil(num_caracteres * multiplicador))`.
Exemplos: 250 chars no `minimax` → `ceil(250*0.9)` = **225**; 1000 chars no
`voice` → **1000**. Recursos extras do Edge podem acrescentar custo.

**Música:** **2.500 créditos por envio** no MiniMax AI Music. Os créditos só ficam
consumidos quando o provedor aceita a tarefa; se o provedor falhar, a devolução
é automática.

---

## 5. Webhooks (opcional, recomendado para `minimax`/`voice`)

O usuário cadastra uma URL no painel "API & Integrações" e o W Studio faz um
`POST` para essa URL quando a geração conclui — evita polling.

Eventos: `tts.completed`, `tts.failed`, `music.completed`, `music.failed`.

Headers enviados ao receiver:
```
Content-Type: application/json
X-WStudio-Event: tts.completed
X-WStudio-Delivery: <uuid único por entrega>
X-WStudio-Signature: <HMAC SHA-256 hex do corpo bruto>
```

Corpo (payload):
```json
{
  "event": "tts.completed",
  "delivered_at": "2026-06-12T19:48:55.334Z",
  "data": {
    "id": "957bde6f-...",
    "status": "done",
    "url": "/generated/957bde6f-...mp3",
    "duration_seconds": 4.2,
    "provider": "edge",
    "voice_id": "pt-BR-FranciscaNeural"
  }
}
```

**Validação da assinatura (obrigatória por segurança):** recompute o HMAC
SHA-256 do corpo bruto recebido usando o `secret` do webhook (mostrado no
painel ao criar) e compare com o header `X-WStudio-Signature`.

Node.js:
```js
const crypto = require('crypto');
function verify(secret, rawBody, signature) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
}
```

Python:
```python
import hmac, hashlib
def verify(secret: str, raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

Responda `200` ao webhook em até 10s. Não-2xx é considerado falha (sem retry
automático). Use `X-WStudio-Delivery` para deduplicar.

---

## 6. Rate limit

- **10 requisições a cada 90 segundos por chave.** Excedeu → `429`.
- Toda resposta traz: `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
  `X-RateLimit-Reset` (epoch em segundos). Ao receber `429`, espere até o
  `X-RateLimit-Reset` antes de tentar de novo.

---

## 7. Códigos de erro

Todo erro retorna JSON `{ "error": "mensagem" }` (alguns campos extras):

| Status | Significado | Ação |
|--------|-------------|------|
| `400` | Parâmetros inválidos (texto vazio/acima do limite, provider inválido, voice_id vazio) | Corrigir o corpo |
| `401` | Sem chave / chave inválida ou revogada | Verificar `Authorization` |
| `402` | Saldo de créditos insuficiente (`credits_needed`, `credits_available`) | Recarregar créditos |
| `404` | Áudio não existe ou não pertence à conta | Verificar o `id` |
| `429` | Rate limit excedido | Aguardar `X-RateLimit-Reset` |
| `400` + `VOICE_ID_OUTDATED` | ID de voz removido/alterado pelo provedor | Consultar `/api/v1/voices` novamente; não repetir com o mesmo ID |
| `502` | Falha do provedor externo | Ler `retryable`; se `true`, aguardar e tentar novamente |
| `503` | Provedor temporariamente indisponível | Tentar mais tarde ou escolher outra voz/provider |

---

## 8. Exemplos de código prontos

> Em todos os exemplos, defina a chave numa variável de ambiente `WSV_API_KEY`
> e a base em `WSV_BASE_URL` (default `https://wstudios.app`). Nunca coloque a chave
> diretamente no código.

### 8.1 cURL

```bash
# Listar vozes
curl "https://wstudios.app/api/v1/voices" \
  -H "Authorization: Bearer $WSV_API_KEY"

# Gerar áudio com uma voz retornada pelo catálogo
curl -X POST "https://wstudios.app/api/v1/tts" \
  -H "Authorization: Bearer $WSV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Olá mundo","voice_id":"pt-BR-FranciscaNeural","provider":"edge"}'

# Baixar o áudio gerado
curl -o saida.mp3 "https://wstudios.app/api/v1/audio/ID_RETORNADO" \
  -H "Authorization: Bearer $WSV_API_KEY"
```

### 8.2 n8n (HTTP Request)

1. Faça um `GET https://wstudios.app/api/v1/voices` com o header
   `Authorization: Bearer SUA_CHAVE`.
2. Selecione uma voz da resposta e guarde **juntos** `id` e `provider`.
3. No node **HTTP Request** de geração, use `POST https://wstudios.app/api/v1/tts`,
   `Content-Type: application/json` e body JSON:
   `{"text":"...","voice_id":"{{$json.id}}","provider":"{{$json.provider}}"}`.
4. Use o `id` retornado para consultar `GET /api/v1/audio/{id}`. O status `202`
   significa “ainda processando”; aguarde 2–5 segundos antes do próximo polling.
5. Se receber `VOICE_ID_OUTDATED`, volte ao passo 1. Não repita o POST com o
   mesmo ID antigo, pois ele não voltará a funcionar.

### 8.3 Python

```python
import os, time, requests

BASE = os.environ.get("WSV_BASE_URL", "https://wstudios.app")
KEY = os.environ["WSV_API_KEY"]
H = {"Authorization": f"Bearer {KEY}"}

def gerar_audio(texto, voice_id="pt-BR-FranciscaNeural", provider="edge"):
    r = requests.post(f"{BASE}/api/v1/tts", headers=H, json={
        "text": texto, "voice_id": voice_id, "provider": provider
    }, timeout=60)
    if r.status_code == 402:
        raise RuntimeError("Sem créditos suficientes")
    r.raise_for_status()
    job = r.json()

    # Provider assíncrono: faz polling no download até ficar pronto
    audio_id = job["id"]
    for _ in range(60):
        a = requests.get(f"{BASE}/api/v1/audio/{audio_id}", headers=H, timeout=60)
        if a.status_code == 200:
            return a.content          # bytes do MP3
        if a.status_code == 202:
            time.sleep(2); continue   # ainda processando
        a.raise_for_status()
    raise TimeoutError("Áudio não ficou pronto a tempo")

if __name__ == "__main__":
    mp3 = gerar_audio("Olá, integração via API")
    open("saida.mp3", "wb").write(mp3)
    print("Áudio salvo em saida.mp3")
```

### 8.4 Node.js (fetch nativo, Node 18+)

```js
const fs = require("fs");
const BASE = process.env.WSV_BASE_URL || "https://wstudios.app";
const KEY = process.env.WSV_API_KEY;
const H = { Authorization: `Bearer ${KEY}` };

async function gerarAudio(texto, voiceId = "pt-BR-FranciscaNeural", provider = "edge") {
  const r = await fetch(`${BASE}/api/v1/tts`, {
    method: "POST",
    headers: { ...H, "Content-Type": "application/json" },
    body: JSON.stringify({ text: texto, voice_id: voiceId, provider })
  });
  if (r.status === 402) throw new Error("Sem créditos suficientes");
  if (!r.ok) throw new Error(`Erro ${r.status}: ${await r.text()}`);
  const job = await r.json();

  for (let i = 0; i < 60; i++) {
    const a = await fetch(`${BASE}/api/v1/audio/${job.id}`, { headers: H });
    if (a.status === 200) return Buffer.from(await a.arrayBuffer());
    if (a.status === 202) { await new Promise(s => setTimeout(s, 2000)); continue; }
    throw new Error(`Erro ${a.status}`);
  }
  throw new Error("Áudio não ficou pronto a tempo");
}

(async () => {
  const mp3 = await gerarAudio("Olá, integração via API");
  fs.writeFileSync("saida.mp3", mp3);
  console.log("Áudio salvo em saida.mp3");
})();
```

### 8.5 PHP

```php
<?php
$BASE = getenv('WSV_BASE_URL') ?: 'https://wstudios.app';
$KEY  = getenv('WSV_API_KEY');

function post_json($url, $key, $payload) {
  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", "Content-Type: application/json"],
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_TIMEOUT => 60,
  ]);
  $body = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  return [$code, $body];
}

list($code, $body) = post_json("$BASE/api/v1/tts", $KEY, [
  "text" => "Olá mundo", "voice_id" => "pt-BR-FranciscaNeural", "provider" => "edge"
]);
if ($code === 402) { exit("Sem créditos\n"); }
$job = json_decode($body, true);

// Baixa o áudio (com polling simples)
for ($i = 0; $i < 60; $i++) {
  $ch = curl_init("$BASE/api/v1/audio/" . $job["id"]);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $KEY"],
  ]);
  $audio = curl_exec($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  if ($code === 200) { file_put_contents("saida.mp3", $audio); echo "Salvo\n"; break; }
  if ($code === 202) { sleep(2); continue; }
  exit("Erro $code\n");
}
```

---

## 9. Checklist de integração (para a IA seguir)

1. Ler `WSV_API_KEY` e `WSV_BASE_URL` de variáveis de ambiente.
2. (Opcional) Chamar `GET /api/v1/account` para validar a chave e mostrar o saldo.
3. Chamar `GET /api/v1/voices` e deixar o usuário escolher uma voz (ou fixar uma).
4. Implementar `gerarAudio(texto, voice_id, provider)` chamando `POST /api/v1/tts`.
5. Tratar `status: "doing"` com polling em `GET /api/v1/audio/{id}` (202 → aguardar) ou via webhook.
6. Salvar/streamar os bytes `audio/mpeg` retornados.
7. Tratar erros `400/401/402/429/500` com mensagens claras.
8. Respeitar o rate limit (60/min) — backoff ao receber `429`.

---

*Documento gerado automaticamente pelo W Studio. Base URL: `https://wstudios.app`. Versão da API: 2.2.0.*


## Eleven Labs V4 e V4 Turbo — beta (aditivo)
Mesma chave Bearer e mesmos endpoints existentes. Modelos antigos e defaults preservados.
Escolha explícita: `provider: "ultra-v4"`, `model_id: "eleven_v4"` ou `"eleven_v4_turbo"`.
POST `/api/v1/tts` com `text` (1–50.000 caracteres), `voice_id` oficial da biblioteca V4, `voice_settings` e header `Idempotency-Key` único por roteiro. Repita a MESMA chave e conteúdo para reconciliar sem novo débito; divergência retorna 409. Também aceita `request_id` no corpo.
POST `/api/v1/tts/estimate` aceita o mesmo corpo, não gera e retorna custo, partes e promoção. Consulte `GET /api/v1/voices?provider=ultra-v4` com sua API key. Use ID oficial ElevenLabs de 20 caracteres; não copie dp_id/UUID de outros motores.
Preço por caractere: V4 4 créditos (promoção 2), Turbo 8 (promoção 6). Promoção válida até 30/10/2026 inclusive, America/Sao_Paulo; preço cheio a partir de 31/10/2026 00:00 -03:00. Valor congelado no aceite. Administrador isento não recebe estorno.
O fornecedor aceita até 5.000 caracteres por chamada. Divisão interna sem perda de texto, mesma voz e controles; um único ID, um item no histórico, MP3 final único em GET `/api/v1/audio/:id`. Intervalo interno 0,5s, sem silêncio externo adicionado e sem fades. Poll 202 durante processamento, 200 audio/mpeg ao concluir, 409 em falha terminal.
Partes concluídas são retomadas após restart; aceite upstream ambíguo NÃO é reenviado automaticamente. Falha encerra e estorna uma única vez somente o efetivamente debitado. Arquivo final retido por 24 horas. Beta: disponibilidade de voz depende do motor; sem promessa de qualidade/timbre igual entre partes.
Exemplo de corpo: `{"provider":"ultra-v4","model_id":"eleven_v4","voice_id":"NOpBlnGInO9m6vDvFkFC","text":"Seu roteiro integral","voice_settings":{"speed":1.0,"stability":0.5}}`.


### V4: IDs oficiais, erros e retomada segura
Para `provider: ultra-v4`, use `model_id: eleven_v4` ou `eleven_v4_turbo` e `voice_id` oficial ElevenLabs: exatamente 20 caracteres alfanuméricos. Consulte `GET /api/v1/voices?provider=ultra-v4` com a mesma API key. IDs internos/UUID só são traduzidos quando o catálogo existente identifica um único ID oficial; sem correspondência inequívoca são recusados antes de criar tarefa/debitar. Não copie UUIDs de outros motores. Limite: 50.000 caracteres por roteiro, dividido internamente em partes de até 5.000.

- HTTP 400 `INVALID_VOICE_ID` / `INVALID_MODEL_ID`: corrija o corpo; nenhuma tarefa ou cobrança criada.
- HTTP 400 `VOICE_ID_UNAVAILABLE`: voz não disponível no motor; sem retry automático. Atualize o catálogo/escolha outra voz.
- HTTP 503 `UPSTREAM_CREDITS`: saldo do motor indisponível; contate suporte (não é o seu saldo WStudios).
- `UPSTREAM_BUSY`: rejeição HTTP 429 ou 503 claramente por limite/concorrência: envio inicial + no máximo 3 retentativas, esperas 2s/5s/10s.
- `UPSTREAM_TRANSIENT`: outra resposta HTTP não-2xx; parte volta para fila e outro tick tenta novamente (30s), até 4 envios ou 6 ciclos de consulta por parte persistidos no banco. Uma única falha transitória não encerra o roteiro.
- HTTP 409 `AMBIGUOUS_SUBMIT`: timeout/queda sem resposta ou aceite sem identidade; pode ter sido aceito. Não crie nova chave nem reenvie automaticamente; contate suporte. Replay também é bloqueado.
- HTTP 409 `RETRY_EXHAUSTED`: teto persistido atingido; contate suporte.
- HTTP 409 `IDEMPOTENCY_CONFLICT`: mesma chave com conteúdo divergente.

Consulte `GET /api/v1/audio/:id`: 202 ainda pendente (pode incluir `code`, `reason`, `retryable`); erro terminal inclui código acionável, motivo, `provider_status`, `refunded`. Em falha recuperável, reenviar exatamente o mesmo corpo e a mesma `Idempotency-Key` retoma o mesmo job, partes prontas e IDs upstream conhecidos, sem nova cobrança ou repetição de partes. Mudança de corpo mantém 409. Pedidos de aceite ambíguo e limite esgotado NÃO são retomados. Estorno já aplicado não é removido, repetido ou cobrado de novo. Não rode polling de falha terminal indefinidamente.


## Legendas SRT e fila V4

`GET /api/v1/audio/{id}/srt` usa a mesma API key da tarefa. `?format=json` devolve `task_id`, `srt`, `srt_mode` (`native` ou `approximate`) e, quando aproximado, `duration_estimated`. Sem format, entrega `application/x-subrip` UTF-8 com BOM. HTTP 202 = ainda processando; 404 = indisponível, excluída, sem letra ou de outro titular; 502 = falha temporária de consulta. Nos motores com SRT local, tempos são proporcionais ao texto e à duração real ou estimada, não alinhamento de fala. Música usa a letra e duração da primeira faixa; instrumental sem letra não recebe legenda inventada.

V4 e V4 Turbo aceitam até 50.000 caracteres com voz oficial de 20 caracteres alfanuméricos. Envie `Idempotency-Key` no POST `/api/v1/tts`; corpo igual reaproveita o pedido, diferente retorna 409. Use `/api/v1/tts/estimate` antes do envio: preço vem da estimativa e fica congelado no aceite. A fila dedicada persiste o pedido (`waiting`, `doing`, `done`, `failed`), divide em partes internas até 5.000 caracteres e entrega um ID e um MP3. Não há posição pública nem SLA de espera. Partes não são pedidos novos; não envie novamente um aceite incerto. Consulte `/api/v1/audio/{id}` com espera e respeite 429/Retry-After; a consulta pode atualizar o estado da tarefa.

## MCP

Endpoint Streamable HTTP: `https://wstudios.app/mcp`. API key em Authorization Bearer ou OAuth com PKCE S256 e consentimento humano. Documentação: https://wstudios.app/mcp-docs.html. Nunca informe senha em ferramentas. Geração paga exige orçamento e aprovação explícita.
