# piv.day API Reference

Full reference for the piv.day REST API: every endpoint, request body, response shape, error code and webhook. Exported from https://piv.day/api/docs for use with AI assistants.

---

## Getting started

REST API no plano Business. Tokens Bearer, JSON, endpoints idempotentes, códigos de erro previsíveis. Cada endpoint inclui um exemplo de requisição e resposta.

### Base URL

```
https://app.piv.day/api/v1
```

Todos os endpoints ficam sob este prefixo. Um único ambiente — sem hosts de staging separados.

### Autenticação

```
Authorization: Bearer YOUR_API_KEY
```

As chaves são criadas no dashboard em `Configurações → API Keys`. Cada chave possui escopos granulares (leitura de números, envio de SMS, compra de proxies, etc.). Um token comprometido é rotacionado com um clique — sem necessidade de recadastrar o time.

### Conta

**200 OK**
```
curl https://app.piv.day/api/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Um único endpoint utilitário — saldo e email da conta atual. Útil para confirmar que a chave funciona e que a autenticação está configurada corretamente.**
```
{
  "success": true,
  "data": {
    "user_id": "usr-uuid-...",
    "balance": 150.50,
    "email": "u***@example.com",
    "team_id": null,
    "is_team_member": false
  }
}
```

GET /api/v1/account

### Formato de resposta

```
{
  "success": true,
  "data": { ... }
}
```

Todas as respostas são JSON. Em caso de erro, `success` é `false` e o corpo contém `error.code` e `error.message`.

### Rate limits

**100 requisições por minuto por API key. O limite é compartilhado entre todos os endpoints. Precisa de mais capacidade para um pico? Fale com o suporte no Telegram e aumentaremos o limite.

Ao exceder o limite, você recebe `429 RATE_LIMITED` e o header `Retry-After` — quantos segundos aguardar antes da próxima chamada.**
```
HTTP/1.1 429 Too Many Requests
Retry-After: 12

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded, retry in 12s"
  }
}
```

429 Too Many Requests

## Números

Compre, renove e restaure números. Receba e envie SMS. Inicie verificações Google QR. Assine eventos via webhooks.

### Países e preços

`GET /api/v1/numbers/countries`

Retorna países disponíveis para compra com o preço atual e recursos de SMS. Todos os preços incluem o desconto da sua assinatura.

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `country_code` | string | Código de país ISO de duas letras (ex.: `SE`). |
| `price_per_month` | number | Preço final para seu plano — o que é cobrado na compra. Descontos Premium / Business já estão aplicados. |
| `base_price` | number | Preço sem descontos (plano Free). Retornado para comparação para ver quanto a assinatura economiza. |
| `can_send_sms` | boolean | Se o envio de SMS de saída é suportado a partir deste número. |
| `can_receive_sms` | boolean | Se o recebimento de SMS de entrada é suportado. |
| `sms_send_price` | number\|null | Preço por SMS de saída, também com o desconto do plano aplicado. `null` quando o envio não é suportado neste país. |

**curl**
```bash
curl https://app.piv.day/api/v1/numbers/countries \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": [
    {
      "country_code": "SE",
      "price_per_month": 4.00,
      "base_price": 5.00,
      "can_send_sms": true,
      "can_receive_sms": true,
      "sms_send_price": 0.25
    },
    {
      "country_code": "GB",
      "price_per_month": 3.00,
      "base_price": 3.00,
      "can_send_sms": true,
      "can_receive_sms": true,
      "sms_send_price": 0.20
    }
  ]
}
```

### Listar números

`GET /api/v1/numbers`

Retorna uma página de números da conta com filtros por país, status e busca por número ou nome personalizado.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Tamanho da página (padrão 50, máximo 200). |
| `offset` | integer | Offset de paginação. |
| `country` | string | Filtro de código de país ISO. |
| `status` | string | Um de `active`, `expired`, `pending_restore`. |
| `search` | string | Busca por substring no número de telefone ou nome personalizado. |

**curl**
```bash
curl "https://app.piv.day/api/v1/numbers?country=SE&status=active&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "numbers": [
      {
        "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
        "phone_number": "+46764794425",
        "country_code": "SE",
        "status": "active",
        "created_at": "2026-05-01T10:00:00Z",
        "expires_at": "2026-05-31T10:00:00Z",
        "auto_renew": false,
        "custom_name": "Office line",
        "purchased_at": "2026-05-01T10:00:00Z",
        "next_renewal_date": "2026-05-31T10:00:00Z",
        "tags": ["support"],
        "can_send_sms": true,
        "can_receive_sms": true
      }
    ],
    "pagination": { "total": 1, "limit": 10, "offset": 0 }
  }
}
```

### Obter um número por ID

`GET /api/v1/numbers/{piv_num_id}`

Registro completo do número: status, datas, renovação automática, tags, direções SMS permitidas.

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não pertence à conta. |

**curl**
```bash
curl https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "phone_number": "+46764794425",
    "country_code": "SE",
    "status": "active",
    "created_at": "2026-05-01T10:00:00Z",
    "expires_at": "2026-05-31T10:00:00Z",
    "auto_renew": false,
    "custom_name": "Office line",
    "purchased_at": "2026-05-01T10:00:00Z",
    "next_renewal_date": "2026-05-31T10:00:00Z",
    "tags": ["support"],
    "can_send_sms": true,
    "can_receive_sms": true
  }
}
```

### Comprar um número

`POST /api/v1/numbers/purchase`

Compra um único número no país selecionado pelo período especificado. O custo é debitado do saldo da conta. Retorna `piv_num_id` usado em todas as operações posteriores do número.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `country_code` * | string | Código de país ISO. |
| `duration_months` * | integer | Período de aluguel em meses (mínimo 1). |
| `auto_renew` * | boolean | Ativar renovação automática logo após a compra. |
| `custom_name` | string | Nome amigável opcional para o número. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `COUNTRY_NOT_AVAILABLE` | 400 | País não disponível ou sem preços. |
| `NO_NUMBERS_AVAILABLE` | 400 | Não há números disponíveis no país solicitado no momento. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para a compra. |
| `VALIDATION_ERROR` | 400 | Campos inválidos no corpo da requisição. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/numbers/purchase \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "country_code": "SE",
    "duration_months": 1,
    "auto_renew": false,
    "custom_name": "My Sweden Number"
  }'
```

