# Rate Limits

A API Monetarie NPC implementa limites de requisicoes para garantir estabilidade e disponibilidade para todos os clientes.

## Limites por Endpoint

Cada endpoint possui limites especificos baseados em sua natureza e impacto no sistema:

| Endpoint                         | Metodo | Limite          | Janela   | Descricao                      |
|----------------------------------|--------|-----------------|----------|--------------------------------|
| `POST /boletos`                  | POST   | 100 req         | 1 minuto | Criacao de boletos             |
| `GET /boletos`                   | GET    | 1.000 req       | 1 minuto | Listagem de boletos            |
| `GET /boletos/:nosso_numero`     | GET    | 1.000 req       | 1 minuto | Consulta de boleto             |
| `PATCH /boletos/:nosso_numero`   | PATCH  | 100 req         | 1 minuto | Atualizacao de boleto          |
| `DELETE /boletos/:nosso_numero`  | DELETE | 100 req         | 1 minuto | Cancelamento de boleto         |
| `POST /boletos/:nosso_numero/pay`| POST   | 100 req         | 1 minuto | Marcar como pago               |
| `GET /settlements`               | GET    | 1.000 req       | 1 minuto | Listagem de liquidacoes        |
| `GET /settlements/:id`           | GET    | 1.000 req       | 1 minuto | Consulta de liquidacao         |
| `POST /settlements/trigger`      | POST   | 10 req          | 1 hora   | Disparo manual de processamento|
| `GET /health`                    | GET    | 1.000 req       | 1 minuto | Health check                   |

::: warning Endpoint de Trigger
O endpoint `POST /settlements/trigger` possui um limite muito restritivo (10 req/hora) pois dispara processamento intensivo. Use apenas quando necessario.
:::

---

## Headers de Rate Limit

Todas as respostas incluem headers que informam o estado atual do seu limite:

| Header                  | Tipo    | Descricao                                          |
|-------------------------|---------|---------------------------------------------------|
| `X-RateLimit-Limit`     | integer | Numero maximo de requisicoes permitidas            |
| `X-RateLimit-Remaining` | integer | Numero de requisicoes restantes na janela atual    |
| `X-RateLimit-Reset`     | integer | Timestamp Unix de quando o limite sera resetado    |

### Exemplo de Headers

```http
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1706968800
```

**Interpretacao:**
- Limite de 100 requisicoes por minuto
- Restam 87 requisicoes
- O limite reseta em `1706968800` (timestamp Unix)

---

## Resposta de Rate Limit Excedido

Quando o limite e excedido, a API retorna status `429 Too Many Requests`:

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706968800
Retry-After: 45
```

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Limite de requisicoes excedido",
    "details": "Aguarde 45 segundos antes de fazer novas requisicoes"
  },
  "meta": {
    "request_id": "req_abc123def456"
  }
}
```

O header `Retry-After` indica quantos segundos voce deve aguardar antes de tentar novamente.

---

## Implementacao de Retry

### JavaScript com Exponential Backoff

```javascript
class Monetarie NPCClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = "https://api.monetarie_npc.com.br/api/v1/central";
    this.maxRetries = 5;
    this.baseDelay = 1000; // 1 segundo
  }

  async request(method, path, body = null, retryCount = 0) {
    const url = `${this.baseUrl}${path}`;

    const options = {
      method,
      headers: {
        "X-API-Key": this.apiKey,
        "Content-Type": "application/json",
      },
    };

    if (body) {
      options.body = JSON.stringify(body);
    }

    try {
      const response = await fetch(url, options);

      // Monitorar headers de rate limit
      const remaining = response.headers.get("X-RateLimit-Remaining");
      const limit = response.headers.get("X-RateLimit-Limit");

      if (remaining !== null) {
        console.log(`Rate limit: ${remaining}/${limit} restantes`);

        // Alerta quando restam poucas requisicoes
        if (parseInt(remaining) < parseInt(limit) * 0.1) {
          console.warn("Atencao: Proximo do limite de requisicoes!");
        }
      }

      // Tratar rate limit
      if (response.status === 429) {
        if (retryCount >= this.maxRetries) {
          throw new Error("Limite de retries excedido");
        }

        const retryAfter = response.headers.get("Retry-After");
        const delay = retryAfter
          ? parseInt(retryAfter) * 1000
          : this.baseDelay * Math.pow(2, retryCount);

        console.log(`Rate limited. Aguardando ${delay/1000}s (tentativa ${retryCount + 1}/${this.maxRetries})`);

        await this.sleep(delay);
        return this.request(method, path, body, retryCount + 1);
      }

      // Tratar erros de servidor com retry
      if (response.status >= 500) {
        if (retryCount >= this.maxRetries) {
          const data = await response.json();
          throw new Error(`Erro do servidor: ${data.error.message}`);
        }

        const delay = this.baseDelay * Math.pow(2, retryCount);
        console.log(`Erro do servidor. Aguardando ${delay/1000}s (tentativa ${retryCount + 1}/${this.maxRetries})`);

        await this.sleep(delay);
        return this.request(method, path, body, retryCount + 1);
      }

      return response.json();
    } catch (error) {
      if (error.name === "TypeError" && retryCount < this.maxRetries) {
        // Erro de rede - tentar novamente
        const delay = this.baseDelay * Math.pow(2, retryCount);
        console.log(`Erro de rede. Aguardando ${delay/1000}s`);

        await this.sleep(delay);
        return this.request(method, path, body, retryCount + 1);
      }
      throw error;
    }
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Metodos de conveniencia
  async createBoleto(boleto) {
    return this.request("POST", "/boletos", { boleto });
  }

  async getBoleto(nossoNumero) {
    return this.request("GET", `/boletos/${nossoNumero}`);
  }

  async listBoletos(params = {}) {
    const query = new URLSearchParams(params).toString();
    return this.request("GET", `/boletos?${query}`);
  }
}

// Uso
const client = new Monetarie NPCClient(process.env.MONETARIE_NPC_API_KEY);

const result = await client.createBoleto({
  nosso_numero: "12345678901",
  amount_cents: 15000,
  due_date: "2026-03-15",
  payer_document: "12345678901",
  payer_name: "Joao Silva",
  beneficiary_ispb: "02992335",
  beneficiary_name: "Empresa XYZ"
});
```

