Constaia
Conceptos

Límites de uso y cuotas

Límites de peticiones por segundo de la API de Constaia, cabeceras RateLimit, 429 con Retry-After, límites de ficheros y lotes, y cómo reintentar con backoff.

Constaia limita cuántas peticiones puede hacer cada clave de API por segundo para proteger el servicio y repartirlo de forma justa. Esta página explica qué se aplica hoy, qué está en despliegue y cómo diseñar tu integración para no tropezar con los límites.

Qué se aplica hoy

  • Por clave de API: cada clave tiene su propio contador. Dos claves de la misma cuenta no se restan entre sí.
  • Ventana fija de 1 segundo.
  • Límite por defecto: 10 peticiones por segundo. Algunas cuentas tienen un límite configurado distinto; el valor real de tu clave viene siempre en RateLimit-Limit.
  • Modo test igual que modo real: las claves ck_test_ tienen el mismo límite de peticiones por segundo (sin cuota de créditos).
  • Cuenta cualquier petición autenticada a /v1: analizar, clasificar, listar, consultar un análisis, etc.

Cabeceras

Cada respuesta autenticada incluye el estado del límite:

CabeceraSignificado
RateLimit-LimitPeticiones permitidas en la ventana.
RateLimit-RemainingPeticiones que te quedan en la ventana actual.
RateLimit-ResetSegundos hasta que se reinicia la ventana.
RateLimit-PolicyPolítica aplicada, por ejemplo 10;w=1 (10 peticiones, ventana de 1 s).
Retry-AfterSolo en 429: segundos que debes esperar.
Respuesta 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2
RateLimit-Limit: 10
RateLimit-Remaining: 7
RateLimit-Reset: 1
RateLimit-Policy: 10;w=1

Las cabeceras RateLimit-* están expuestas por CORS, aunque tu clave nunca debe usarse desde un navegador.

Los SDK guardan las de la última respuesta: constaia.lastRateLimit en JavaScript, $constaia->lastRateLimit en PHP y client.last_rate_limit en Python, con limit, remaining, reset, policy y retryAfter (retry_after en Python).

Respuesta 429

Si superas el límite, recibes 429 con Retry-After:

Respuesta 429
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 1
RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 1
RateLimit-Policy: 10;w=1

{
  "error": {
    "type": "rate_limited",
    "code": "rate_limited",
    "message": "Demasiadas peticiones. Reintenta en un momento.",
    "request_id": "req_01J9Z8Q3K4M5N6P7Q8R9S0T1V3"
  }
}

Una petición rechazada con 429 no llega a procesarse ni se cobra.

Niveles por plan (en despliegue)

En despliegue

Hoy solo se aplica el límite de peticiones por segundo descrito arriba (10 por defecto). La tabla siguiente es el esquema por niveles que se está desplegando: las columnas de análisis simultáneos y páginas por minuto todavía no se aplican. Diseña tu integración para respetar RateLimit-* y Retry-After y no tendrás que cambiar nada cuando entren en vigor.

NivelPeticiones/sAnálisis simultáneosPáginas/minCuándo
Gratis2260Sin compras
Pago1010600Con algún pack comprado
EmpresaA medidaA medidaA medidaContrato

En modo test el límite de peticiones por segundo será el mismo que el de tu nivel, sin cuota de créditos.

Otros límites

LímiteValorError si lo superas
Tamaño por fichero20 MB413 file_too_large
Páginas de PDF, análisis síncrono30422 too_many_pages
Páginas de PDF, async: true o lotes200422 too_many_pages
Documentos por lote1–100400 batch_too_large / 400 empty_batch
Tipos en expect1–20422 invalid_parameter
metadata20 claves; clave ≤ 40 caracteres; valor ≤ 500422 invalid_parameter
Descarga de file_url15 s, https, IP pública422 file_url_unreachable / 400 invalid_file_url
Idempotency-Key255 caracteres400 invalid_idempotency_key
Resultados por página al listar1–100 (10 por defecto)422 invalid_parameter
Espera en análisis síncrono30 s; después, 202 y resultado por webhook—

Cómo reintentar

Reglas:

  1. Reintenta solo lo reintentable: 429, 409 idempotency_in_progress, 5xx (salvo 501) y errores de red. Ver errores.
  2. En 429, espera exactamente lo que indica Retry-After.
  3. En el resto, usa backoff exponencial con jitter: 0,5 s, 1 s, 2 s, 4 s… con un tope, más un componente aleatorio para que tus procesos no reintenten todos a la vez.
  4. Manda una Idempotency-Key en cada POST y reutilízala en los reintentos, para no cobrar dos veces. Ver idempotencia.
  5. Limita el número de intentos (3–5) y registra el X-Request-Id.