**200 OK**
```json
{
  "success": true,
  "cost": 5.00,
  "numbers": [
    {
      "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
      "country_code": "SE",
      "phone_number": "+46764794425",
      "created_at": "2026-05-01T10:00:00Z",
      "expires_at": "2026-05-31T10:00:00Z",
      "auto_renew": false,
      "custom_name": "My Sweden Number"
    }
  ]
}
```

### Renovar um número

`POST /api/v1/numbers/{piv_num_id}/renew`

Estende um número ativo pelo período especificado. O custo é debitado no momento da chamada.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `duration_months` * | integer | Número de meses a adicionar. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não pertence à conta. |
| `COUNTRY_NOT_AVAILABLE` | 400 | Nenhum preço encontrado para o país do número. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para renovar. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/renew \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "duration_months": 1 }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "phone_number": "+46764794425",
    "old_expires_at": "2026-05-31T10:00:00Z",
    "new_expires_at": "2026-06-30T10:00:00Z",
    "cost": 5.00
  }
}
```

### Atualizar um número

`PATCH /api/v1/numbers/{piv_num_id}`

Atualmente apenas o nome personalizado pode ser atualizado.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `custom_name` * | string | Novo nome para o número. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não pertence à conta. |
| `VALIDATION_ERROR` | 400 | Valor inválido no corpo da requisição. |

**curl**
```bash
curl -X PATCH https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "custom_name": "Support line" }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "phone_number": "+46764794425",
    "country_code": "SE",
    "status": "active",
    "custom_name": "Support line",
    "auto_renew": false,
    "expires_at": "2026-05-31T10:00:00Z",
    "tags": []
  }
}
```

### Gerenciar renovação automática

`PATCH /api/v1/numbers/{piv_num_id}/auto-renewal`

Ativa ou desativa a renovação automática do número. Quando ativada, a renovação é debitada automaticamente 24 horas antes do vencimento. Se o saldo for insuficiente, o número expira; você pode recuperá-lo via `POST /numbers/restore` dentro de 7 dias.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `auto_renew` * | boolean | Novo valor do sinalizador de renovação automática. |

**curl**
```bash
curl -X PATCH https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/auto-renewal \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "auto_renew": true }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "phone_number": "+46764794425",
    "auto_renew": true
  }
}
```

### Restaurar números expirados

`POST /api/v1/numbers/restore`

Recupera números expirados dentro de um período de 7 dias. Passe um array de `piv_num_id` — os números retornam com histórico SMS e configurações preservadas. A restauração custa mais do que comprar um número novo (inclui um multiplicador de restauração) — os valores exatos aparecem no dashboard antes da confirmação. O status final de cada número chega via webhook `number.restore_completed`; reembolsos por números malsucedidos são creditados automaticamente.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `piv_num_ids` * | string[] | Array de IDs de números para restaurar. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NO_RESTORABLE_NUMBERS` | 404 | Nenhum dos números fornecidos pode ser restaurado (período de 7 dias expirou ou não são seus). |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para cobrir a restauração. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/numbers/restore \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "piv_num_ids": [
      "vzPA1-kHKSg-EAL7e-Jqd3o",
      "abc12-defgh-ijklm-nopqr"
    ]
  }'
```

**200 OK**
```json
{
  "success": true,
  "queued": 2,
  "skipped": 0,
  "total_charged": 9.00,
  "numbers": [
    {
      "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
      "phone_number": "+46764794425",
      "status": "pending"
    }
  ]
}
```

### Histórico de SMS do número

`GET /api/v1/numbers/{piv_num_id}/sms`

Retorna mensagens de entrada e saída do número em ordem cronológica reversa.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Quantas mensagens retornar (padrão 50, máximo 200). |
| `offset` | integer | Offset de paginação. |

**curl**
```bash
curl "https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/sms?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "messages": [
      {
        "id": "42",
        "from_number": "+46764794425",
        "to_number": "+14155551234",
        "message_body": "Hello from piv.day",
        "direction": "outbound",
        "status": "delivered",
        "received_at": "2026-05-22T11:30:00Z"
      },
      {
        "id": "41",
        "from_number": "+14155551234",
        "to_number": "+46764794425",
        "message_body": "Hey, got your message!",
        "direction": "inbound",
        "status": "received",
        "received_at": "2026-05-22T11:35:00Z"
      }
    ],
    "pagination": { "total": 2, "limit": 20, "offset": 0 }
  }
}
```

### Enviar SMS

`POST /api/v1/numbers/{piv_num_id}/sms/send`

Disponível para países onde o envio de saída é permitido (ex.: CA, GB, SE). O custo da mensagem é debitado no momento do envio.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `to_number` * | string | Número do destinatário no formato internacional (E.164). |
| `message_body` * | string | Texto da mensagem. GSM-7 e UCS-2 são suportados. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não pertence à conta. |
| `NUMBER_EXPIRED` | 403 | O número já expirou. |
| `NUMBER_NOT_ACTIVE` | 400 | O número está em um estado que não permite envio. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para enviar. |
| `INVALID_MESSAGE_FORMAT` | 400 | O corpo da mensagem excede o comprimento permitido ou contém caracteres proibidos. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/sms/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to_number": "+14155551234",
    "message_body": "Hello from piv.day"
  }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "message_id": "42",
    "from_number": "+46764794425",
    "to_number": "+14155551234",
    "message_body": "Hello from piv.day",
    "status": "queued",
    "created_at": "2026-05-22T11:30:00Z"
  }
}
```

