> ## 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 v2 do SULTS

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

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

A v2 sinaliza falhas pelo **status HTTP** e devolve o motivo em um corpo JSON, no mesmo contrato da plataforma SULTS confirmado pela engenharia.

## Status de resposta

| Status | Quando acontece | Como reagir |
| - | - | - |
| 200 | Sucesso. Nas escritas (`/add`, `/edit`), pode vir **sem corpo adicional**. | Processe a resposta (ou siga em frente, nas escritas). |
| 400 | Erro de validação ou formato errado nos dados. | Corrija o campo apontado na mensagem e reenvie. |
| 401 | Token ausente ou inválido. | Confira o token: copiado por completo, cabeçalho `Authorization` exato, valor sem o prefixo `Bearer`, e da **Gestão v2** (token v1 não funciona aqui). |
| 404 | Recurso inexistente, ou recurso condicionado desabilitado (por exemplo, endpoints de Mix sem o recurso habilitado). | Confirme o identificador e, nos endpoints de Mix, se o recurso está ativo na plataforma. |
| 429 | Limite de requisições excedido (2 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, com espera crescente a partir de 1 segundo. |
| 5xx | Falha inesperada do servidor (raro). | Tente novamente com espera crescente; persistindo, acione o suporte. |

## O corpo do erro

Os erros `400`, `401` e `404` vêm com a mensagem em um JSON simples. 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 validação:

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

O `429` é a exceção: a resposta é a **página HTML padrão do servidor**, sem JSON e sem o header `Retry-After`; trate pelo status, sem parse do corpo. Não existe código de erro interno: **decida pelo status** na sua lógica e use a mensagem para logs. Erros de validação vêm em **mensagem única**, um campo por vez: corrija, reenvie e repita até o 200.

## Receita de tratamento

**400 e 404 não se resolvem sozinhos** (corrija antes de reenviar), **401 é configuração** (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 descrito em [limites de requisição](/geral/limites).

## 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/api/v2/compras/product/list?page=0&active=true" \
    -H "Authorization: SEU_TOKEN"
  ```

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

  BASE = "https://api.sults.com.br/api/v2/compras"

  def buscar(caminho, params=None, tentativas=5):
      espera = 1
      for _ in range(tentativas):
          r = requests.get(
              f"{BASE}{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")

  produtos = buscar("/product/list", {"page": 0, "active": True})
  ```

  ```javascript Node theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  const BASE = "https://api.sults.com.br/api/v2/compras";
  const dormir = (ms) => new Promise((r) => setTimeout(r, ms));

  async function buscar(caminho, params = {}, tentativas = 5) {
    const url = new URL(BASE + 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 produtos = await buscar("/product/list", { page: 0, active: true });
  ```

  ```php PHP theme={"theme":{"light":"github-dark","dark":"github-dark"}}
  <?php
  const BASE = "https://api.sults.com.br/api/v2/compras";

  function buscar(string $caminho, array $params = [], int $tentativas = 5): array
  {
      $espera = 1;
      for ($i = 0; $i < $tentativas; $i++) {
          $ch = curl_init(BASE . $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");
  }

  $produtos = buscar("/product/list", ["page" => 0, "active" => "true"]);
  ```

  ```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 produtos = await Buscar("/api/v2/compras/product/list?page=0&active=true");
  ```
</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="/v2/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.