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

# Erros

> Códigos de status HTTP, códigos de erro e como tratar falhas na sua integração.

A Vendaze API usa códigos de status HTTP padrão e retorna um formato de erro consistente em toda falha.

## Formato do erro

```json theme={null}
{
  "error": {
    "code": "not_found",
    "message": "Person not found.",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

| Campo        | Descrição                                                        |
| ------------ | ---------------------------------------------------------------- |
| `code`       | Código de erro legível por máquina. Use em lógica de tratamento. |
| `message`    | Descrição para desenvolvedores. Não exiba ao usuário final.      |
| `request_id` | ID único da requisição. Inclua ao contatar o suporte.            |

<Note>
  Uma resposta com status 2xx nunca contém `error`. Uma resposta com status não-2xx sempre contém
  `error`. O `request_id` também é retornado no header `X-Request-ID` em toda resposta, incluindo as
  bem-sucedidas.
</Note>

## Códigos de status HTTP

| Status | Significado                                                                      |
| ------ | -------------------------------------------------------------------------------- |
| `200`  | Sucesso. Recurso lido ou atualizado.                                             |
| `201`  | Criado com sucesso.                                                              |
| `204`  | Sem conteúdo. Recurso deletado. Body vazio.                                      |
| `400`  | Requisição malformada ou parâmetro obrigatório ausente.                          |
| `401`  | Não autenticado. Token ausente, inválido ou expirado.                            |
| `403`  | Sem permissão. Token válido mas escopo insuficiente ou operação negada.          |
| `404`  | Não encontrado. Recurso não existe, foi deletado, ou pertence a outro workspace. |
| `409`  | Conflito. A requisição não pode ser concluída por conflito com dados existentes. |
| `422`  | Validação falhou. Veja o objeto `fields`.                                        |
| `429`  | Limite de taxa excedido.                                                         |
| `500`  | Erro interno. Algo deu errado no servidor.                                       |

## Referência de códigos de erro

<Accordion title="unauthorized (401)">
  Retornado em três situações:

  * **Header ausente ou inválido:** `"Missing or invalid Authorization header."` o header `Authorization: Bearer <token>` está ausente ou malformado.
  * **Token inválido:** `"Invalid or malformed token."` a assinatura do token falhou na verificação ou o token não é um JWT válido.
  * **Claims ausentes:** `"Token is missing required claims."` o token é estruturalmente válido mas está faltando claims internos obrigatórios (`workspace_id`, `client_id` ou `vendaze_scopes`). Isso não deve ocorrer com tokens emitidos pelo fluxo OAuth da Vendaze.
</Accordion>

<Accordion title="token_expired (401)">
  `"Access token expired. Refresh using the refresh token."`

  O access token expirou. Access tokens são válidos por 1 hora. Use o `refresh_token` para obter um
  novo via `POST /oauth/token` com `grant_type=refresh_token`. Se o refresh token também expirou, o
  usuário precisa re-autorizar.
</Accordion>

<Accordion title="insufficient_scope (403)">
  `"This endpoint requires the '{scope}' scope."`

  O token não tem o escopo necessário para este endpoint. O `{scope}` na mensagem é o escopo exato
  que está faltando (por exemplo, `"This endpoint requires the 'people:write' scope."`). O usuário
  autorizou seu app com um conjunto de escopos que não inclui o que esta operação requer. Você
  precisa solicitar uma nova autorização com os escopos corretos.
</Accordion>

<Accordion title="forbidden (403)">
  O token é válido e tem o escopo correto, mas a operação foi negada por uma verificação de
  permissão no banco de dados. A mensagem varia conforme o contexto:

  * **Criação:** `"You do not have permission to create this {entity}. Check that all fields are within your access level."`
  * **Atualização:** `"You do not have permission to update this {entity} or one of the provided values is not allowed for your access level."`
  * **Deleção:** `"You do not have permission to delete this {entity}."`

  Causa comum: definir `owner_user_id` como outro usuário quando seu papel não permite.
</Accordion>

<Accordion title="not_found (404)">
  `"{Entity} not found."` por exemplo, `"Person not found."` ou `"Deal not found."`

  O recurso solicitado não existe, foi soft-deletado ou pertence a um workspace diferente. A API não
  distingue entre esses casos para evitar vazar informações sobre outros workspaces.
</Accordion>

<Accordion title="conflict (409)">
  `"A record with this value already exists."`

  A requisição conflita com dados existentes. A resposta também inclui o objeto `fields`
  identificando o campo em conflito. Causa comum: tentar registrar um app com um e-mail que já está
  em uso.
</Accordion>

<Accordion title="validation_error (422)">
  `"Validation failed."`

  O input falhou na validação. A resposta inclui o objeto `fields` mapeando cada campo inválido a
  uma descrição do problema. Corrija os campos listados e tente novamente. Veja [Erros de validação](#erros-de-validação) para o formato completo da resposta.
</Accordion>

<Accordion title="bad_request (400)">
  Retornado em duas situações:

  * **Body malformado:** `"Request body must be valid JSON."` o body da requisição não pôde ser interpretado como JSON.
  * **Atualização vazia:** `"No fields provided for update."` uma requisição `PATCH` foi enviada sem nenhum campo para modificar.
</Accordion>

<Accordion title="rate_limit_exceeded (429)">
  `"Rate limit exceeded. Try again in {N} seconds."`

  Muitas requisições. O `{N}` na mensagem é o número exato de segundos a aguardar. O mesmo valor
  também está no header `Retry-After`. Veja [Limites de taxa](/pt/guides/rate-limits) para os
  limites completos por método e tipo de endpoint.
</Accordion>

<Accordion title="internal_error (500)">
  Retornado em duas situações:

  * **Erro inesperado no servidor:** `"An unexpected error occurred."` algo falhou no servidor. Tente novamente com backoff exponencial. Se o erro persistir, entre em contato com o suporte incluindo o `request_id`.
  * **Atualização parcial:** `"{Entity} updated but associations failed. Retry the associations."` o registro principal foi salvo mas a atualização de associações (tags, listas, negócios ou campos adicionais) falhou. O registro em si está consistente. Retente apenas os campos de associação.
</Accordion>

## Códigos de erro OAuth

Estes códigos são retornados exclusivamente pelos endpoints OAuth (`/oauth/authorize`, `/oauth/token`, `/oauth/revoke`, `/v1/auth/register-app`, `/v1/auth/rotate-app`).

<Accordion title="invalid_client (401)">
  `"Invalid client credentials."` o `client_id` ou `client_secret` está incorreto. Verifique suas
  credenciais. Se suspeitar que o `client_secret` foi comprometido, rotacione via `POST
      /v1/auth/rotate-app`.

  Também retornado como `401` pelo `/oauth/authorize` quando o `client_id` não existe ou o app não
  está ativo, e pelo `/v1/auth/rotate-app` quando o e-mail enviado não corresponde ao cadastrado no
  aplicativo.
</Accordion>

<Accordion title="invalid_grant (400)">
  `"The provided authorization grant is invalid, expired, or does not match."`

  O código de autorização é inválido, expirou ou já foi utilizado. Códigos expiram em 10 minutos e
  são de uso único. Também retornado quando um `refresh_token` é inválido ou expirado. Em ambos os
  casos, o usuário precisa passar pelo fluxo completo de autorização novamente.
</Accordion>

<Accordion title="invalid_request (400)">
  Um parâmetro OAuth obrigatório está ausente ou inválido. A mensagem identifica o parâmetro
  específico. Exemplos:

  * `"Missing required parameter: client_id."`
  * `"Missing required parameter: redirect_uri."`
  * `"redirect_uri does not match any registered URI."`
  * `"Missing required parameter: token."` (revoke)
  * `"token must be a valid access_token. refresh_tokens are not accepted."` (revoke)
</Accordion>

<Accordion title="unsupported_grant_type (400)">
  `"grant_type must be \"authorization_code\" or \"refresh_token\"."`

  O campo `grant_type` enviado para `/oauth/token` não é um dos dois valores suportados.
</Accordion>

<Accordion title="unsupported_response_type (400)">
  `"response_type must be \"code\"."`

  O parâmetro `response_type` enviado para `/oauth/authorize` não é `"code"`.
</Accordion>

## Erros de validação

Quando a validação falha, a resposta inclui o objeto `fields` mapeando cada campo ao problema:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed.",
    "fields": {
      "full_name": "String must contain at most 70 character(s).",
      "forecast_date": "Invalid ISO 8601 date format."
    },
    "request_id": "uuid"
  }
}
```