### Iniciar verificação Google QR

`POST /api/v1/numbers/{piv_num_id}/verify`

Inicia uma verificação Google QR usando a URL fornecida. O resultado — sucesso ou falha — chega via webhook verify.completed ou verify.failed. O custo é debitado quando a tarefa é enfileirada.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `gv_url` * | string | URL completa de verificação do Google (retirada da página do Google). |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não é seu. |
| `NUMBER_NOT_ACTIVE` | 400 | O número não está ativo. |
| `SMS_DISABLED` | 400 | O envio de SMS está desativado para este número. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para a verificação. |
| `PROXY_DEAD` | 502 | Proxy indisponível — saldo reembolsado. |
| `QUEUE_FULL` | 503 | Fila cheia, tente mais tarde — saldo reembolsado. |
| `SERVER_UNAVAILABLE` | 503 | Servidor de automação indisponível — saldo reembolsado. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/numbers/vzPA1-kHKSg-EAL7e-Jqd3o/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "gv_url": "https://gv.google.com/..." }'
```

**202 Accepted**
```json
{
  "success": true,
  "message": "Verification initiated"
}
```

## Proxy

IPv6 residencial em dezenas de países. Compra em massa, renovação, Restore mantendo o mesmo login e senha, exportação no formato desejado.

### Lista de servidores

`GET /api/v1/proxy/servers`

Servidores proxy disponíveis com preços.

**curl**
```bash
curl https://app.piv.day/api/v1/proxy/servers \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "servers": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "name": "EU-DE-01",
        "country": "DE",
        "socks5_port": 1080,
        "http_port": 3128,
        "price_per_proxy": 0.50
      },
      {
        "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
        "name": "EU-NL-01",
        "country": "NL",
        "socks5_port": 1080,
        "http_port": 3128,
        "price_per_proxy": 0.45
      }
    ]
  }
}
```

### Lista de pedidos

`GET /api/v1/proxy/orders`

Lista dos seus pedidos de proxy com filtros por status ou pesquisa.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Tamanho da página. Padrão 100, máximo 500. |
| `offset` | integer | Deslocamento de paginação. |
| `status` | string | Filtro por status: `active`, `expired`. |
| `search` | string | Pesquisa em pedidos. |

**curl**
```bash
curl "https://app.piv.day/api/v1/proxy/orders?status=active&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": "ord-uuid-...",
        "country": "DE",
        "quantity": 10,
        "price_per_proxy": 0.50,
        "price_total": 5.00,
        "status": "active",
        "purchased_at": "2026-01-01T00:00:00Z",
        "expires_at": "2026-02-01T00:00:00Z"
      }
    ],
    "pagination": { "total": 1, "limit": 20, "offset": 0 }
  }
}
```

### Obter um pedido

`GET /api/v1/proxy/orders/{id}`

Obter os detalhes de um pedido específico, incluindo a lista de credenciais de proxy.

**curl**
```bash
curl https://app.piv.day/api/v1/proxy/orders/ord-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "order": {
      "id": "ord-uuid-...",
      "country": "DE",
      "quantity": 10,
      "price_per_proxy": 0.50,
      "price_total": 5.00,
      "status": "active",
      "purchased_at": "2026-01-01T00:00:00Z",
      "expires_at": "2026-02-01T00:00:00Z",
      "created_at": "2026-01-01T00:00:00Z",
      "items": [
        {
          "login": "user1",
          "password": "pass1",
          "host": "185.1.2.3",
          "socks5_port": 1080,
          "http_port": 3128,
          "ipv6": "2a00:1:2:3::1"
        }
      ]
    }
  }
}
```

### Comprar proxies

`POST /api/v1/proxy/purchase`

Comprar proxies no servidor escolhido. Uma requisição cria um pedido com N proxies (até 500). Não há pedidos em massa — chame o método novamente se precisar de mais.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `server_id` * | string | ID do servidor de `GET /proxy/servers`. |
| `quantity` * | integer | Quantos proxies comprar (1–500). |
| `months` * | integer | Período de locação em meses (1–12). |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente. |
| `NOT_FOUND` | 404 | Servidor proxy não encontrado ou inativo. |
| `VALIDATION_ERROR` | 400 | Corpo da requisição inválido. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/proxy/purchase \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "quantity": 5,
    "months": 1
  }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "order": {
      "id": "ord-uuid-...",
      "country": "DE",
      "quantity": 5,
      "price_total": 2.50,
      "status": "active",
      "expires_at": "2026-02-01T00:00:00Z",
      "created_at": "2026-01-01T00:00:00Z",
      "items": [
        {
          "login": "user1",
          "password": "pass1",
          "host": "185.1.2.3",
          "socks5_port": 1080,
          "http_port": 3128,
          "ipv6": "2a00:1:2:3::1"
        },
        {
          "login": "user2",
          "password": "pass2",
          "host": "185.1.2.3",
          "socks5_port": 1080,
          "http_port": 3128,
          "ipv6": "2a00:1:2:3::2"
        }
      ]
    }
  }
}
```

### Renovar um pedido

`POST /api/v1/proxy/orders/{id}/renew`

