# Erros

Formato dos erros, códigos HTTP e o que fazer em cada caso.

Todo erro vem no mesmo formato, com um `code` estável. Programe sempre em cima do `code` — a `message` é escrita para humanos e pode mudar sem aviso.

_Formato do erro_

```json
{
  "error": {
    "code": "validation_error",
    "message": "2 campos estão inválidos. Veja "fields".",
    "fields": {
      "amount": "Informe o valor em CENTAVOS, como número inteiro (ex.: 1990 = R$ 19,90).",
      "customer.document": "CPF inválido."
    },
    "doc_url": "https://novexfinance.com.br/docs/erros",
    "request_id": "a3f1c09b7e42d85610fb2c7d9a4e5b83"
  }
}
```

| Campo | Tipo | Descrição |
| --- | --- | --- |
| `error.code` opcional | string | Identificador estável do erro. É o que o seu código deve verificar. |
| `error.message` opcional | string | Explicação em português. Não faça lógica em cima dela. |
| `error.fields` opcional | objeto | Só em `validation_error`: o problema de cada campo, pelo caminho completo (`customer.email`). |
| `error.request_id` opcional | string | Identificador da requisição. Registre no seu log — é por ele que o suporte acha o que aconteceu. |
| `error.doc_url` opcional | string | Página da documentação que trata do assunto. |

## Códigos HTTP

| HTTP | Significa | Retentar? |
| --- | --- | --- |
| `200` · `201` | Deu certo. | — |
| `400` | Requisição malformada (JSON inválido, por exemplo). | Não, sem corrigir. |
| `401` | Credencial ausente, inválida ou revogada. | Não, sem corrigir. |
| `402` | Pagamento recusado pelo emissor. | Não. Ofereça outro meio ao cliente. |
| `403` | Autenticado, mas sem permissão para esta operação. | Não, sem resolver a pendência. |
| `404` | Recurso ou endpoint não encontrado. | Não. |
| `405` | Método HTTP errado para este caminho. Veja o header `Allow`. | Não. |
| `409` | Conflito com o estado atual do recurso. | Depende do caso. |
| `422` | JSON válido, mas os dados não passam nas regras. | Não, sem corrigir. |
| `429` | Limite de requisições excedido. | **Sim**, após o `Retry-After`. |
| `500` | Erro nosso. | **Sim**, com a mesma `Idempotency-Key`. |
| `502` · `503` | Serviço de processamento indisponível. | **Sim**, com a mesma `Idempotency-Key`. |

## Todos os códigos de erro

### Autenticação e acesso

| Código | HTTP | O que fazer |
| --- | --- | --- |
| `missing_api_key` | 401 | Enviar o header `Authorization: Bearer`. Se você já envia, verifique se o seu servidor não o está removendo. |
| `invalid_api_key` | 401 | Conferir a chave. Ela começa com `nvx_live_`. |
| `revoked_api_key` | 401 | Gerar uma chave nova no painel. |
| `unauthorized` | 401 | Requisição sem autenticação válida. |
| `account_inactive` | 403 | Conta suspensa ou bloqueada. Falar com o suporte. |
| `kyc_required` | 403 | Concluir a verificação de identidade no painel. Consultas funcionam; cobranças e saques, não. |

### Dados enviados

| Código | HTTP | O que fazer |
| --- | --- | --- |
| `invalid_json` | 400 | Corpo não é JSON válido. Confira o `Content-Type: application/json`. |
| `validation_error` | 422 | Ler `fields` e corrigir cada campo apontado. |
| `resource_not_found` | 404 | O ID não existe nesta conta. Conferir se não veio de outro ambiente. |
| `not_found` | 404 | Endpoint inexistente. Conferir o caminho. |
| `method_not_allowed` | 405 | Usar um dos verbos listados no header `Allow`. |

### Regras de negócio

