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

# Limites API Camtom | Rate limits y creditos

> Limites de concurrencia, sistema de creditos y mejores practicas para usar la API de clasificacion arancelaria y extraccion de documentos.

La API de Camtom esta disenada para manejar volumen de produccion. Los limites dependen del tipo de endpoint y la capacidad del servicio.

## Limites actuales

### Endpoints de IA

Los endpoints principales de la API (TariffPro, CamtomDocs, Quoter, etc.) **no tienen rate limiting global por peticiones por minuto**. En su lugar, el control se realiza mediante:

* **Concurrencia:** TariffPro Mini tiene un limite de **50 peticiones simultaneas por worker** del servicio. Si se alcanza este limite, las peticiones adicionales esperan hasta que haya capacidad.
* **Creditos:** Cada llamada consume creditos de la suscripcion de tu organizacion. Cuando los creditos se agotan, las peticiones son rechazadas.
* **Suscripcion:** Solo organizaciones con suscripcion `pro` o `enterprise` pueden usar API keys.

### Endpoints internos (Node.js - KYB)

El modulo KYB (Know Your Business) es el unico con rate limiting explicito por peticiones. Estos limites aplican a rutas internas de onboarding, no a los endpoints publicos de la API:

| Operacion                 | Ventana  | Max peticiones |
| ------------------------- | -------- | -------------- |
| Creacion de verificacion  | 1 minuto | 5              |
| Consultas                 | 1 minuto | 30             |
| Cambios de estado         | 1 minuto | 10             |
| Verificaciones EFOS       | 1 minuto | 10             |
| Operaciones de documentos | 1 minuto | 15             |
| Cierre de verificaciones  | 1 minuto | 5              |
| Webhooks                  | 1 minuto | 50             |

Al exceder estos limites, la respuesta es:

```json theme={null}
{
  "error": "RATE_LIMITED",
  "message": "Too many ... requests. Try again in a minute."
}
```

## Sistema de creditos como control de uso

El principal mecanismo de control de uso en la API es el **sistema de creditos**. Cada organizacion tiene limites definidos en su suscripcion:

| Recurso     | Como se verifica                                              |
| ----------- | ------------------------------------------------------------- |
| TariffPro   | Segun tu plan de suscripcion                                  |
| CamtomDocs  | Paginas procesadas cobradas (ver `billed_pages` en respuesta) |
| Digiter     | 1 credito por archivo procesado                               |
| Quoter Meli | 1 credito por checkout                                        |

Cuando los creditos disponibles llegan a cero:

* TariffPro devuelve un error indicando creditos insuficientes
* CamtomDocs devuelve `402 CamtomDocsCreditError`

Puedes consultar tu saldo de creditos desde el endpoint `GET /api/v3/organization/subscription-info` o desde [app.camtomx.com](https://app.camtomx.com).

## Estrategia de reintentos

Aunque no hay rate limiting estricto en los endpoints principales, es buena practica implementar **backoff exponencial** para manejar errores transitorios (5xx):

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  def request_with_retry(url, headers, data, files=None, params=None, max_retries=5):
      for attempt in range(max_retries):
          response = requests.post(url, headers=headers, data=data, files=files, params=params)

          if response.status_code == 200:
              return response.json()

          if response.status_code == 429:
              # Limite excedido (solo aplica en ciertos endpoints)
              wait_time = 2 ** attempt
              print(f"Limite excedido. Esperando {wait_time}s (intento {attempt + 1}/{max_retries})")
              time.sleep(wait_time)
              continue

          if response.status_code >= 500:
              wait_time = 2 ** attempt  # 1s, 2s, 4s, 8s, 16s
              print(f"Error {response.status_code}. Reintentando en {wait_time}s...")
              time.sleep(wait_time)
              continue

          # Para errores 4xx (excepto 429), no reintentar
          error_body = response.json()
          raise Exception(f"Error {response.status_code}: {error_body.get('message', 'Error desconocido')}")

      raise Exception(f"Fallo despues de {max_retries} intentos")
  ```

  ```javascript JavaScript theme={null}
  async function requestWithRetry(url, headers, formData, params = {}, maxRetries = 5) {
    const queryString = new URLSearchParams(params).toString();
    const fullUrl = queryString ? `${url}?${queryString}` : url;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(fullUrl, {
        method: "POST",
        headers,
        body: formData
      });

      if (response.ok) {
        return await response.json();
      }

      if (response.status === 429) {
        const waitTime = 2 ** attempt;
        console.log(`Limite excedido. Esperando ${waitTime}s (intento ${attempt + 1}/${maxRetries})`);
        await new Promise(r => setTimeout(r, waitTime * 1000));
        continue;
      }

      if (response.status >= 500) {
        const waitTime = 2 ** attempt; // 1s, 2s, 4s, 8s, 16s
        console.log(`Error ${response.status}. Reintentando en ${waitTime}s...`);
        await new Promise(r => setTimeout(r, waitTime * 1000));
        continue;
      }

      // Para errores 4xx (excepto 429), no reintentar
      const errorBody = await response.json();
      throw new Error(`Error ${response.status}: ${errorBody.message || "Error desconocido"}`);
    }

    throw new Error(`Fallo despues de ${maxRetries} intentos`);
  }
  ```
</CodeGroup>

## Mejores practicas

### 1. Implementa caching

Si clasificas el mismo producto repetidamente, almacena el resultado en cache para evitar llamadas innecesarias y conservar creditos:

```python theme={null}
from functools import lru_cache
import requests
import json

@lru_cache(maxsize=1000)
def classify_product(description, country_code):
    response = requests.post(
        "https://api.camtomx.com/api/v3/tariffpro/tariffpro-mini",
        headers={"Authorization": f"Bearer {api_key}"},
        params={"country_code": country_code, "model": "pro"},
        data={"product_description": description}
    )
    return json.dumps(response.json())  # lru_cache requiere tipos hashables
```

### 2. Controla la concurrencia

Dado que TariffPro Mini tiene un limite de 50 peticiones simultaneas por worker, controla la concurrencia de tus llamadas:

```python theme={null}
import asyncio

semaphore = asyncio.Semaphore(20)  # Limita a 20 peticiones simultaneas

async def classify_with_limit(description, country_code):
    async with semaphore:
        return await classify_async(description, country_code)
```

### 3. Usa procesamiento por lotes

Para clasificar grandes volumenes de productos, considera usar los endpoints batch de TariffPro (`/xlsx`, `/items-to-excel`, `/tariff-tables`) que procesan multiples items en una sola peticion y devuelven un `job_id` para consultar el progreso.

### 4. Monitorea tus creditos

Consulta periodicamente tu saldo de creditos para evitar interrupciones:

```python theme={null}
response = requests.get(
    "https://api.camtomx.com/api/v3/organization/subscription-info",
    headers={"Authorization": f"Bearer {api_key}"}
)
subscription = response.json()
# Revisa los limites disponibles
```

<Warning>
  No intentes evadir los limites de creditos creando multiples organizaciones o cuentas. Esta practica viola los terminos de uso y puede resultar en la suspension de tu cuenta.
</Warning>

## ¿Necesitas más capacidad?

Si los creditos o la concurrencia de tu plan actual no son suficientes para tu caso de uso, contacta al equipo de ventas para conocer los planes Enterprise con limites personalizados:

<Card title="Contactar ventas" icon="envelope" href="https://camtomx.com/contacto">
  Solicita un plan Enterprise con capacidad personalizada para tu volumen de operaciones.
</Card>
