> ## Documentation Index
> Fetch the complete documentation index at: https://developers.sults.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Erros da API v1 do SULTS

> Os status HTTP reais da API do SULTS, o formato do corpo de erro e como tratar cada falha na sua integração

<span className="badge-v1">API v1</span>

A API sinaliza falhas por **status HTTP** e devolve o motivo em um corpo JSON simples. Esta página documenta o comportamento real, confirmado pela engenharia do SULTS.

## Status de resposta

| Status | Quando acontece | Como reagir |
| - | - | - |
| 200 | Sucesso. | Processe a resposta. |
| 400 | Erro de validação ou formato errado nos dados de entrada. | Corrija o campo apontado na mensagem e reenvie. |
| 401 | Token ausente ou inválido no cabeçalho `Authorization`. | Confira o token: copiado por completo, cabeçalho com o nome exato, valor sem o prefixo `Bearer`, e da versão certa (v1 para esta API). Veja [Autenticação](/v1/autenticacao). |
| 404 | Recurso ou entidade inexistente (por exemplo, um `{id}` que não existe no caminho). | Confirme o identificador; se veio do seu banco, o registro pode ter sido excluído no SULTS. |
| 429 | Limite de requisições excedido: **2 requisições por segundo, por IP**. A resposta é uma página HTML padrão do servidor, não JSON. | Trate pelo status, sem fazer parse do corpo, e tente de novo com espera crescente a partir de 1 segundo. Detalhes em [Limites de requisição](/geral/limites). |
| 5xx | Falha inesperada do servidor (raro). | Tente novamente com espera crescente; persistindo, acione o suporte com data, hora e endpoint. |

## O corpo do erro

Os erros `400`, `401` e `404` vêm com um JSON de campo único `error`, trazendo a mensagem em português. O corpo real do 401, confirmado pela engenharia, é:

```json theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  "error": "Acesso não autorizado."
}
```

E um 400 real, de uma criação de negócio com campo obrigatório faltando:

```json theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  "error": "Campo responsavel não pode ser vazio ou nulo."
}
```

A exceção é o `429`: a resposta é a **página HTML padrão do servidor**, sem JSON e sem o header `Retry-After`. Não tente fazer parse do corpo de um 429; decida pelo status. Dois fatos importantes sobre o contrato de erros:

**Não existe código de erro interno.** O status HTTP é o único identificador estruturado; a mensagem existe para humanos. Portanto, **decida pelo status** na sua lógica (retry, alerta, correção) e use a mensagem para logs e diagnóstico, nunca como condição de código, porque o texto pode mudar.

**Erros de validação vêm em mensagem única**, não em lista por campo. Se o payload tiver mais de um problema, a API aponta um por vez: corrija, reenvie e repita até o 200.

## Receita de tratamento

Na prática, a sua integração precisa de três comportamentos: **400 e 404 não se repetem sozinhos** (reenviar o mesmo payload dá o mesmo erro; corrija antes), **401 é configuração** (pare e revise o token, sem retry automático), e **429 e 5xx pedem retry com espera crescente** (1 s, 2 s, 4 s, 8 s...), respeitando o ritmo de 2 requisições por segundo nas varreduras de [sincronização](/v1/convencoes#como-manter-os-dados-atualizados).

## Retry com backoff exponencial

O `429` é o erro mais provável da sua integração, porque o limite de 2 requisições por segundo é fácil de estourar em varreduras. Como não há header `Retry-After`, comece com 1 segundo e dobre a espera a cada falha seguida.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  # --retry repete em 429 e 5xx: espera 1 s e dobra a cada tentativa
  curl --retry 5 "https://api.sults.com.br/v1/empresas?start=0&limit=100" \
    -H "Authorization: SEU_TOKEN"
  ```

  ```python Python theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  import time
  import requests

  def buscar(caminho, params=None, tentativas=5):
      espera = 1
      for _ in range(tentativas):
          r = requests.get(
              f"https://api.sults.com.br{caminho}",
              headers={"Authorization": "SEU_TOKEN"},
              params=params,
          )
          if r.status_code not in (429, 500, 502, 503, 504):
              r.raise_for_status()
              return r.json()
          time.sleep(espera)
          espera *= 2  # 1 s, depois 2 s, 4 s, 8 s
      raise RuntimeError("a API seguiu recusando apos as tentativas previstas")

  unidades = buscar("/v1/empresas", {"start": 0, "limit": 100})
  ```

  ```javascript Node theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  const dormir = (ms) => new Promise((r) => setTimeout(r, ms));

  async function buscar(caminho, params = {}, tentativas = 5) {
    const url = new URL("https://api.sults.com.br" + caminho);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

    let espera = 1000;
    for (let i = 0; i < tentativas; i++) {
      const r = await fetch(url, { headers: { Authorization: "SEU_TOKEN" } });
      if (![429, 500, 502, 503, 504].includes(r.status)) {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      }
      await dormir(espera);
      espera *= 2; // 1 s, depois 2 s, 4 s, 8 s
    }
    throw new Error("a API seguiu recusando apos as tentativas previstas");
  }

  const unidades = await buscar("/v1/empresas", { start: 0, limit: 100 });
  ```

  ```php PHP theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  <?php
  function buscar(string $caminho, array $params = [], int $tentativas = 5): array
  {
      $espera = 1;
      for ($i = 0; $i < $tentativas; $i++) {
          $ch = curl_init("https://api.sults.com.br" . $caminho . "?" . http_build_query($params));
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: SEU_TOKEN"]);
          $corpo = curl_exec($ch);
          $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
          curl_close($ch);

          if (!in_array($status, [429, 500, 502, 503, 504], true)) {
              if ($status >= 400) {
                  throw new RuntimeException("HTTP $status");
              }
              return json_decode($corpo, true);
          }
          sleep($espera);
          $espera *= 2; // 1 s, depois 2 s, 4 s, 8 s
      }
      throw new RuntimeException("a API seguiu recusando apos as tentativas previstas");
  }

  $unidades = buscar("/v1/empresas", ["start" => 0, "limit" => 100]);
  ```

  ```csharp C# theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  using System.Net.Http;
  using System.Text.Json;

  var http = new HttpClient { BaseAddress = new Uri("https://api.sults.com.br") };
  http.DefaultRequestHeaders.Add("Authorization", "SEU_TOKEN");
  int[] repetir = { 429, 500, 502, 503, 504 };

  async Task<JsonDocument> Buscar(string caminho, int tentativas = 5)
  {
      var espera = TimeSpan.FromSeconds(1);
      for (var i = 0; i < tentativas; i++)
      {
          var resposta = await http.GetAsync(caminho);
          if (!repetir.Contains((int)resposta.StatusCode))
          {
              resposta.EnsureSuccessStatusCode();
              return JsonDocument.Parse(await resposta.Content.ReadAsStringAsync());
          }
          await Task.Delay(espera);
          espera *= 2; // 1 s, depois 2 s, 4 s, 8 s
      }
      throw new HttpRequestException("a API seguiu recusando apos as tentativas previstas");
  }

  using var unidades = await Buscar("/v1/empresas?start=0&limit=100");
  ```
</CodeGroup>

Repare que `400`, `401` e `404` ficam de fora do retry: eles não se resolvem com espera.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Limites de requisição" icon="gauge" href="/geral/limites">
    O rate limit oficial e como ritmar as suas chamadas.
  </Card>

  <Card title="Autenticação" icon="key" href="/v1/autenticacao">
    O checklist completo para nunca ver um 401.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.