### Python com Exponential Backoff

```python
import requests
import time
import os
from typing import Optional, Dict, Any

class Monetarie NPCClient:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.monetarie_npc.com.br/api/v1/central"
        self.max_retries = 5
        self.base_delay = 1.0  # 1 segundo
        self.session = requests.Session()
        self.session.headers.update({
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        })

    def request(
        self,
        method: str,
        path: str,
        json: Optional[Dict] = None,
        retry_count: int = 0
    ) -> Dict[str, Any]:
        url = f"{self.base_url}{path}"

        try:
            response = self.session.request(method, url, json=json)

            # Monitorar headers de rate limit
            remaining = response.headers.get("X-RateLimit-Remaining")
            limit = response.headers.get("X-RateLimit-Limit")

            if remaining is not None:
                print(f"Rate limit: {remaining}/{limit} restantes")

                # Alerta quando restam poucas requisicoes
                if int(remaining) < int(limit) * 0.1:
                    print("Atencao: Proximo do limite de requisicoes!")

            # Tratar rate limit
            if response.status_code == 429:
                if retry_count >= self.max_retries:
                    raise Exception("Limite de retries excedido")

                retry_after = response.headers.get("Retry-After")
                delay = (
                    float(retry_after) if retry_after
                    else self.base_delay * (2 ** retry_count)
                )

                print(f"Rate limited. Aguardando {delay}s (tentativa {retry_count + 1}/{self.max_retries})")
                time.sleep(delay)
                return self.request(method, path, json, retry_count + 1)

            # Tratar erros de servidor com retry
            if response.status_code >= 500:
                if retry_count >= self.max_retries:
                    data = response.json()
                    raise Exception(f"Erro do servidor: {data['error']['message']}")

                delay = self.base_delay * (2 ** retry_count)
                print(f"Erro do servidor. Aguardando {delay}s (tentativa {retry_count + 1}/{self.max_retries})")
                time.sleep(delay)
                return self.request(method, path, json, retry_count + 1)

            return response.json()

        except requests.exceptions.RequestException as e:
            if retry_count < self.max_retries:
                delay = self.base_delay * (2 ** retry_count)
                print(f"Erro de rede. Aguardando {delay}s")
                time.sleep(delay)
                return self.request(method, path, json, retry_count + 1)
            raise

    # Metodos de conveniencia
    def create_boleto(self, boleto: Dict) -> Dict:
        return self.request("POST", "/boletos", {"boleto": boleto})

    def get_boleto(self, nosso_numero: str) -> Dict:
        return self.request("GET", f"/boletos/{nosso_numero}")

    def list_boletos(self, **params) -> Dict:
        query = "&".join(f"{k}={v}" for k, v in params.items())
        path = f"/boletos?{query}" if query else "/boletos"
        return self.request("GET", path)


# Uso
client = Monetarie NPCClient(os.environ["MONETARIE_NPC_API_KEY"])

result = client.create_boleto({
    "nosso_numero": "12345678901",
    "amount_cents": 15000,
    "due_date": "2026-03-15",
    "payer_document": "12345678901",
    "payer_name": "Joao Silva",
    "beneficiary_ispb": "02992335",
    "beneficiary_name": "Empresa XYZ"
})
```

---

## Boas Praticas

### 1. Monitore os Headers