Estender um pedido de proxy ativo por um número de meses.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `months` * | integer | Meses a adicionar. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../renew \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "months": 1 }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "order_id": "ord-uuid-...",
    "cost": 5.00,
    "new_expires_at": "2026-03-01T00:00:00Z"
  }
}
```

### Restore de pedido vencido

`POST /api/v1/proxy/orders/{id}/restore`

Recuperar um pedido de proxy vencido sem recompra. Login, senha, endereços IPv6, país e vinculações — os mesmos. O prazo é estendido por 1 mês. Sem necessidade de reconfigurar o antidetect.

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../restore \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "order_id": "ord-uuid-...",
    "quantity": 10,
    "total_cost": 5.00,
    "expires_at": "2026-03-01T00:00:00Z"
  }
}
```

### Exportar proxies

`GET /api/v1/proxy/orders/{id}/export`

Exportar as credenciais de proxy do pedido como uma lista de texto (login:password@host:port).

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `format` | string | Formato de exportação: `socks5`, `http` ou `both` (padrão `socks5`). |

**curl**
```bash
curl "https://app.piv.day/api/v1/proxy/orders/ord-uuid-.../export?format=socks5" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "content": "user1:pass1@1.2.3.4:1080\nuser2:pass2@1.2.3.4:1080",
    "filename": "piv-day-proxy-ord-uuid--socks5.txt",
    "format": "socks5",
    "count": 2
  }
}
```

## Domínios

Pesquisa e registro sem KYC, Cloudflare e SSL automáticos, gerenciamento de registros DNS e servidores NS via API.

### Verificação de disponibilidade

`GET /api/v1/domains/search`

Verificar se um domínio está disponível e quanto custa registrá-lo.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `q` * | string | Nome de domínio para pesquisar, ex.: `mysite.com`. |

**curl**
```bash
curl "https://app.piv.day/api/v1/domains/search?q=mysite.com" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "domain": "mysite.com",
    "available": true,
    "create_price": 12.50,
    "renew_price": 12.50,
    "currency": "USD"
  }
}
```

### Listar domínios

`GET /api/v1/domains`

Lista dos seus domínios registrados com filtragem.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Tamanho da página. Padrão 100, máximo 500. |
| `offset` | integer | Offset de paginação. |
| `status` | string | Status do domínio: `active`, `pending`, `expired`. |
| `cloudflare` | string | Filtrar por status do Cloudflare: `true` ou `false`. |
| `auto_renew` | string | Filtrar por renovação automática: `true` ou `false`. |
| `search` | string | Pesquisar por nome de domínio. |

**curl**
```bash
curl "https://app.piv.day/api/v1/domains?status=active&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "domains": [
      {
        "id": "dom-uuid-...",
        "domain_name": "mysite.com",
        "status": "active",
        "registered_at": "2026-01-01T00:00:00Z",
        "expires_at": "2027-01-01T00:00:00Z",
        "auto_renew": true,
        "cloudflare_enabled": true,
        "cloudflare_status": "active",
        "ssl_type": "lets_encrypt",
        "ssl_status": "active",
        "ssl_mode": "full",
        "ssl_expires_at": "2026-04-01T00:00:00Z",
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z"
      }
    ],
    "pagination": { "total": 1, "limit": 20, "offset": 0 }
  }
}
```

### Registrar um domínio

`POST /api/v1/domains`

Registrar um novo domínio. A requisição é assíncrona — retorna `queue_id` para rastreamento. Cloudflare ATIVADO: não passe `nameservers`; opcionalmente pode passar `dns_records` e `ssl_mode`. Cloudflare DESATIVADO: não passe `dns_records` nem `ssl_mode`; `nameservers` é obrigatório (mínimo 2). Nunca expomos os nameservers do Cloudflare.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `domain` * | string | Nome de domínio a registrar, ex.: `mysite.com`. |
| `period` * | integer | Período de registro em anos (1–10). |
| `cloudflare` * | boolean | Conectar o domínio ao Cloudflare automaticamente. |
| `auto_renew` * | boolean | Ativar renovação automática. |
| `ssl_mode` | string | `flexible` \| `full` \| `strict`. Somente com `cloudflare: true`. |
| `nameservers` | string[] | Obrigatório com `cloudflare: false` (≥2 NS). Proibido com `cloudflare: true`. |
| `dns_records` | object[] | Registros DNS iniciais. Somente com `cloudflare: true`. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `DOMAIN_NOT_AVAILABLE` | 400 | O domínio não está disponível para registro. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente. |
| `VALIDATION_ERROR` | 400 | Corpo da requisição inválido. |

**Cloudflare ATIVADO**
```bash
curl -X POST https://app.piv.day/api/v1/domains \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "mysite.com",
    "period": 1,
    "cloudflare": true,
    "auto_renew": true,
    "ssl_mode": "full",
    "dns_records": [
      { "type": "A", "name": "@", "content": "1.2.3.4", "proxied": true }
    ]
  }'
```

**Cloudflare DESATIVADO — NS próprios**
```bash
curl -X POST https://app.piv.day/api/v1/domains \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "mysite.com",
    "period": 1,
    "cloudflare": false,
    "auto_renew": false,
    "nameservers": ["ns1.myhost.com", "ns2.myhost.com"]
  }'
```

**202 Accepted**
```json
{
  "success": true,
  "data": {
    "queue_id": "que-uuid-...",
    "domain": {
      "id": "dom-uuid-...",
      "domain_name": "mysite.com",
      "status": "purchasing",
      "period": 1,
      "cloudflare_enabled": true,
      "auto_renew": true,
      "ssl_mode": "full",
      "created_at": "2026-01-01T00:00:00Z"
    }
  }
}
```

### Status da fila de registro

`GET /api/v1/domains/queue/{id}`