## O campo request\_id

Toda requisição, bem-sucedida ou não, recebe um `request_id` único. Ele aparece em:

* O campo `error.request_id` nas respostas de erro
* O header `X-Request-ID` em todas as respostas

Ao contatar o suporte sobre um problema, sempre inclua o `request_id`. Ele permite que o time localize a requisição exata nos logs e diagnostique o que aconteceu.

## Tratamento de erros em produção

### Erros recuperáveis vs não-recuperáveis

Nem todos os erros devem ser retentados. Retentar um erro não-recuperável desperdiça recursos e atrasa a identificação do problema real.

**Erros recuperáveis** são transitórios. Retente com backoff:

| Status | Código                | Estratégia                                                               |
| ------ | --------------------- | ------------------------------------------------------------------------ |
| `429`  | `rate_limit_exceeded` | Aguarde os segundos indicados em `Retry-After` e retente.                |
| `500`  | `internal_error`      | Retente com backoff exponencial (1s, 2s, 4s). Desista após 3 tentativas. |

**Erros não-recuperáveis** indicam problema na requisição ou nas credenciais. Não retente automaticamente:

| Status | Código                   | O que fazer                                                                                            |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------ |
| `400`  | `bad_request`            | Corrija a requisição. O body está malformado ou o PATCH não tem campos.                                |
| `400`  | `invalid_grant`          | Code ou refresh token inválido. Redirecione o usuário pelo fluxo OAuth.                                |
| `400`  | `invalid_request`        | Um parâmetro OAuth obrigatório está ausente ou inválido. Corrija a requisição.                         |
| `400`  | `unsupported_grant_type` | Use `authorization_code` ou `refresh_token`.                                                           |
| `401`  | `token_expired`          | Renove o access token e retente a requisição original.                                                 |
| `401`  | `unauthorized`           | Verifique se o token está presente e formatado corretamente.                                           |
| `401`  | `invalid_client`         | Verifique `client_id` e `client_secret`. Não retente automaticamente.                                  |
| `403`  | `insufficient_scope`     | Solicite nova autorização com os escopos necessários.                                                  |
| `403`  | `forbidden`              | Corrija a requisição. A operação ou os valores dos campos não são permitidos para seu nível de acesso. |
| `404`  | `not_found`              | O recurso não existe. Não retente.                                                                     |
| `409`  | `conflict`               | Resolva o conflito antes de retentar.                                                                  |
| `422`  | `validation_error`       | Corrija os campos listados em `fields`. Não retente com o mesmo payload.                               |

### Padrão recomendado de tratamento de erros

```javascript theme={null}
async function apiCall(url, options) {
  const res = await fetch(url, options);

  if (!res.ok) {
    const { error } = await res.json();

    switch (error.code) {
      case 'token_expired':
        await refreshAccessToken();
        return apiCall(url, options);

      case 'invalid_grant':
        throw new Error('Autorização expirada. Usuário precisa re-autorizar.');

      case 'invalid_client':
        throw new Error('Credenciais inválidas. Verifique client_id e client_secret.');

      case 'rate_limit_exceeded': {
        const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60');
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        return apiCall(url, options);
      }

      case 'not_found':
        return null;

      case 'internal_error':
        console.error(`Erro no servidor. request_id: ${error.request_id}`);
        throw new Error(`Erro no servidor (request_id: ${error.request_id})`);

      default:
        throw new Error(`${error.code}: ${error.message} (request_id: ${error.request_id})`);
    }
  }

  return res.json();
}
```
