# Tratamento de Erros

## Códigos HTTP

| Código | Significado | Content-Type | Ação recomendada |
|--------|-------------|--------------|------------------|
| 200 | Sucesso | `application/json` | — |
| 401 | Não autenticado | `text/plain` | Verificar API Key ou token JWT |
| 403 | Sem permissão | `text/plain` | Verificar roles do token |
| 404 | Não encontrado | `text/plain` | Verificar URL do endpoint |
| 422 | Validação falhou | `application/json` ou vazio | Corrigir parâmetros (ver abaixo) |
| 429 | Rate limit / quota | `text/plain` | Ver seção Rate Limiting & Quota |
| 500 | Erro interno | `text/plain` | Retry com backoff, contatar suporte |

## Formato do corpo de erro

**Erros 401, 403, 404, 429 e 500** retornam **texto plano** — use `resp.text`, não `resp.json()`. O conteúdo do texto pode variar e não deve ser usado para decidir o tratamento — use o código HTTP:

| Código | Situação |
|--------|----------|
| 401 | API key ausente ou inválida (o corpo pode vir vazio quando a chave não é enviada) |
| 404 | Endpoint inexistente (`404 page not found`) |

**Erro 422** possui dois cenários distintos:

1. **API Gateway** — quando o tipo do query parameter é inválido (ex: `lat=abc`), o gateway rejeita a requisição **antes** de encaminhá-la ao serviço. Neste caso o corpo da resposta é **vazio** (`content-length: 0`).
2. **Serviço** — quando o parâmetro tem tipo correto mas valor inválido (ex: `lat` ausente), o serviço retorna JSON no formato `ValidationError`.

> **Importante:** sempre verifique se o corpo da resposta 422 tem conteúdo antes de fazer parse JSON.

## Formato do erro 422 (ValidationError)

Quando os parâmetros enviados são inválidos e a requisição chega ao serviço, a API retorna um `422` com o formato padrão do FastAPI:

```json
{
  "detail": [
    {
      "type": "missing",
      "loc": ["query", "lat"],
      "msg": "Field required",
      "input": null
    }
  ]
}
```

Cada item em `detail` contém:
- `type`: tipo do erro (ex: `missing`, `value_error`, `type_error`)
- `loc`: localização do parâmetro (ex: `["query", "lat"]`, `["body", "coordenadas"]`)
- `msg`: mensagem descritiva do erro
- `input`: valor recebido (ou `null` se ausente)

## Exemplos de tratamento

**Python (httpx):**

```python
import httpx

resp = httpx.get(
    "https://api.prismadata.io/v1/location/prison",
    params={"lat": -25.5, "lng": -54.5},
    headers={"X-Apikey": "ak_xxxxxxxxxxxxxxxx"},
)

if resp.status_code == 200:
    dados = resp.json()
elif resp.status_code == 422:
    # Gateway pode retornar 422 com body vazio
    if resp.headers.get("content-type", "").startswith("application/json") and resp.text:
        erros = resp.json()["detail"]
        for erro in erros:
            print(f"Erro em {erro['loc']}: {erro['msg']}")
    else:
        print("Parâmetro com tipo inválido (rejeitado pelo gateway)")
elif resp.status_code == 429:
    reset = resp.headers.get("X-RateLimit-Reset")
    print(f"Rate limit. Retry após timestamp {reset}")
else:
    # 401, 403, 404, 500 — corpo é texto plano
    print(f"Erro {resp.status_code}: {resp.text}")
```

**JavaScript (fetch):**

```javascript
const resp = await fetch(
  "https://api.prismadata.io/v1/location/prison?lat=-25.5&lng=-54.5",
  { headers: { "X-Apikey": "ak_xxxxxxxxxxxxxxxx" } }
);

if (resp.ok) {
  const dados = await resp.json();
} else if (resp.status === 422) {
  // Gateway pode retornar 422 com body vazio
  const contentType = resp.headers.get("content-type") || "";
  const body = await resp.text();
  if (contentType.includes("application/json") && body) {
    const { detail } = JSON.parse(body);
    detail.forEach(e => console.error(`Erro em ${e.loc}: ${e.msg}`));
  } else {
    console.error("Parâmetro com tipo inválido (rejeitado pelo gateway)");
  }
} else if (resp.status === 429) {
  const reset = resp.headers.get("X-RateLimit-Reset");
  console.warn(`Rate limit. Retry após timestamp ${reset}`);
} else {
  // 401, 403, 404, 500 — corpo é texto plano
  console.error(`Erro ${resp.status}: ${await resp.text()}`);
}
```

## Token JWT expirado

O token JWT tem validade configurável de **1 a 15 minutos** (default: 15 min), definida pelo administrador da conta por usuário. O campo `exp` no payload do JWT contém o timestamp Unix de expiração.

### Com `X-Apikey` (recomendado)

O gateway renova o token automaticamente a cada requisição. Nenhum tratamento especial é necessário.

### Com `username/password` via `/auth/token`

Ao gerenciar o JWT diretamente, extraia o campo `exp` dos claims e renove o token **antes** da expiração:

```python
import httpx
import json
import base64
import time

BASE = "https://api.prismadata.io/v1"

class TokenManager:
    def __init__(self, username: str, password: str):
        self.username = username
        self.password = password
        self.token = None
        self.exp = 0

    def _obter_token(self):
        resp = httpx.post(
            f"{BASE}/auth/token",
            data={"username": self.username, "password": self.password},
        )
        resp.raise_for_status()
        self.token = resp.json()["access_token"]
        # Extrair exp do payload JWT (segunda parte, base64)
        payload = self.token.split(".")[1]
        payload += "=" * (-len(payload) % 4)  # padding
        claims = json.loads(base64.urlsafe_b64decode(payload))
        self.exp = claims["exp"]

    def get_headers(self) -> dict:
        if time.time() >= self.exp - 30:  # renova 30s antes
            self._obter_token()
        return {"Authorization": f"Bearer {self.token}"}

mgr = TokenManager("user@email.com", "senha")
resp = httpx.get(
    f"{BASE}/location/prison",
    params={"lat": -25.5, "lng": -54.5},
    headers=mgr.get_headers(),
)
```

---

- Índice geral: https://prismadata.io/llms.txt
- Documentação Redoc interativa: https://prismadata.io/redoc.html