Verificar o status de uma entrada na fila de registro ou renovação de domínio. No status `completed`, a resposta já traz o cartão completo do domínio — não é necessária uma chamada separada a `/domains/{id}`.

**curl**
```bash
curl https://app.piv.day/api/v1/domains/queue/que-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**pending / processing**
```json
{
  "success": true,
  "data": {
    "queue_id": "que-uuid-...",
    "domain": "mysite.com",
    "status": "pending"
  }
}
```

**completed — cartão completo**
```json
{
  "success": true,
  "data": {
    "queue_id": "que-uuid-...",
    "status": "completed",
    "domain": {
      "id": "dom-uuid-...",
      "domain_name": "mysite.com",
      "status": "active",
      "registered_at": "2026-01-01T12:00:00Z",
      "expires_at": "2027-01-01T12:00:00Z",
      "auto_renew": true,
      "cloudflare_enabled": true,
      "cloudflare_status": "active",
      "ssl_type": "lets_encrypt",
      "ssl_status": "active",
      "ssl_mode": "full",
      "ssl_expires_at": "2026-04-01T00:00:00Z",
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T12:00:00Z",
      "dns_records": [
        { "name": "@", "type": "A", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true }
      ]
    }
  }
}
```

### Obter um domínio

`GET /api/v1/domains/{id}`

Obter informações completas sobre um domínio registrado. O campo `nameservers` está presente somente quando `cloudflare_enabled: false`.

**curl**
```bash
curl https://app.piv.day/api/v1/domains/dom-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "domain": {
      "id": "dom-uuid-...",
      "domain_name": "mysite.com",
      "status": "active",
      "registered_at": "2026-01-01T12:00:00Z",
      "expires_at": "2027-01-01T12:00:00Z",
      "auto_renew": true,
      "cloudflare_enabled": true,
      "cloudflare_status": "active",
      "ssl_type": "lets_encrypt",
      "ssl_status": "active",
      "ssl_mode": "full",
      "ssl_expires_at": "2026-04-01T00:00:00Z",
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T12:00:00Z",
      "dns_records": [
        { "name": "@", "type": "A", "content": "1.2.3.4", "ttl": 1, "priority": null, "proxied": true },
        { "name": "www", "type": "CNAME", "content": "mysite.com", "ttl": 1, "priority": null, "proxied": true }
      ]
    }
  }
}
```

### Renovar domínio

`POST /api/v1/domains/{id}/renew`

Renovar o registro do domínio. Retorna `queue_id` para rastreamento.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `period` * | integer | Anos a adicionar. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../renew \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "period": 1 }'
```

**202 Accepted**
```json
{
  "success": true,
  "data": {
    "queue_id": "que-uuid-...",
    "domain_id": "dom-uuid-...",
    "expires_at": "2027-01-01T12:00:00Z"
  }
}
```

### Alternar renovação automática

`POST /api/v1/domains/{id}/auto-renew`

Ativar ou desativar a renovação automática do domínio.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `auto_renew` * | boolean | Novo valor do sinalizador. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../auto-renew \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "auto_renew": true }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "id": "dom-uuid-...",
    "auto_renew": true
  }
}
```

### Registros DNS do domínio

`GET /api/v1/domains/{id}/dns`

Listar os registros DNS do domínio (via Cloudflare).

**curl**
```bash
curl https://app.piv.day/api/v1/domains/dom-uuid-.../dns \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "records": [
      {
        "id": "rec-uuid-...",
        "type": "A",
        "name": "@",
        "content": "1.2.3.4",
        "ttl": 1,
        "priority": null,
        "proxied": true
      }
    ]
  }
}
```

### Adicionar registro DNS

`POST /api/v1/domains/{id}/dns`

Criar um novo registro DNS.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `type` * | string | Tipo de registro: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, etc. |
| `name` * | string | Nome do registro, ex.: `@` ou `subdomain`. |
| `content` * | string | Valor do registro. |
| `ttl` | integer | TTL em segundos (`1` = automático). |
| `proxied` | boolean | Fazer proxy pelo Cloudflare. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../dns \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "A",
    "name": "@",
    "content": "1.2.3.4",
    "ttl": 1,
    "proxied": true
  }'
```

**201 Created**
```json
{
  "success": true,
  "data": {
    "record": {
      "id": "rec-uuid-...",
      "type": "A",
      "name": "@",
      "content": "1.2.3.4",
      "ttl": 1,
      "priority": null,
      "proxied": true
    }
  }
}
```

### Atualizar registro DNS

`PUT /api/v1/domains/{id}/dns/{recordId}`

Atualizar um registro DNS existente por ID. Apenas os campos enviados são sobrescritos.

**curl**
```bash
curl -X PUT https://app.piv.day/api/v1/domains/dom-uuid-.../dns/rec-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "content": "5.6.7.8" }'
```

### Excluir registro DNS

`DELETE /api/v1/domains/{id}/dns/{recordId}`

Excluir um registro DNS por ID.

**curl**
```bash
curl -X DELETE https://app.piv.day/api/v1/domains/dom-uuid-.../dns/rec-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Ativar Cloudflare

`POST /api/v1/domains/{id}/cloudflare`

Ativar Cloudflare para o domínio. Não retornamos os nameservers do Cloudflare — a delegação é configurada do nosso lado. Desativar Cloudflare via este endpoint não é suportado.

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../cloudflare \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "domain_id": "dom-uuid-...",
    "ssl_mode": "flexible"
  }
}
```

### Definir nameservers

`POST /api/v1/domains/{id}/ns-servers`

