Límites de uso y CORS
Cuántas peticiones admite la clave de tu aplicación, qué pasa al superar el límite, desde qué webs se puede llamar a la API externa y qué permisos admite una clave que va en una página.
Actualizado el . 10 min de lectura
Incluido en todos los planes. Ver precios
La API externa aplica varios frenos antes de atender una petición: cuántas puede hacer tu aplicación (los límites de uso), desde qué webs puede llamarla un navegador (CORS, el mecanismo con el que el navegador decide si una web puede leer la respuesta de otro dominio) y qué permisos admite una clave que va en el código de una página. Conocerlos te evita los 429 y los 403 que no esperabas.
Los límites de un vistazo
Cada freno lleva su propio contador. Por eso un 429 puede llegar aunque X-Utilia-Quota-Remaining diga que al cupo por hora todavía le quedan peticiones: lo ha cortado otro freno.
| Freno | Límite | Qué cuenta | Al pasarse |
|---|---|---|---|
| Cupo por hora de la aplicación | 1.000 por hora al crearla | Todas sus peticiones a /external/v1, salvo las que envían errores | 429 RATE_LIMIT_EXCEEDED |
| Freno general | 600 por minuto | Cada IP en cada ruta, en toda la API | 429 sin errorCode |
| Contactos | 600 por hora | Listado, búsqueda y ficha de /contacts, por aplicación | 429 RATE_LIMIT_EXCEEDED |
| Leads | 600 por hora | El resto de rutas de /leads, por aplicación | 429 RATE_LIMIT_EXCEEDED |
| Clientes | 500 por hora | El resto de rutas de /clients, por aplicación | 429 RATE_LIMIT_EXCEEDED |
| Sincronizar | 60 por hora | GET /contacts/sync, /leads/sync y /clients/sync, cada una con su contador | 429 RATE_LIMIT_EXCEEDED |
| Buscar por correo | 300 por hora | GET /contacts/by-email y /leads/by-email, cada una con su contador | 429 RATE_LIMIT_EXCEEDED |
| Buscar un cliente por NIF | 100 por hora | GET /clients/by-tax-id, por aplicación | 429 RATE_LIMIT_EXCEEDED |
| Enviar facturas por correo | 100 por hora | POST /invoices/:id/send y el correo al emitir, por aplicación | 429 RATE_LIMIT_EXCEEDED |
| Facturas y cobros | 60 por minuto | Facturas y sus acciones, rectificativas, métodos de pago, suscripciones, cobros programados y autorizaciones de cobro, por aplicación | 429 sin errorCode |
| Validar y emitir rectificativas | 30 y 10 por minuto | POST /invoices/:id/rectifications/validate y POST /invoices/:id/rectifications | 429 sin errorCode |
| Reintentar un cobro de suscripción | 3 por minuto | POST /subscriptions/:id/invoices/:invoiceId/retry-charge | 429 sin errorCode |
| Registro de errores | 120 por minuto | Cada ruta de /errors, por aplicación | 429 sin errorCode |
| Errores del navegador | 30 por minuto | POST /tickets/client-errors, por aplicación | 429 sin errorCode |
Los frenos por hora devuelven errorCode: 'RATE_LIMIT_EXCEEDED' y su mensaje dice qué cupo se ha agotado. Los frenos por minuto responden 429 sin errorCode, con el mensaje «Has hecho demasiadas peticiones seguidas» y las cabeceras X-RateLimit-* y Retry-After. En los dos casos, espera lo que diga Retry-After (o retryAfterSeconds, si viene en el cuerpo) antes de repetir.
El cupo por hora de tu clave
Las claves de las aplicaciones externas nacen con 1.000 peticiones por hora, y cada respuesta te dice cuánto te queda.
- El contador arranca con la primera petición y se reinicia una hora después. No es una ventana deslizante: si gastas el cupo en diez minutos, esperas los cincuenta restantes.
- Cuentan todas las peticiones de una aplicación activa con una clave válida, también las que después fallan por el origen, por el plan o por los permisos de la API. La clave vigente y la anterior, durante una renovación, comparten contador.
- La excepción son las que ENVÍAN errores al registro (
POST /external/v1/errors/report,POST /external/v1/errors/batchyPOST /external/v1/tickets/client-errors): tienen su propio límite por minuto, para que una avalancha de errores no deje sin servicio al resto de tu integración, y por eso sus respuestas no llevan las cabeceras del cupo. Consultar o gestionar esos errores sí cuenta como cualquier otra petición (Límites y conservación). - Toda respuesta que cuenta lleva el estado del cupo en tres cabeceras:
X-Utilia-Quota-Limit(el cupo por hora),X-Utilia-Quota-Remaining(lo que queda) yX-Utilia-Quota-Reset(cuándo se vacía el contador, en segundos UNIX). - Al superarlo, la respuesta es
429conerrorCode: 'RATE_LIMIT_EXCEEDED',X-Utilia-Quota-Remaining: 0y la cabeceraRetry-Aftercon los segundos que le quedan de verdad a la hora en curso.
HTTP/1.1 429 Too Many Requests
Retry-After: 1260
X-Utilia-Quota-Limit: 1000
X-Utilia-Quota-Remaining: 0
X-Utilia-Quota-Reset: 1791455760{
"statusCode": 429,
"message": "Límite de peticiones superado",
"error": "TOO_MANY_REQUESTS",
"errorCode": "RATE_LIMIT_EXCEEDED",
"fixedBy": "developer",
"timestamp": "2026-10-08T10:15:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}El cuerpo trae también docsUrl, la dirección del apartado de este error en la documentación pública.
Las cabeceras X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset son otra cosa: las del freno por minuto (por IP y ruta, y en algunas rutas por aplicación), con Reset en segundos que faltan. Para el cupo por hora de tu clave, lee las X-Utilia-Quota-*.
Si recibes un 429, espera lo que diga Retry-After antes de repetir. No reintentes enseguida: cada intento cuenta. Si Retry-After es muy largo, es un cupo por hora agotado (el de la aplicación o el de un recurso, como contactos o clientes): reparte las llamadas en el tiempo en vez de esperar en bucle. Si X-Utilia-Quota-Remaining es mayor que cero, el que te ha frenado es el cupo propio de esa ruta, que la tabla de arriba nombra.
Las rutas de diagnóstico tienen además su propio freno: 30 peticiones al minuto por aplicación en /external/v1/me y /external/v1/diagnostics/*, 6 al minuto para la prueba activa (POST /external/v1/diagnostics/run) y 5 al minuto para los avisos de prueba, que se comparten con el panel. Los dos últimos responden 429 DIAGNOSTICS_RATE_LIMITED con las X-RateLimit-* de su propio cupo (Remaining a 0), y todos llevan Retry-After. Cómo se usan está en Probar tu integración.
Las claves de API de la organización (utk_…) tienen su propio cupo: 1.000 peticiones por hora de partida, que se pueden subir hasta 10.000 y se renuevan al empezar cada hora de reloj.
CORS: desde qué webs se puede llamar
CORS solo afecta a las llamadas que hace un navegador. Desde tu servidor, con curl o con el SDK en Node.js o Python, no interviene. Una clave de API de la organización no se puede usar desde un navegador en ningún caso, ni siquiera desde un origen permitido: responde 403 con API_KEY_BROWSER_NOT_ALLOWED.
Cada aplicación externa tiene una lista de orígenes permitidos (en la API, allowedOrigins). Se edita en su ficha, en Configuración › Ajustes, pestaña Avanzado, bloque Dominios autorizados. Un origen es solo esquema, dominio y, si hace falta, puerto: https://app.tuempresa.com, sin ruta ni barra final.
- Si la lista tiene entradas y la petición llega desde un origen que no está en ella, la respuesta es
403conORIGIN_NOT_ALLOWED. - Si la lista está vacía, la aplicación no restringe el origen, pero un origen que la plataforma no tiene autorizado recibe un
403conCORS_ORIGIN_NOT_ALLOWED, sin llegar a la API. Para llamar desde una web, declara su origen.
Comodines
Puedes usar * para cubrir subdominios, con estas reglas:
| Patrón | ¿Se admite? |
|---|---|
https://app.tuempresa.com | Sí |
https://*.tuempresa.com | Sí: cualquier subdominio de tuempresa.com |
https://app-*.tuempresa.es | Sí |
http://localhost:3000 | Sí, para desarrollo |
*, https://*, https://*.com o https://*.tuempresa.* | No: abrirían la API a cualquier web |
https://app.tuempresa.com/ruta | No: un origen no lleva ruta |
El comodín no se admite en el dominio principal ni en la extensión (.com, .es), y el puerto, si lo pones, tiene que ser un número. Al guardar, los patrones que no cumplan las reglas se rechazan con el motivo.
CORS no protege tu clave
Permitir un origen deja que esa web llame a la API, pero la clave viaja en la petición y cualquiera puede verla en las herramientas del navegador. Por eso una clave que va en una página solo admite los permisos del navegador, los del apartado siguiente. Todo lo demás se llama desde tu servidor, con otra clave, o pasando por un intermediario tuyo que añada la clave.
Claves dentro de una página web
Una clave que va en el código de una página la puede copiar cualquiera que la abra y usarla después desde otro sitio. Por eso UTILIA OS trata como publicada la clave de una aplicación en dos casos:
- la aplicación tiene dominios autorizados;
- la clave vigente se ha usado desde un navegador desde su última renovación, aunque la aplicación ya no tenga dominios.
Quitar los dominios no vuelve secreta una clave que estuvo en una página: para eso hay que renovarla sin transición, para que la anterior deje de valer en el acto.
Una clave publicada solo admite los siete permisos que necesita una página, los del widget: support:tickets:read, support:tickets:write y support:tickets:ai (tickets y sus sugerencias de IA), fm:files:read y fm:files:write (archivos), crm:users:write (dar de alta a los usuarios con identify) y analytics:errors:write (enviar errores). El catálogo de permisos los marca con publishableAllowed: true (Catálogo de permisos de la API). Todo lo demás, como facturar, cobrar o leer el CRM, se hace desde tu servidor, con la clave de una aplicación sin dominios autorizados.
Qué va en la página y qué en tu servidor
Con dominios autorizados
Aplicación de la web
Sin dominios autorizados
Aplicación del servidor
Qué pasa con un permiso de fuera
- Desde un navegador, una ruta que exige un permiso de fuera de esos siete responde
403 SCOPE_NOT_ALLOWED_FROM_BROWSER, con los permisos que lo impiden ennotAllowedScopes. Vale para cualquier aplicación, tenga o no dominios autorizados y tenga o no ese permiso. Si la ruta se conforma con uno de varios permisos, basta con tener concedido uno de los siete. - Con una clave publicada, los permisos de fuera sobran aunque la aplicación los tenga concedidos de antes: retíralos ya o pasa esas operaciones a una aplicación de servidor (ver más abajo). Mientras los tenga, su diagnóstico avisa con
publishable-key-sensitive-scopes, y una llamada que los use puede responder el mismo403aunque no venga de un navegador. - Al configurar la aplicación, en el panel o por MCP, conceder a una clave publicada un permiso de fuera, o añadir dominios a una aplicación que tiene alguno, responde
400 SCOPE_NOT_ALLOWED_WITH_ALLOWED_ORIGINS. Quitar permisos o dominios se puede siempre. - En el diagnóstico, con una clave publicada,
/meyGET /external/v1/diagnosticssolo enseñan los permisos de dentro (los de fuera se cuentan encredential.scopesOutsideCeiling, sin nombrarlos) y las pruebas que leen como la aplicación o envían avisos se lanzan desde el panel. Lo explica Claves publicadas en una web.
Si necesitas las dos cosas
Si tu web llama a la API desde el navegador y tu servidor necesita permisos de fuera, separa la integración en dos aplicaciones:
- Deja la que tiene los dominios para la web, solo con los permisos del navegador.
- Crea otra sin dominios autorizados, concédele los permisos de servidor y usa su clave solo en tu servidor.
Si tu web ya no llama a la API desde el navegador, la otra salida es quitar los dominios autorizados, renovar la clave sin transición y usar la nueva solo desde tu servidor: la que estuvo en la página deja de valer en el acto.
Cada aplicación tiene sus propios usuarios
Los usuarios, las facturas y las suscripciones van por aplicación. Un usuario dado de alta con identify en la aplicación de la web no existe para la del servidor, y cada una solo ve las facturas y las suscripciones de sus usuarios. Las fichas de cliente del CRM que crea una tampoco las reutiliza la otra, así que una misma persona puede acabar con dos fichas: tu equipo puede vincular a mano el usuario con la ficha buena desde la pestaña Usuarios de la aplicación. Decide antes qué aplicación da de alta a cada usuario y lo factura.
Siguiente paso
Prepara tu integración para los fallos en Errores, identificador de petición y reintentos.
Ponlo en práctica
Lo encontrarás en Creadas por tu empresa. Para abrirlo, entra con tu cuenta de UTILIA OS.