# Hasos — API de chamadas de voz no WhatsApp

API REST para fazer e receber **ligações de voz pelo WhatsApp** a partir do seu produto: conecte um número de WhatsApp existente (QR Code ou código de pareamento), emita tokens para o discador, receba eventos por webhook e baixe gravações e transcrições. Sem chip novo, sem DID, sem custo por minuto.

- Base URL: `https://api.hasos.com.br`
- Autenticação: header `x-api-key: hs_live_…` (crie a chave no painel em Configurações → Chaves de API)
- Formato: JSON
- Servidor MCP para agentes de IA: `https://api.hasos.com.br/mcp` (ver https://hasos.com.br/mcp.md)
- Guia completo (HTML): https://hasos.com.br/guides

## Conceitos
- **Dispositivo / instância**: um número de WhatsApp conectado à Hasos. Cada número faz 1 chamada por vez (limite do WhatsApp); N números = N chamadas simultâneas.
- **Token de dispositivo**: credencial do discador (iframe/embed) para um número, sem expor a chave de API.
- **Webhook**: URL que recebe eventos assinados (HMAC SHA-256).

## Endpoints principais

| Método | Caminho | Descrição |
|---|---|---|
| POST | `/v1/instances` | Cria um dispositivo (número). Body opcional: `{ "phone": "5511999999999" }` |
| GET | `/v1/instances` | Lista os dispositivos da conta |
| GET | `/v1/instances/:id` | Detalhes e status do dispositivo |
| DELETE | `/v1/instances/:id` | Remove o dispositivo |
| GET | `/v1/instances/:id/qr` | QR Code (string) + `status` + `error` para vincular o WhatsApp |
| GET | `/v1/instances/:id/pairing-code` | Código de 8 caracteres para vincular sem QR |
| POST | `/v1/instances/:id/import-session` | Importa uma sessão Baileys existente (Evolution, WAHA-NOWEB) sem QR |
| POST | `/v1/instances/:id/restart` | Reinicia a conexão com o WhatsApp |
| POST | `/v1/instances/:id/tokens` | Emite um token do discador |
| POST | `/v1/instances/:id/webhook` | Define a URL do webhook do número (retorna o secret) |
| GET | `/v1/instances/:id/webhook/deliveries` | Entregas recentes do webhook |
| GET/PUT/DELETE | `/v1/webhook` | Webhook global da conta (todos os números) |
| POST | `/v1/webhook/test` | Envia um evento de teste |
| GET | `/v1/instances/:id/sip` | Credenciais SIP do número |
| POST | `/v1/instances/:id/sip/enable` | Liga/desliga o SIP (`{ "enabled": true }`, plano pago) |
| POST | `/v1/instances/:id/sip/regenerate` | Gera nova senha SIP |
| GET | `/v1/calls` | Histórico de chamadas da conta |
| GET | `/v1/live-calls` | Chamadas em andamento |
| GET | `/v1/calls/:id/recording` | Link assinado da gravação |
| GET | `/v1/recordings/:id/transcript` | Transcrição + resumo por IA (Pacote IA) |
| GET | `/v1/analytics` | Métricas agregadas (taxa de atendimento, volume…) |
| GET | `/v1/leads` · POST `/v1/leads/bulk` · POST `/v1/leads/status` | Leads do funil |
| GET | `/v1/api-keys` | Lista chaves (mascaradas) |

## Conectar um número (QR Code)
```js
const KEY = "hs_live_sua_chave";
const { id } = await fetch("https://api.hasos.com.br/v1/instances", {
  method: "POST", headers: { "x-api-key": KEY, "Content-Type": "application/json" }, body: "{}",
}).then((r) => r.json());

// Faça polling de /qr a cada 2–3 s e re-renderize o QR (ele muda a cada ~20 s).
const poll = setInterval(async () => {
  const { qr, status, error } = await fetch(`https://api.hasos.com.br/v1/instances/${id}/qr`, {
    headers: { "x-api-key": KEY },
  }).then((r) => r.json());
  if (status === "CONNECTED") { clearInterval(poll); return; }
  if (qr) renderizarQR(qr); // desenhe a string como QR
}, 2500);
```
No celular: WhatsApp → Configurações → Aparelhos conectados → Conectar aparelho. Alternativa sem câmera: `GET /v1/instances/:id/pairing-code` e "Conectar com número de telefone".

## Fazer e receber chamadas
As chamadas são feitas pelo **discador** (WebRTC no navegador), autenticado por token de dispositivo:
- **Iframe:** `<iframe src="https://phone.hasos.com.br/?token=TOKEN" allow="microphone">`
- **Headless (sua UI):**
```html
<script src="https://phone.hasos.com.br/embed.js"></script>
<script>
  const phone = HasosPhone.create({ token: "TOKEN", allowedOrigin: "https://seu-app.com" });
  await phone.ready();
  phone.call.start("5511999998888");          // ligar
  phone.on("offer:received", () => phone.call.accept()); // atender
  phone.on("call:ended", (c) => console.log(c));
</script>
```
- **SIP:** registre um softphone ou PABX em `sip.hasos.com.br` com as credenciais de `/v1/instances/:id/sip`.

## Webhooks
Cada entrega traz `x-webhook-signature: sha256=<HMAC>` — HMAC SHA-256 do corpo bruto com o secret.

```json
{
  "event": "CALL",
  "instanceId": "a1b2c3d4",
  "phone": "5511988887777",
  "timestamp": "2026-07-01T13:14:41.077Z",
  "data": {
    "type": "ended",
    "callId": "00ED0D44…",
    "direction": "OUTGOING",
    "peerPhone": "5511999999999",
    "durationSec": 23,
    "endReason": "user_ended"
  }
}
```
Eventos: `CALL` (`ringing` | `answered` | `ended`), `RECORD` (gravação pronta, com URL assinada por 7 dias), `TRANSCRIPT` (transcrição + resumo), `DEVICE` (status do número).

### Valores de `endReason` (evento CALL `ended`)
| Valor | Significado |
|---|---|
| `user_ended` | Desligada normalmente por um dos lados |
| `declined` | Recusada |
| `busy` | Ocupado |
| `timeout` | Ninguém atendeu, ou encerrada por tempo pela plataforma |
| `cancelled` | Quem ligou cancelou antes de atenderem |
| `failed` | Falha técnica (conexão ou áudio) |
| `do_not_disturb` | Contato em modo não perturbe |
| `after_hours` | Recebida fora do horário de atendimento, recusada automaticamente |
| `unknown` | Não identificado |

Para saber se foi atendida use `durationSec > 0`. Trate valores fora da lista como `unknown`.

## Erros comuns
| HTTP | `error` | Quando |
|---|---|---|
| 401 | `Unauthorized` | Chave ausente ou inválida |
| 402 | `TRIAL_REQUIRED` | Conta nova sem teste/assinatura ativa tentando criar número |
| 402 | `DEVICE_LIMIT` | Limite de números do plano atingido |
| 402 | `ACCOUNT_SUSPENDED` | Pagamento em atraso |
| 403 | `plan_required` | Recurso de plano pago (ex.: SIP) |

As respostas de erro trazem `message` em português.