Definir nameservers personalizados para o domínio.

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/domains/dom-uuid-.../ns-servers \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "nameservers": ["ns1.example.com", "ns2.example.com"] }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "nameservers": [
      { "nameserver": "ns1.example.com", "order_index": 1 },
      { "nameserver": "ns2.example.com", "order_index": 2 }
    ]
  }
}
```

## Whites

Geração de white-pages por nicho e tier. Cada site é único. Troque domínio e contatos sem regenerar.

### Listar tiers

`GET /api/v1/whites/tiers`

Tiers de geração disponíveis com preços. `price` é o preço por geração já ajustado à sua conta (assinatura aplicada), um único número. `quality: "premium"` custa mais (calculado no momento da criação). Blog apenas no `v2_tier3`: os primeiros 3 artigos estão incluídos; cada artigo extra (até 20) é cobrado a `extra_article_price`.

**curl**
```bash
curl https://app.piv.day/api/v1/whites/tiers \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "tiers": [
      {
        "tier_key": "v2_tier1",
        "name": "Landing",
        "generator_version": "v2",
        "min_articles": 0,
        "max_articles": 0,
        "price": 3.00,
        "extra_article_price": null
      },
      {
        "tier_key": "v2_tier2",
        "name": "Multi-page",
        "generator_version": "v2",
        "min_articles": 0,
        "max_articles": 0,
        "price": 6.00,
        "extra_article_price": null
      },
      {
        "tier_key": "v2_tier3",
        "name": "Full + Blog",
        "generator_version": "v2",
        "min_articles": 3,
        "max_articles": 20,
        "price": 10.00,
        "extra_article_price": 1.50
      }
    ]
  }
}
```

### Listar whites

`GET /api/v1/whites`

Lista dos seus jobs de geração com filtragem.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | Tamanho da página. Padrão 100, máximo 500. |
| `offset` | integer | Offset de paginação. |
| `status` | string | `queued` \| `processing` \| `done` \| `failed`. |
| `tier_key` | string | `v2_tier1` \| `v2_tier2` \| `v2_tier3`. |

**curl**
```bash
curl "https://app.piv.day/api/v1/whites?status=done&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "whites": [
      {
        "id": "job-uuid-...",
        "name": "My White",
        "status": 10,
        "tier_key": "v2_tier3",
        "quality": "standard",
        "niche": "IT Consulting",
        "country": "DE",
        "language": "de",
        "domain": "mysite.com",
        "result_url": null,
        "created_at": "2026-01-01T00:00:00Z"
      }
    ],
    "pagination": { "total": 1, "limit": 20, "offset": 0 }
  }
}
```

### Iniciar geração

`POST /api/v1/whites`

Coloque um job de geração de white-page na fila. O pagamento é cobrado imediatamente. O status final chega via webhook `white.completed` / `white.failed` ou pode ser consultado via `GET /whites/{id}`.

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `name` * | string | Nome do job (2–20 caracteres). |
| `tier_key` * | string | `v2_tier1` \| `v2_tier2` \| `v2_tier3`. |
| `niche` * | string | Nicho do site (até 25 caracteres). |
| `country` * | string | Código do país (ISO 3166-1 alpha-2). |
| `language` * | string | Idioma principal do site (ISO 639-1). |
| `quality` | string | `standard` \| `premium` (padrão `standard`). |
| `languages` | string[] | Lista de idiomas. O primeiro está incluído no preço; cada idioma extra tem custo adicional. |
| `domain` | string | Domínio completo (ex.: `example.com`). Sem autocompletar. |
| `email` | string | Email completo (ex.: `info@example.com`). Sem autocompletar. |
| `phone` | string | Telefone de contato. |
| `address` | string | Endereço da empresa. |
| `legal_name` | string | Nome jurídico. |
| `style_hint` | string | Estilo de design (Random, Minimalista, Corporativo, etc.). |
| `keywords` | string[] | Palavras-chave SEO. |
| `banned_words` | string[] | Palavras proibidas no conteúdo. |
| `blog_count` | integer | Quantidade de artigos do blog 3–20. Apenas para `v2_tier3`. Artigos além de 3 têm custo extra. |
| `contacts_mode` | string | `same` \| `template`. |
| `contacts_template` | string | Template da página de contato (quando `contacts_mode=template`). |
| `facebook` | string | URL completa da página do Facebook. |
| `instagram` | string | URL completa do perfil do Instagram. |
| `linkedin` | string | URL completa do perfil do LinkedIn. |
| `youtube` | string | URL completa do canal do YouTube. |
| `tiktok` | string | URL completa do perfil do TikTok. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente. |
| `INVALID_TIER` | 400 | Nenhum tier encontrado com essa chave. |
| `VALIDATION_ERROR` | 400 | Corpo da requisição inválido. |

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/whites \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tech Blog DE",
    "tier_key": "v2_tier3",
    "niche": "IT Consulting",
    "country": "DE",
    "language": "de",
    "quality": "premium",
    "languages": ["de", "en"],
    "domain": "techblog-de.com",
    "email": "info@techblog-de.com",
    "phone": "+49 30 123456",
    "address": "Berliner Str. 1, 10115 Berlin",
    "legal_name": "TechBlog GmbH",
    "style_hint": "Корпоративный",
    "keywords": ["IT", "consulting", "cloud"],
    "banned_words": ["cheap", "free"],
    "blog_count": 8,
    "contacts_mode": "same",
    "facebook": "https://facebook.com/techblogde",
    "instagram": "https://instagram.com/techblogde",
    "linkedin": "https://linkedin.com/company/techblogde"
  }'
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "status": "queued",
    "white": {
      "id": "job-uuid-...",
      "name": "Tech Blog DE",
      "status": 0,
      "tier_key": "v2_tier3",
      "quality": "premium",
      "niche": "IT Consulting",
      "country": "DE",
      "language": "de",
      "domain": "techblog-de.com",
      "result_url": null,
      "created_at": "2026-01-01T00:00:00Z"
    }
  }
}
```