| Código | HTTP | O que fazer |
| --- | --- | --- |
| `payment_refused` | 402 | O emissor não autorizou. Oferecer outro cartão ou outro meio. |
| `insufficient_balance` | 422 | Saldo menor que valor + taxa. A mensagem traz os três números. |
| `amount_limit_exceeded` | 403 | Valor acima do limite por transação da conta. |
| `daily_limit_exceeded` | 403 | Limite diário atingido. Tentar no dia seguinte ou pedir revisão do limite. |
| `card_direct_not_enabled` | 403 | Enviar dados de cartão pela API exige PCI-DSS. Usar [checkout hospedado](https://novexfinance.com.br/docs/checkout). |
| `checkout_already_paid` | 409 | Não dá para expirar um checkout já pago. |
| `webhook_already_exists` | 409 | Já existe endpoint com esta URL. |
| `webhook_limit_reached` | 409 | Máximo de 10 endpoints. Remover um antes de criar outro. |

### Idempotência e limites

| Código | HTTP | O que fazer |
| --- | --- | --- |
| `idempotency_key_in_progress` | 409 | A primeira requisição ainda está sendo processada. Aguardar e consultar antes de repetir. |
| `idempotency_key_reused` | 422 | A mesma chave foi usada com outro corpo. Usar uma chave nova. |
| `rate_limit_exceeded` | 429 | Esperar o `Retry-After` em segundos. |

### Do nosso lado

| Código | HTTP | O que fazer |
| --- | --- | --- |
| `processing_error` | 502 | Falha no processamento. Retentar com a mesma `Idempotency-Key`. |
| `service_unavailable` | 503 | Indisponibilidade temporária ou manutenção. Retentar com a mesma `Idempotency-Key`. |
| `internal_error` | 500 | Erro nosso. Retentar; se persistir, informar o `request_id` ao suporte. |

## Como tratar erros

A regra é simples: **4xx é problema do pedido** — retentar sem mudar nada só repete o erro. **5xx e 429 são transitórios** — retentar faz sentido, com espera crescente e sempre com a mesma `Idempotency-Key`.

_Retentativa com backoff_

```php
function criarCobranca(array $payload, string $idemKey): array
{
    $espera = 1;

    for ($tentativa = 1; $tentativa <= 4; $tentativa++) {
        [$status, $body] = post('/charges', $payload, $idemKey);

        if ($status < 300) {
            return $body;
        }

        // 4xx (menos 429): o pedido está errado. Retentar não resolve.
        if ($status < 500 && $status !== 429) {
            throw new RuntimeException(
                $body['error']['code'] . ': ' . $body['error']['message']
            );
        }

        // Mesma Idempotency-Key: se a 1ª chegou a criar, recebemos ela de volta
        // em vez de uma segunda cobrança.
        sleep($espera);
        $espera *= 2;
    }

    throw new RuntimeException('Falha após 4 tentativas.');
}
```

```js
async function criarCobranca(payload, idemKey) {
  let espera = 1000;

  for (let tentativa = 1; tentativa <= 4; tentativa++) {
    const res = await post('/charges', payload, idemKey);
    const body = await res.json();

    if (res.ok) return body;

    // 4xx (menos 429): o pedido está errado. Retentar não resolve.
    if (res.status < 500 && res.status !== 429) {
      throw new Error(`${body.error.code}: ${body.error.message}`);
    }

    // Mesma Idempotency-Key: nunca vira cobrança duplicada.
    await new Promise((r) => setTimeout(r, espera));
    espera *= 2;
  }

  throw new Error('Falha após 4 tentativas.');
}
```

```python
import time

def criar_cobranca(payload, idem_key):
    espera = 1

    for _ in range(4):
        res = post('/charges', payload, idem_key)

        if res.ok:
            return res.json()

        # 4xx (menos 429): o pedido está errado. Retentar não resolve.
        if res.status_code < 500 and res.status_code != 429:
            err = res.json()['error']
            raise RuntimeError(f'{err["code"]}: {err["message"]}')

        # Mesma Idempotency-Key: nunca vira cobrança duplicada.
        time.sleep(espera)
        espera *= 2

    raise RuntimeError('Falha após 4 tentativas.')
```

> ℹ️ **Registre sempre o `request_id`** junto com o erro no seu log. Sem ele, investigar uma falha vira uma busca por horário aproximado; com ele, o suporte localiza a requisição exata em segundos.

## Timeout do seu lado

Criar cobrança envolve comunicação com o processador de pagamento. Use um timeout de **pelo menos 40 segundos** — um timeout curto derruba a conexão enquanto a cobrança está sendo criada de verdade, e você fica sem saber o resultado.

Se o timeout acontecer mesmo assim, não crie outra cobrança às cegas: repita a chamada com a **mesma `Idempotency-Key`**. Se a primeira tiver dado certo, você recebe ela de volta; se não, uma nova é criada. Nunca duas.


---

Documentação completa: https://novexfinance.com.br/docs/erros
Base da API: https://novexfinance.com.br/api/v1
