Tema
API HTTP
La API para llamar a ur desde tu backend: ejecutar inferencias, buscar en tus RAGs y consultar el estado de indexación de tus documentos.
| Recurso | Para qué |
|---|---|
| Inferencias | Ejecutar un prompt, con o sin tus documentos como contexto |
| RAGs | Buscar fragmentos relevantes, listar ficheros, acotar el alcance |
| Webhooks | Que otros sistemas avisen a ur de que hay cambios |
| Agentes | Listar los agentes del token, con sus tools |
Base URL y autenticación
Todo cuelga de /api en el origen de tu instancia. Salvo los webhooks —que llevan el secreto de su propia fuente de ficheros—, cada llamada va con un access token en la cabecera Authorization:
bash
curl https://ur.tuempresa.com/api/inferences \
-H "Authorization: Bearer ur-Ah7kQ2mZ..."No hay CORS
Esta API no se llama desde un navegador
/api/* no emite cabeceras CORS, a propósito: el token Bearer no debe llegar nunca a un navegador, porque cualquiera que abra las devtools se lo lleva.
Llámala solo desde tu servidor. Si lo que necesitas es hablar desde el navegador del usuario final, la vía es el canal de agentes, que está diseñado para ser público.
Permisos
El token declara sobre qué recursos puede actuar, en tres grupos: agents, inferences y rags. Ver Credenciales.
Los webhooks quedan fuera de estos grupos: no se autentican con el access token, sino con el secreto de la file source. Ver Webhooks.
Hay dos comportamientos distintos según cómo llegues al recurso:
- Listados — filtran en silencio.
GET /api/agentsoGET /api/inferencesdevuelven solo los permitidos. - Acceso directo —
403 {"error":"Forbidden"}si el recurso existe pero no está permitido.
Qué tiene disponible tu token
Un listado por recurso, de solo lectura. Cada uno devuelve únicamente lo que permite el token, así que con los tres sabes todo lo que puede usar una integración sin mirar el panel:
| Listado | Qué trae de cada elemento |
|---|---|
GET /api/agents | Nombre, identifier y los nombres de sus tools |
GET /api/inferences | Nombre, identifier, URL, los RAGs y fuentes que consulta, los adjuntos que admite su modelo y los nombres de sus tools |
GET /api/rags | Nombre, identifier, estado de indexación, contadores y su fuente de ficheros |
Las tools son las que el agente o la inferencia pueden llamar de verdad: si un administrador desactiva la plantilla de una tool en el catálogo, deja de salir aunque siga asignada. Sin tools, []. Cada una sale por el nombre de su instancia; una de búsqueda en RAG (rag_retrieve) le ofrece además al modelo <nombre>_list_paths, _list_tags, _process_file y _get_link.
bash
curl https://ur.tuempresa.com/api/agents -H "Authorization: Bearer $UR_TOKEN"
curl https://ur.tuempresa.com/api/inferences -H "Authorization: Bearer $UR_TOKEN"
curl https://ur.tuempresa.com/api/rags -H "Authorization: Bearer $UR_TOKEN"Errores
| Código | Cuerpo | Cuándo |
|---|---|---|
400 | {"error":"..."} | Cuerpo inválido. El mensaje describe qué falla, pero no siempre nombra el campo: si tienes dudas, contrasta con el esquema de /api/docs |
401 | {"error":"Unauthorized"} | Falta el token o no existe. Con {"error":"Token expired"}, el token existe pero ya caducó: hay que renovarlo |
403 | {"error":"Forbidden"} | El token no cubre ese recurso. Con "code":"manage_required" (los scopes de un RAG), el dueño del token lo ve pero no lo administra |
404 | {"error":"Not found"} | No existe ese recurso — o el identifier del padre, o un sub-recurso (un fichero) que sí pediste por id |
409 | {"error":"Scope already exists"} | Ese scope ya está dado de alta en el RAG |
413 | {"error":"..."} | El cuerpo de la petición pasa del máximo: al ejecutar una inferencia con adjuntos |
422 | {"error":"RAG is not in scoped mode"} | La operación no aplica al estado actual del recurso — o el modelo de la inferencia no admite el tipo de adjunto enviado |
429 | {"error":"Too many requests"} | Superado el límite por minuto del usuario dueño del token. Retry-After dice cuántos segundos esperar. Con {"error":"Too many failed authentication attempts"}, demasiados intentos con un token que no vale desde esa IP |
502 | {"error":"...","errorId":"..."} | Falló el proveedor del modelo. El mensaje es genérico: el error completo queda en el log del servidor bajo ese errorId |
503 | {"error":"Rate limiting unavailable"} | El backend no puede hablar con Redis |
Límites de peticiones
/api/inferences, /api/agents y /api/rags cuentan por usuario, en ventanas de 60 segundos y en un contador compartido por las tres:
- Todos los tokens de un mismo usuario comparten cupo, y lo comparten también con sus conexiones MCP y con el probador de inferencias del panel. Crear más tokens no da más cupo.
- Por defecto son 60 peticiones por minuto. Un administrador cambia el número general en Ajustes → API, y puede dar uno más alto a un usuario o a un grupo desde su ficha. A cada usuario se le aplica el mayor de los que le tocan; cada uno ve el suyo en Mi cuenta.
- Cada respuesta lleva
X-RateLimit-Limit(el límite) yX-RateLimit-Remaining(lo que queda en la ventana). Al pasarse,429conRetry-After: los segundos hasta que vuelve a haber cupo.
Las cabeceras
bash
curl -i https://ur.tuempresa.com/api/rags \
-H "Authorization: Bearer ur-Ah7kQ2mZ..."http
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57Y al pasarte, hasta que acaba la ventana:
http
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{"error":"Too many requests"}La ventana es fija: empieza con la primera petición y dura 60 segundos, y reintentar antes de tiempo no la alarga ni la acorta. X-RateLimit-Remaining sirve para ir más despacio antes de llegar al 429.
Qué hacer con un 429
Espera los segundos de Retry-After y repite la petición una vez. No reintentes en bucle: hasta que pase la ventana, cada intento vuelve a dar 429.
js
async function callUr(url, init) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const seconds = Number(res.headers.get('Retry-After')) || 60;
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
return fetch(url, init);
}Si una integración vive pegada al límite, pide a un administrador que suba el de su usuario, o mejor, crea un usuario solo para ella: así no comparte cupo con nadie.
Intentos fallidos, por IP
Por IP solo cuentan los intentos fallidos: tras 30 peticiones en un minuto sin token, con uno que no existe o con uno caducado, esa IP recibe 429 {"error":"Too many failed authentication attempts"} con Retry-After (sin las X-RateLimit-*, que son del cupo de un usuario) hasta que pasa la ventana, aunque luego llegue con uno bueno.
Otras superficies
El resto cuenta por IP, sin cabeceras X-RateLimit-*. Su 429 también lleva Retry-After, y en /embed se puede leer desde el navegador (va en Access-Control-Expose-Headers):
| Superficie | Límite |
|---|---|
/api/webhooks/* | 20 por minuto |
/embed/* (widget y SDK) | 180 por minuto para cada agente (se cambia en su sección Seguridad), con un tope de 600 sumando todos los agentes (Ajustes → API) |
El 503 es a propósito
Si Redis no está disponible, las rutas con límite devuelven 503 en vez de dejar pasar el tráfico. Falla cerrado. Un 503 aquí apunta a la infraestructura, no a tu petición.
Campos solo de administrador
La API acepta también la cookie de sesión del panel, y esto tiene una consecuencia que muerde al pasar a producción:
Probar en el navegador enseña campos que luego no están
Con sesión del panel (cualquier usuario con acceso al recurso, no solo un administrador) la respuesta trae campos de diagnóstico que con un token Bearer no aparecen:
timings— desglose de tiempos (contexto, reranking, LLM)usage— tokens consumidos y costetoolCalls[].debug— detalle interno de cada toolmessagedentro dedegraded- la cabecera
X-Inference-Timings
Si construyes tu integración probando desde el navegador con sesión abierta y luego cambias a un token, esos campos desaparecen sin previo aviso. No los des por hechos.
Referencia navegable
Tu propia instancia publica la especificación OpenAPI, generada del código y siempre al día:
| URL | Qué es |
|---|---|
https://<tu-instancia>/api/docs | Referencia navegable, campo a campo |
https://<tu-instancia>/api/openapi.json | La especificación en crudo, para generar clientes |
Las dos son públicas: no hacen falta credenciales para leerlas (sí para llamar a los endpoints).