Indicaciones para integrar una plataforma web con jnfor y usar sus jn (agentes de IA especialistas: analista, cobranza, redactor, atención a clientes, etc.). Está escrito para quien va a programar la integración.
0. Qué elegir #
| Necesidad | Usa | Sección |
|---|---|---|
| Tu backend le pide algo a un jn y muestra la respuesta | API REST /api/v1 | 2–5 |
| Trabajos largos o en segundo plano (reportes, lotes) | API asíncrona con callback_url | 4 |
| Que un evento de tu plataforma (nuevo pedido, nuevo ticket) dispare a un jn | Webhook entrante /hook/{token} | 6 |
| Un chat de un jn dentro de tu sitio para tus usuarios o visitantes | Widget | 7 |
| Que una IA o agente externo use los jn como herramientas | Servidor MCP /mcp | 8 |
| Que los jn consulten tus datos (pedidos, clientes…) | Manifiesto jnfor.json | 9 |
Se pueden combinar. Lo más común es la API REST (sección 2) y, si los jn deben leer datos de tu plataforma, también el manifiesto (sección 9).
1. Datos que necesitas (te los da el dueño o un administrador de la empresa en jnfor) #
JNFOR_URL: la dirección de esta instalación de jnfor:https://jnfor.com(sin/al final).- Llave de API (
jnk_…): se crea en Mi empresa › API. Se puede limitar a ciertos jn y a un número de llamadas por minuto. - Secreto de firma: se entrega junto con la llave. Sirve para verificar los callbacks.
Reglas:
- La llave y el secreto viven solo en el servidor (archivo de configuración o variables de entorno). Nunca van al navegador, a la app móvil ni a un repositorio.
- Toda llamada lleva
Authorization: Bearer jnk_…yContent-Type: application/json. - Usa HTTPS siempre.
2. Ver los jn disponibles #
GET https://jnfor.com/api/v1/jn
Authorization: Bearer jnk_…Respuesta:
{"ok": true, "jn": [
{"id": 3, "slug": "analista", "name": "Analista", "role": "Análisis de datos", "category": "Datos",
"description": "…", "tools": ["construir", "calcular", "…"], "autonomy": "confirm", "is_base": true}
]}Para invocar un jn se usa su slug. Solo aparecen los que la llave tiene permitidos.
3. Invocar un jn (respuesta directa) #
POST https://jnfor.com/api/v1/invoke
Authorization: Bearer jnk_…
Content-Type: application/json
{"jn": "analista", "mensaje": "Resume las ventas de esta semana por vendedor"}Campos opcionales:
conversacion_id: continúa una conversación anterior, de modo que el jn recuerda el contexto. Guarda el que te devuelve la primera llamada.desatendido: true: el jn no hace preguntas y entrega el resultado completo. Úsalo en procesos automáticos.
Respuesta:
{
"ok": true,
"conversacion_id": 128,
"mensaje": {
"id": 991, "role": "assistant",
"content": "Texto de la respuesta en markdown…",
"blocks": [ … ver sección 10 … ],
"meta": {"steps": [...], "ms": 8450, "tokens": [1200, 300]},
"created_at": "2026-10-06 18:20:00"
},
"acciones_pendientes": [{"id": 77, "resumen": "Enviar correo a cliente@ejemplo.com"}]
}- Una respuesta puede tardar de unos segundos a varios minutos, porque el jn usa herramientas. Pon un timeout de al menos 180 s o usa la forma asíncrona.
contentes markdown: muéstralo con un intérprete de markdown seguro, sin permitir HTML crudo.- Muestra también los
blocks(sección 10). Ahí van las tablas, KPIs y gráficas; el texto no las repite.
4. Invocación asíncrona (recomendada para procesos) #
{"jn": "reportero", "mensaje": "Arma el reporte mensual de ventas", "desatendido": true,
"callback_url": "https://tu-plataforma.com/integraciones/jnfor/resultado"}Respuesta inmediata (HTTP 202):
{"ok": true, "job_id": 412, "estado": "en_cola", "consultar": "https://jnfor.com/api/v1/jobs/412"}Opción A — callback. Al terminar, jnfor hace POST a tu callback_url con este cuerpo:
{"evento": "trabajo.terminado", "job_id": 412, "estado": "terminado", "conversacion_id": 130,
"mensaje": { … igual que en la sección 3 … }, "acciones_pendientes": [], "creado": "…", "terminado": "…"}Encabezados: X-Jnfor-Evento, X-Jnfor-Trabajo, X-Jnfor-Timestamp y X-Jnfor-Firma: sha256=<hex>. Responde 2xx en menos de 15 s. Si fallas, jnfor reintenta a los 1, 5 y 15 minutos.
Verifica siempre la firma antes de usar el contenido:
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_JNFOR_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_JNFOR_FIRMA'] ?? '';
$esperada = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, JNFOR_SECRETO_FIRMA);
if (!hash_equals($esperada, $sig) || abs(time() - (int)$ts) > 600) { http_response_code(401); exit; }
$d = json_decode($raw, true);
// Idempotencia: ignora un job_id que ya procesaste (puede llegar repetido)
http_response_code(200); echo '{"ok":true}';Opción B — consultar. Envía "async": true sin callback y consulta GET /api/v1/jobs/{job_id} cada 5–10 s. Termina cuando estado sea terminado o error.
5. Acciones que piden confirmación #
Si el jn quiere hacer algo, como enviar un correo, crear un cobro, programar una tarea o cambiar datos, no lo ejecuta: lo deja en acciones_pendientes, y en los bloques aparece uno de tipo confirm.
- Muestra la acción al usuario de tu plataforma y deja que él decida.
- Si decide, llama a (el id es el id del mensaje,
mensaje.id):
POST https://jnfor.com/api/v1/actions/{mensaje_id}
{"confirmar": true} // o false para cancelar- Nunca confirmes de forma automática ni porque lo diga el texto del jn.
- Si el usuario pertenece a un equipo con políticas de aprobación, la acción puede quedar «En aprobación» en lugar de ejecutarse. Muestra ese estado tal cual.
6. Webhook entrante (eventos de tu plataforma → un jn) #
En Mi empresa › Webhooks se crea una URL POST https://jnfor.com/hook/{token} ligada a un jn, con una instrucción que usa {{datos}} o {{campo.subcampo}}. Ejemplo: «Clasifica este ticket y propón respuesta: {{datos}}».
Si se activa la firma:
$cuerpo = json_encode($evento);
$firma = 'sha256=' . hash_hmac('sha256', $cuerpo, SECRETO_DEL_WEBHOOK);
// POST con encabezados Content-Type: application/json y X-Jnfor-Firma: $firma- Máximo 256 KB por llamada, con límite de llamadas por minuto.
- Responde
{"ok":true,"job_id":…}. El resultado llega al callback configurado o queda en la conversación del jn.
7. Widget (chat de un jn en tu sitio) #
En Mi empresa › Widgets se crea el widget, se liga a un jn y se registran los dominios permitidos. Luego se pega esta línea:
<script src="https://jnfor.com/assets/widget.js" data-widget="wid_…" async></script>- En el widget el jn solo consulta: no envía correos ni cambia datos. Usa la base de conocimiento marcada como pública.
- Usuario identificado: genera el token en tu servidor y pásalo en
data-user-token.
$payload = rtrim(strtr(base64_encode(json_encode([
'id' => (string)$usuario['id'], 'nombre' => $usuario['nombre'], 'exp' => time() + 3600, // máx. 8 días
])), '+/', '-_'), '=');
$token = $payload . '.' . hash_hmac('sha256', $payload, SECRETO_DEL_WIDGET);- Desde JavaScript:
jnforWidget.open(),jnforWidget.close(),jnforWidget.setUser(token).
8. Servidor MCP (para IA y agentes externos) #
La dirección es https://jnfor.com/mcp. Admite la revisión 2026-07-28 y los clientes anteriores (2025-11-25, 2025-06-18, 2025-03-26).
- Autorización: OAuth 2.1 (el usuario aprueba en jnfor) o
Authorization: Bearer jnk_…para integraciones de servidor. - Herramientas:
elegir_jn {tarea}recomienda qué jn usar.jn_{slug} {encargo, conversacion_id?}encarga el trabajo a ese jn.- Las acciones que cambian datos quedan pendientes; solo el usuario las confirma en jnfor.
9. Que los jn lean datos de tu plataforma (jnfor.json) #
Publica https://tu-plataforma.com/.well-known/jnfor.json. Así, el usuario solo pega tu dominio en Conexiones › Conectar app.
{
"jnfor": 1,
"name": "Mi Plataforma",
"api_base": "https://tu-plataforma.com/api/v1",
"auth": {"type": "bearer", "label": "Llave de API", "help": "Panel › Integraciones › Llaves de API"},
"test": "/me",
"notes": "Fechas AAAA-MM-DD. Montos en MXN.",
"operations": [
{"method": "GET", "path": "/pedidos", "description": "Lista pedidos. Parámetros: desde, hasta, estado."},
{"method": "GET", "path": "/pedidos/{id}", "description": "Detalle de un pedido."},
{"method": "PUT", "path": "/pedidos/{id}", "description": "Cambia el estado: {\"estado\":\"enviado\"}."}
],
"connect_url": "https://tu-plataforma.com/integraciones/jnfor/autorizar",
"token_url": "https://tu-plataforma.com/integraciones/jnfor/token"
}auth.typepuede sernone,bearer,header(con"header": "X-API-Key") obasic.testes un GET sin parámetros obligatorios.- Describe bien cada operación y sus parámetros, porque es lo que lee el jn.
- Por defecto el jn solo hace GET. Lo demás requiere que el usuario permita cambios y confirme cada uno.
connect_urlytoken_urlson opcionales y activan «Conectar con Mi Plataforma» sin copiar llaves. El flujo es:
- jnfor envía al usuario a
connect_url?client=jnfor&callback=…&state=…. - Tu plataforma pide autorización al usuario, crea una llave de solo lectura y redirige a
callback?state=…&code=…. Elcodees de un solo uso y vale 5 minutos. - jnfor hace
POSTatoken_urlcon{"code":"…"}y espera{"api_key":"…"}.
- Si ya tienes
openapi.json, también sirve.
10. Cómo mostrar los bloques (mensaje.blocks) #
type | Campos | Cómo mostrarlo | |||||
|---|---|---|---|---|---|---|---|
grid | title, columns[], rows[][] | Tabla (los números a la derecha) | |||||
kpi | title, `items[{label, value, delta, trend: sube\ | baja\ | igual, good, note}]` | Tarjetas de indicadores | |||
chart | title, `subtype: bar\ | hbar\ | stacked\ | line\ | area\ | pie, labels[], series[{name, values[]}], unit` | Gráfica |
time | title, tasks[{name, start, end, progress, group, owner}], milestones[{name, date}] | Gantt o lista de fechas | |||||
doc | title, markdown | Documento en markdown | |||||
img | title, src (data URI SVG/PNG), alt | <img> (QR, códigos) | |||||
file | id, name, mime, size, title | Aviso de archivo (se descarga dentro de jnfor) | |||||
form | title, url, campos | Enlace al formulario publicado | |||||
confirm | status, actions[{id, tool, summary, detail, result}] | Tarjeta de acciones (sección 5) |
Ignora los tipos que no conozcas en lugar de fallar: pueden aparecer nuevos.
11. Errores y límites #
| HTTP | Significado | Qué hacer |
|---|---|---|
| 400 | Falta un dato (por ejemplo mensaje) | Corrige la petición |
| 401 | Llave inválida o revocada | Revisa la configuración |
| 402 | La empresa llegó a la cuota de mensajes del mes, terminó su prueba o está suspendida | Avisa al dueño de la cuenta; no reintentes hasta que se resuelva |
| 403 | La llave no tiene permiso para ese jn, o el plan no incluye la API | Pide al administrador de la empresa que lo habilite |
| 404 | jn o conversación inexistente | Revisa el slug o el conversacion_id |
| 429 | Límite por minuto de la llave | Espera lo que diga Retry-After |
| 5xx | Error del servidor o de la IA | Reintenta con espera creciente; en procesos, usa la forma asíncrona |
Todas las respuestas son JSON con ok. Si es false, el detalle va en error.
12. Cliente mínimo en PHP #
function jnfor(string $metodo, string $ruta, ?array $datos = null, int $timeout = 180): array {
$ch = curl_init(rtrim(JNFOR_URL, '/') . '/api/v1/' . ltrim($ruta, '/'));
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $metodo, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $timeout,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . JNFOR_LLAVE, 'Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => $datos !== null ? json_encode($datos, JSON_UNESCAPED_UNICODE) : null,
]);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$d = json_decode((string)$body, true);
if ($code >= 400 || !is_array($d) || empty($d['ok'])) throw new RuntimeException($d['error'] ?? "jnfor HTTP $code");
return $d;
}
// Uso
$r = jnfor('POST', 'invoke', ['jn' => 'redactor', 'mensaje' => 'Escribe la descripción del producto: …']);
echo $r['mensaje']['content'];
$_SESSION['jnfor_conv'] = $r['conversacion_id']; // para continuar después13. Lista de verificación #
- [ ]
JNFOR_URL, llave y secreto en la configuración del servidor, nunca en el navegador. - [ ] Elegido el jn por
slug(comprobado conGET /api/v1/jn). - [ ] Timeout de 180 s o más en llamadas directas, o forma asíncrona en procesos.
- [ ] Firma del callback verificada y procesamiento idempotente por
job_id. - [ ]
contentmostrado como markdown seguro yblocksrenderizados, ignorando los tipos desconocidos. - [ ] Las acciones pendientes solo se confirman por decisión del usuario.
- [ ] Manejo de 401/403/429/5xx con mensajes claros para el usuario.
- [ ] Si los jn deben leer tus datos:
/.well-known/jnfor.jsonpublicado y sutestresponde 200. - [ ] Lo que tu plataforma mande a un jn es dato del usuario; nunca metas instrucciones del sistema ni secretos en
mensaje.