### Obter um white

`GET /api/v1/whites/{id}`

Obter o estado atual de um job de geração.

**curl**
```bash
curl https://app.piv.day/api/v1/whites/job-uuid-... \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "white": {
      "id": "job-uuid-...",
      "name": "Tech Blog DE",
      "status": 10,
      "tier_key": "v2_tier3",
      "quality": "premium",
      "niche": "IT Consulting",
      "country": "DE",
      "language": "de",
      "domain": "techblog-de.com",
      "result_url": null,
      "created_at": "2026-01-01T00:00:00Z"
    }
  }
}
```

### Baixar arquivo

`GET /api/v1/whites/{id}/download`

Obtenha um link assinado de curta duração para o arquivo final da white-page. O link é válido por ~5 minutos (veja `expires_at`). Se expirou, basta chamar `/download` novamente — emitiremos um novo. Se a white-page ainda não foi gerada, retorna `409 NOT_READY`.

**Query parameters**

| Field | Type | Description |
| --- | --- | --- |
| `format` | string | Formato do arquivo. Padrão `php`. |

**Errors**

| Code | HTTP | When it fires |
| --- | --- | --- |
| `NOT_READY` | 409 | A white-page ainda está sendo gerada — tente novamente após `white.completed`. |

**curl**
```bash
curl "https://app.piv.day/api/v1/whites/job-uuid-.../download?format=php" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**200 OK**
```json
{
  "success": true,
  "data": {
    "url": "https://strg.piv.day/download/<user_id>/<job_id>?format=php&token=<hmac>&filename=<name>.zip",
    "filename": "example.com_2026-05-28_DE_en.zip",
    "format": "php",
    "expires_at": "2026-05-28T14:46:00Z"
  }
}
```

### Trocar domínio e contatos

`POST /api/v1/whites/{id}/config`

Atualize os dados de contato da empresa na white-page sem regenerá-la.

**curl**
```bash
curl -X POST https://app.piv.day/api/v1/whites/job-uuid-.../config \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company": "My Brand",
    "domain": "newdomain.com",
    "email": "info@newdomain.com",
    "phone": "+49 30 123456",
    "address": "Berliner Str. 1, Berlin",
    "legal": "My Brand GmbH"
  }'
```

## Webhooks

Enviamos um POST simples com corpo JSON para sua URL. O formato é o mesmo para todos os eventos: `event_type`, `timestamp` e um bloco `data`. Seu servidor deve responder com qualquer 2xx em 5 segundos.

```
POST <your webhook URL>
Content-Type: application/json
User-Agent: piv.day-webhook/1.0