Sempre verifique os headers `X-RateLimit-Remaining` e ajuste sua taxa de requisicoes proativamente:

```javascript
if (remaining < limit * 0.2) {
  // Restam menos de 20% - reduzir velocidade
  await sleep(1000);
}
```

### 2. Implemente Exponential Backoff

Quando receber erro 429 ou 5xx, use backoff exponencial:

```
Tentativa 1: 1 segundo
Tentativa 2: 2 segundos
Tentativa 3: 4 segundos
Tentativa 4: 8 segundos
Tentativa 5: 16 segundos
```

::: tip Jitter
Adicione um pequeno valor aleatorio (jitter) ao delay para evitar que multiplos clientes retry no mesmo instante:

```javascript
const jitter = Math.random() * 1000;
const delay = baseDelay * Math.pow(2, retryCount) + jitter;
```
:::

### 3. Circuit Breaker Pattern

Para sistemas de alta disponibilidade, implemente um circuit breaker:

```javascript
class CircuitBreaker {
  constructor(threshold = 5, timeout = 30000) {
    this.failures = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
    this.lastFailure = null;
  }

  async execute(fn) {
    if (this.state === "OPEN") {
      if (Date.now() - this.lastFailure > this.timeout) {
        this.state = "HALF_OPEN";
      } else {
        throw new Error("Circuit breaker is OPEN");
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = "CLOSED";
  }

  onFailure() {
    this.failures++;
    this.lastFailure = Date.now();
    if (this.failures >= this.threshold) {
      this.state = "OPEN";
    }
  }
}

// Uso
const breaker = new CircuitBreaker();

try {
  const result = await breaker.execute(() =>
    client.createBoleto(boletoData)
  );
} catch (error) {
  if (error.message === "Circuit breaker is OPEN") {
    // Servico temporariamente indisponivel
    // Usar fallback ou notificar usuario
  }
}
```

### 4. Agrupe Requisicoes

Quando possivel, agrupe operacoes para reduzir o numero de requisicoes:

```javascript
// Evite: multiplas requisicoes individuais
for (const id of boletoIds) {
  await client.getBoleto(id); // Muitas requisicoes!
}

// Prefira: usar listagem com filtros
const result = await client.listBoletos({
  nosso_numero: boletoIds.join(","),
  limit: 100
});
```

### 5. Cache de Leitura

Implemente cache para requisicoes GET frequentes:

```javascript
const cache = new Map();
const CACHE_TTL = 60000; // 1 minuto

async function getBoletoWithCache(nossoNumero) {
  const cacheKey = `boleto:${nossoNumero}`;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const result = await client.getBoleto(nossoNumero);
  cache.set(cacheKey, {
    data: result.data,
    timestamp: Date.now()
  });

  return result.data;
}
```

---

## Limites por Ambiente

Os limites variam de acordo com o ambiente:

| Ambiente       | Multiplicador | POST /boletos | GET endpoints | POST /settlements/trigger |
|----------------|---------------|---------------|---------------|---------------------------|
| **Producao**   | 1x            | 100/min       | 1.000/min     | 10/hora                   |
| **Sandbox**    | 0.1x          | 10/min        | 100/min       | 1/hora                    |
| **Local**      | Ilimitado     | -             | -             | -                         |

::: info Ambiente de Testes
O ambiente sandbox possui limites reduzidos para evitar uso excessivo durante desenvolvimento. Para testes de carga, entre em contato com o suporte.
:::

---

## Solicitar Aumento de Limite

Se os limites atuais nao atendem sua demanda, voce pode solicitar um aumento:

### Requisitos

1. **Conta em producao ativa** por pelo menos 30 dias
2. **Historico de uso** demonstrando necessidade
3. **Caso de uso** justificado

### Processo de Solicitacao

1. Acesse o [Monetarie Portal](https://npcadmin-dev.fluxiq.com.br)
2. Navegue para **Configuracoes** > **API** > **Rate Limits**
3. Clique em **Solicitar Aumento**
4. Preencha o formulario com:
   - Endpoint(s) que precisam de aumento
   - Limite desejado
   - Justificativa tecnica
   - Volume esperado de transacoes

### SLA de Resposta

| Tipo de Conta | Tempo de Resposta |
|---------------|-------------------|
| Enterprise    | 1 dia util        |
| Business      | 3 dias uteis      |
| Standard      | 5 dias uteis      |

::: tip Dica
Antes de solicitar aumento, revise suas implementacoes para garantir que voce esta utilizando as melhores praticas descritas nesta pagina. Muitas vezes, otimizacoes no codigo podem resolver problemas de rate limit.
:::

---

## Proximos Passos

- [Codigos de Erro](/reference/errors) - Entenda todos os erros possiveis
- [Autenticacao](/authentication) - Configurar API Key
- [Ambientes](/environments) - Testar em sandbox
