jnfor Desarrolladores
En esta guía

Conectar una plataforma con los jn de jnfor

Ver en Markdown

¿Programas con una IA? Pásale este enlace: https://jnfor.com/desarrolladores/plataforma.md · Índice para asistentes: https://jnfor.com/llms.txt

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 #

NecesidadUsaSección
Tu backend le pide algo a un jn y muestra la respuestaAPI REST /api/v12–5
Trabajos largos o en segundo plano (reportes, lotes)API asíncrona con callback_url4
Que un evento de tu plataforma (nuevo pedido, nuevo ticket) dispare a un jnWebhook entrante /hook/{token}6
Un chat de un jn dentro de tu sitio para tus usuarios o visitantesWidget7
Que una IA o agente externo use los jn como herramientasServidor MCP /mcp8
Que los jn consulten tus datos (pedidos, clientes…)Manifiesto jnfor.json9

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_… y Content-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.
  • content es 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.type puede ser none, bearer, header (con "header": "X-API-Key") o basic. test es 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_url y token_url son opcionales y activan «Conectar con Mi Plataforma» sin copiar llaves. El flujo es:
  1. jnfor envía al usuario a connect_url?client=jnfor&callback=…&state=….
  2. Tu plataforma pide autorización al usuario, crea una llave de solo lectura y redirige a callback?state=…&code=…. El code es de un solo uso y vale 5 minutos.
  3. jnfor hace POST a token_url con {"code":"…"} y espera {"api_key":"…"}.
  • Si ya tienes openapi.json, también sirve.

10. Cómo mostrar los bloques (mensaje.blocks) #

typeCamposCómo mostrarlo
gridtitle, columns[], rows[][]Tabla (los números a la derecha)
kpititle, `items[{label, value, delta, trend: sube\baja\igual, good, note}]`Tarjetas de indicadores
charttitle, `subtype: bar\hbar\stacked\line\area\pie, labels[], series[{name, values[]}], unit`Gráfica
timetitle, tasks[{name, start, end, progress, group, owner}], milestones[{name, date}]Gantt o lista de fechas
doctitle, markdownDocumento en markdown
imgtitle, src (data URI SVG/PNG), alt<img> (QR, códigos)
fileid, name, mime, size, titleAviso de archivo (se descarga dentro de jnfor)
formtitle, url, camposEnlace al formulario publicado
confirmstatus, 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 #

HTTPSignificadoQué hacer
400Falta un dato (por ejemplo mensaje)Corrige la petición
401Llave inválida o revocadaRevisa la configuración
402La empresa llegó a la cuota de mensajes del mes, terminó su prueba o está suspendidaAvisa al dueño de la cuenta; no reintentes hasta que se resuelva
403La llave no tiene permiso para ese jn, o el plan no incluye la APIPide al administrador de la empresa que lo habilite
404jn o conversación inexistenteRevisa el slug o el conversacion_id
429Límite por minuto de la llaveEspera lo que diga Retry-After
5xxError del servidor o de la IAReintenta 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és

13. 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 con GET /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.
  • [ ] content mostrado como markdown seguro y blocks renderizados, 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.json publicado y su test responde 200.
  • [ ] Lo que tu plataforma mande a un jn es dato del usuario; nunca metas instrucciones del sistema ni secretos en mensaje.