{
  "event_type": "<event identifier>",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": { ... }
}
```

**Retries.** Em caso de não-2xx ou timeout — até 3 tentativas com espera de 1s / 2s / 4s. O histórico completo de entregas fica na página de chaves API no dashboard. Se todas as três falharem, o evento é marcado como failed e pode ser reenviado manualmente.

**Security.** Use um endpoint HTTPS. Cada requisição chega com o cabeçalho `User-Agent: piv.day-webhook/1.0`. A URL do webhook é configurada no perfil da chave API.

### All events

| Event | When it fires |
| --- | --- |
| `sms.received` | Um remetente confiável acabou de enviar uma mensagem para um dos seus números. |
| `sms.status_updated` | Um SMS de saída mudou de estado — entregue, falhou, etc. |
| `number.restore_completed` | Um Restore em lote foi concluído — o resumo final chega aqui. |
| `verify.completed` | A verificação da conta Google foi concluída com sucesso. |
| `verify.failed` | Algo deu errado — o Google não aceitou. |
| `proxy.expires_soon` | Restam cerca de três dias no pedido de proxy. |
| `domain.registered` | O pedido de registro foi concluído com sucesso — o domínio está ativo. |
| `domain.failed` | O pedido não foi concluído — causa comum: o nome acabou de ser registrado. |
| `domain.expires_soon` | Falta cerca de uma semana para o domínio expirar. |
| `white.completed` | A tarefa de geração do White foi concluída com sucesso. |
| `white.failed` | A tarefa falhou — os fundos são reembolsados ao saldo. |

### SMS recebido no seu número

**`sms.received`**

Na maioria das vezes são códigos de verificação de redes de publicidade. O payload inclui o texto bruto e um código detectado automaticamente (quando presente) para que você possa passá-lo diretamente para seu fluxo de trabalho sem parsing. Mensagens de remetentes de spam conhecidos não são encaminhadas.

```json
{
  "event_type": "sms.received",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "text": "Your verification code is 123456",
    "code": "123456",
    "country": "SE",
    "received_at": "2026-05-22T12:34:56Z"
  }
}
```

### Status do SMS enviado mudou

**`sms.status_updated`**

Útil quando você envia confirmações do seu próprio número e precisa saber se realmente chegaram. Se o status for failed, o código de erro e a mensagem da operadora estão no payload.

```json
{
  "event_type": "sms.status_updated",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "message_id": "42",
    "old_status": "sent",
    "new_status": "delivered",
    "error_code": null,
    "error_message": null
  }
}
```

### Restauração de números concluída

**`number.restore_completed`**

Contém duas listas — quais números voltaram e quais não. O valor reembolsado pelos que falharam também está no payload. Prático para scripts que tentam novamente de forma inteligente.

```json
{
  "event_type": "number.restore_completed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "restored": [
      {
        "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
        "phone_number": "+46764794425",
        "country": "SE"
      }
    ],
    "failed": [
      {
        "piv_num_id": "abc12-defgh-ijklm-nopqr",
        "phone_number": "+46123456789",
        "country": "SE",
        "refunded": 7.50
      }
    ],
    "total_restored": 1,
    "total_failed": 1,
    "total_refunded": 7.50
  }
}
```

### Verificação Google QR bem-sucedida

**`verify.completed`**

Se um SMS com código chegou durante o processo, já está aqui — não é necessária uma busca extra. captured_phone e captured_message contêm o que o Google enviou.

```json
{
  "event_type": "verify.completed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "task_id": 12,
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "status": "sms_sent",
    "captured_phone": "+14155551234",
    "captured_message": "Your Google verification code is 123456",
    "sms_message_id": 42,
    "sms_status": "queued"
  }
}
```

### Verificação Google QR falhou

**`verify.failed`**

error_code informa o que especificamente — timeout, rejeição, URL inválida. Verificações com falha são reembolsadas ao saldo automaticamente.

```json
{
  "event_type": "verify.failed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "task_id": 12,
    "piv_num_id": "vzPA1-kHKSg-EAL7e-Jqd3o",
    "status": "failed",
    "error_code": "VERIFY_TIMEOUT",
    "error_message": "Verification timed out"
  }
}
```

### Proxy expirando em breve

**`proxy.expires_soon`**

Ideal para quem não renova automaticamente: você vê com antecedência e decide — estender ou encerrar a campanha. Conecte ao POST /proxy/orders/{id}/renew para renovação totalmente automática.

```json
{
  "event_type": "proxy.expires_soon",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "order_id": "ord-uuid-...",
    "country": "DE",
    "quantity": 10,
    "expires_at": "2026-05-25T00:00:00Z"
  }
}
```

### Domínio registrado

**`domain.registered`**

Se você solicitou Cloudflare e SSL na compra, ambos já estão ativos — cloudflare_enabled reflete o status. Você pode adicionar registros DNS ou implantar o site imediatamente.

```json
{
  "event_type": "domain.registered",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "domain_id": "dom-uuid-...",
    "domain_name": "mysite.com",
    "registered_at": "2026-05-22T12:34:56Z",
    "expires_at": "2027-05-22T12:34:56Z",
    "cloudflare_enabled": true
  }
}
```

### Registro de domínio falhou

**`domain.failed`**

O campo error tem uma razão legível por humanos. O custo é reembolsado ao saldo. Escolha outro nome e tente novamente.

```json
{
  "event_type": "domain.failed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "domain_id": "dom-uuid-...",
    "domain_name": "mysite.com",
    "error": "Domain is no longer available"
  }
}
```

### Domínio expirando em breve

**`domain.expires_soon`**

Útil quando a renovação automática está desativada: você tem tempo para renovar manualmente antes que o domínio entre em redenção. O sinalizador auto_renew indica se é necessária intervenção.

```json
{
  "event_type": "domain.expires_soon",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "domain_id": "dom-uuid-...",
    "domain_name": "mysite.com",
    "expires_at": "2026-05-29T00:00:00Z",
    "auto_renew": false
  }
}
```

### White gerado

**`white.completed`**

O arquivo está disponível via GET /whites/{id}/download. Se o White tinha um domínio configurado, o sitemap e contatos já estão vinculados a ele — pronto para deploy.

```json
{
  "event_type": "white.completed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "job_id": "job-uuid-...",
    "name": "My White",
    "tier_key": "t1",
    "country": "DE"
  }
}
```

### Geração do White falhou

**`white.failed`**

O campo error explica o motivo. Inicie um novo trabalho via POST /whites — sem perda de dinheiro.

```json
{
  "event_type": "white.failed",
  "timestamp": "2026-05-22T12:34:56Z",
  "data": {
    "job_id": "job-uuid-...",
    "name": "My White",
    "error": "Generation failed"
  }
}
```

## Error codes

Todos os erros seguem o mesmo formato: status HTTP + campo `error.code` + mensagem legível em `error.message`.

```json
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Not enough balance: required $5.00, available $1.20"
  }
}
```

| Code | HTTP | When it fires |
| --- | --- | --- |
| `UNAUTHORIZED` | 401 | Chave ausente, expirada ou revogada. |
| `INSUFFICIENT_PERMISSIONS` | 403 | A chave não possui o scope necessário para esta ação. |
| `PERMISSION_DENIED` | 403 | O acesso no nível da conta não permite esta ação (ex.: funcionalidade desativada). |
| `VALIDATION_ERROR` | 400 | Corpo da requisição inválido. A mensagem explica qual campo falhou e por quê. |
| `NUMBER_NOT_FOUND` | 404 | O número não existe ou não pertence à conta. |
| `NUMBER_EXPIRED` | 403 | O número expirou — use Restore (dentro de 7 dias) ou compre um novo. |
| `NUMBER_NOT_ACTIVE` | 400 | O número está em um estado que não permite esta operação. |
| `INSUFFICIENT_BALANCE` | 402 | Saldo insuficiente para esta operação. |
| `INVALID_MESSAGE_FORMAT` | 400 | O corpo do SMS é muito longo ou contém caracteres não permitidos. |
| `COUNTRY_NOT_AVAILABLE` | 400 | O país não está disponível para compra ou renovação. |
| `NO_NUMBERS_AVAILABLE` | 400 | Não há números disponíveis no país escolhido no momento. |
| `NO_RESTORABLE_NUMBERS` | 404 | Nenhum dos números fornecidos pode ser restaurado. |
| `RATE_LIMITED` | 429 | Rate limit excedido. Aguarde o tempo indicado no cabeçalho `Retry-After`. |
| `INTERNAL_ERROR` | 500 | Algo deu errado do nosso lado. Se persistir, entre em contato com o suporte. |