Los SDK de JavaScript, PHP y Python ya lo hacen: reintentan 429, 408, 409 idempotency_in_progress, 5xx (salvo 501) y errores de red, respetan Retry-After (JavaScript y Python esperan como máximo 60 s), usan backoff exponencial con jitter y reutilizan la Idempotency-Key. Por defecto hacen 2 reintentos (maxRetries en JavaScript, max_retries en PHP y Python).

Con el SDK solo tienes que ajustar maxRetries:

src/constaia.ts
import { Constaia } from "@constaia/sdk";

export const constaia = new Constaia({ maxRetries: 4, timeout: 90_000 });

Si llamas a la API con fetch directamente:

src/post-with-retry.ts
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);

export async function postWithRetry(url: string, body: FormData | string, headers: Record<string, string>, maxRetries = 4) {
  const idempotencyKey = crypto.randomUUID();
  for (let attempt = 0; ; attempt++) {
    try {
      const res = await fetch(url, {
        method: "POST",
        body,
        headers: { ...headers, Authorization: `Bearer ${process.env.CONSTAIA_API_KEY}`, "Idempotency-Key": idempotencyKey },
      });
      const payload = await res.json();
      const inProgress = res.status === 409 && payload?.error?.code === "idempotency_in_progress";
      if ((!RETRYABLE.has(res.status) && !inProgress) || attempt >= maxRetries) {
        if (!res.ok) throw Object.assign(new Error(payload.error?.message), { status: res.status, error: payload.error });
        return payload;
      }
      const retryAfter = Number(res.headers.get("retry-after"));
      await sleep(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
    } catch (err) {
      if ((err as { status?: number }).status || attempt >= maxRetries) throw err;
      await sleep(backoff(attempt)); // error de red
    }
  }
}

const backoff = (attempt: number) => Math.min(8000, 500 * 2 ** attempt) * (0.5 + Math.random() / 2);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

Limita la concurrencia en tu cliente

Reintentar bien no basta si lanzas cientos de análisis a la vez: la mayoría acabarán en 429. Limita cuántas peticiones tienes en vuelo. Los tres SDK lo hacen en el cliente (4 por defecto); las peticiones que sobran esperan su turno:

SDKOpción
JavaScriptnew Constaia({ maxConcurrency: 4 })
PHPnew Client(null, ['max_concurrency' => 4]), usado por analyzeMany() para analizar en paralelo
PythonConstaia(max_concurrency=4) o AsyncConstaia(max_concurrency=4), con hilos o tareas de asyncio

El límite es por cliente (por proceso). Con varios workers, el límite por segundo de la clave es compartido: mantén concurrencia × workers acorde con él.

scripts/analyze-folder.ts
import { readdir } from "node:fs/promises";
import path from "node:path";
import { Constaia } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";

const constaia = new Constaia({ maxConcurrency: 4, maxRetries: 4 }); // como máximo 4 peticiones en vuelo

const dir = process.argv[2] ?? "./docs";
const files = (await readdir(dir)).filter((f) => /\.(jpe?g|png|webp|heic|pdf)$/i.test(f));

const results = await Promise.all(
  files.map(async (name) => {
    const analysis = await constaia.analyze(await fromPath(path.join(dir, name)), { expect: "es_dni" });
    return { name, status: analysis.verdict?.status };
  }),
);
console.table(results);

Sin SDK, limita tú la concurrencia: p-limit en JavaScript o un asyncio.Semaphore en Python.

Mucho volumen: usa lotes

Para procesar cientos de documentos, un lote es más eficiente que cientos de llamadas: una sola petición POST /v1/batches admite hasta 100 documentos, cuenta como una petición para el límite por segundo, responde 202 al momento y te avisa con el webhook batch.completed. Guía completa: lotes masivos.

Controlar el gasto

Próximamente: tope de gasto mensual

Habrá un tope opcional de créditos al mes por cuenta, con avisos por email al 80 % y al 100 %. Todavía no está disponible.

Mientras tanto:

  • Configura el umbral de saldo bajo en el panel y suscríbete al webhook credits.low (solo modo real, se envía una vez hasta que recargues). Ver webhooks.
  • Consulta GET /v1/balance antes de trabajos grandes. Lo que puedes gastar es credits_available + free_tier_remaining.
  • Desarrolla y prueba con claves ck_test_: nunca cobran. Ver modo test.

Más en créditos y facturación.

Pedir límites más altos

Si necesitas más peticiones por segundo, escribe a hola@constaia.com con tu cuenta, el volumen previsto (documentos al día y picos) y el caso de uso. Los límites se pueden ampliar por cuenta. El plan Empresa incluye límites a medida.

Siguientes pasos

En esta página