Qué es NSNatAPI
nsnatapi (NSNatAPI) es la pasarela de Nacarsoft que expone subprogramas Natural como endpoints REST (GET/POST/PUT/DELETE) para clientes máquina (Java, scripts, cron, microservicios), sin EntireX ni broker RPC. Cada endpoint se define en la consola, se protege con tokens Bearer y se ejecuta en uno de dos modos:
- batch (por defecto): un proceso Natural por llamada (~30–90 ms de arranque).
- session (opt-in): un pool de sesiones Natural persistentes reutilizadas (~1–5 ms por llamada) para tráfico de alta frecuencia.
Los nombres de programas, hosts, tokens y cuentas de los ejemplos son ficticios.

Presentación comercial: PDF (ES) · PPTX (ES) · PDF (EN) · PPTX (EN)
Requisitos
- Linux (RHEL 8/9).
- Software AG Natural + cliente Adabas (
sagenv) con buffer pool arrancado. - FUSER con los subprogramas de negocio (lectura y escritura para desplegar).
- Proxy inverso con HTTPS en producción.
Puesta en marcha
nsnatapi init ./data # config.yaml (0600), db, usuario admin
nsnatapi start ./data # daemon, puerto 11032 por defecto
nsnatapi status ./data
Consola web en http://host:11032/ (login admin; cambia la contraseña tras el primer acceso). El comando nsnatapi passwd permite recuperar el acceso.
Orden recomendado: 1) escribe el subprograma Natural, 2) crea el endpoint, 3) haz Deploy, 4) crea el token con --endpoint <nombre> (ese nombre ya tiene que existir).
El módulo Natural, paso a paso
El único código que escribes tú es el subprograma .NSN, con el convenio de la casa: exactamente tres parámetros.
El contrato mínimo
0010* MIECHO - eco mínimo (plantilla funcional)
0020DEFINE DATA PARAMETER
00301 #PARM-IN (A) DYNAMIC /* entrada: una línea de params (<=240 chars) */
00401 #PARM-OUT (A) DYNAMIC /* salida: respuesta (ver modos de respuesta) */
00501 #RESP (I4) /* 0 = ok, >0 = error de negocio */
0060END-DEFINE
0070RESET #PARM-OUT #RESP
0080COMPRESS 'ECHO:' #PARM-IN INTO #PARM-OUT
0090END
Qué recibe y qué devuelve cada parámetro:
| Parámetro | Dirección | Contenido |
|---|---|---|
#PARM-IN (A) DYNAMIC | Entrada | Una sola línea con los parámetros, unidos con el separador del endpoint (máx. 240 caracteres) |
#PARM-OUT (A) DYNAMIC | Salida | La respuesta del negocio (texto, campos separados o JSON, según response_mode) |
#RESP (I4) | Salida | 0 = ok; >0 = error de negocio (el cliente lo recibe en rc) |
Reglas obligatorias
- Nombre de programa
A-Z0-9-_, máximo 8 caracteres, en mayúsculas. - Terminar siempre con
END(nuncaSTOPniLOGON: matarían la sesión batch). - Stateless por llamada: nada de globales/AIVs entre llamadas.
- Cierra siempre la unidad de trabajo:
END TRANSACTIONsi fue bien,BACKOUTsi falla una actualización. - Despliega el
.NSNpor tu proceso habitual (nsdeploy/SYSOBJH); nsnatapi no recompila tus programas, solo genera y compila sus stubs/drivers.
Ejemplo 1: consulta con RC de negocio
Un GET /v1/saldo/:cuenta donde la cuenta viaja en la línea de entrada y el programa decide el resultado con #RESP:
0010* CPOLIZA - consulta de póliza (plantilla)
0020DEFINE DATA PARAMETER
00301 #PARM-IN (A) DYNAMIC
00401 #PARM-OUT (A) DYNAMIC
00501 #RESP (I4)
0060LOCAL
00701 #CUENTA (A8)
0080END-DEFINE
0090RESET #PARM-OUT #RESP #CUENTA
0100MOVE #PARM-IN TO #CUENTA
0110* Aquí iría tu READ a Adabas con tu vista/DDM; si no encuentra:
0120IF #CUENTA = '999999'
0130 MOVE 'CUENTA NO ENCONTRADA' TO #PARM-OUT
0140 MOVE 4 TO #RESP
0150ELSE
0160 COMPRESS 'SALDO 1234.56' INTO #PARM-OUT
0170 MOVE 0 TO #RESP
0180END-IF
0190END TRANSACTION
0200END
Llamada y respuesta (response_mode=text):
curl -H "Authorization: Bearer nstm_..." \
http://host:11032/api/ext/v1/saldo/001234
{ "ok": true, "rc": 0, "output": "SALDO 1234.56", "duration_ms": 22 }
Con una cuenta inexistente, HTTP sigue siendo 200 porque el programa se ejecutó; el error de negocio viaja en rc:
{ "ok": true, "rc": 4, "output": "CUENTA NO ENCONTRADA", "duration_ms": 19 }
Ejemplo 2: POST con dos campos y separador
Un POST /v1/pagos con JSON body. Los valores se unen en una línea (importe|concepto) que el programa separa:
0010* CPAGOS - alta de pago (2 campos separados por |)
0020DEFINE DATA PARAMETER
00301 #PARM-IN (A) DYNAMIC
00401 #PARM-OUT (A) DYNAMIC
00501 #RESP (I4)
0060LOCAL
00701 #IMPORTE (A15)
00801 #CONCEPTO (A60)
0090END-DEFINE
0100RESET #PARM-OUT #RESP #IMPORTE #CONCEPTO
0110SEPARATE #PARM-IN INTO #IMPORTE #CONCEPTO WITH DELIMITER '|'
0120IF #IMPORTE = ' ' OR #IMPORTE = ''
0130 MOVE 'FALTA IMPORTE' TO #PARM-OUT
0140 MOVE 1 TO #RESP
0150ELSE
0160 COMPRESS 'PAGO REGISTRADO|' #IMPORTE '|' #CONCEPTO INTO #PARM-OUT
0170 MOVE 0 TO #RESP
0180END-IF
0190END TRANSACTION
0200END
curl -H "Authorization: Bearer nstm_..." -H 'Content-Type: application/json' \
-d '{"importe":"1500","concepto":"prima"}' \
http://host:11032/api/ext/v1/pagos
{ "ok": true, "rc": 0, "output": "PAGO REGISTRADO|1500|prima", "duration_ms": 31 }
Ejemplo 3: respuesta JSON real
Si el endpoint usa response_mode=json, el programa debe emitir JSON válido. Ojo: en este entorno Natural un literal " del fuente se compila como '; construye las comillas con H'22' (o usa GENERATE JSON):
0010* JECHO - devuelve un objeto JSON (response_mode=json)
0020DEFINE DATA
0030PARAMETER
00401 #PARM-IN (A) DYNAMIC
00501 #PARM-OUT (A) DYNAMIC
00601 #RESP (I4)
0070LOCAL
00801 #Q (A1)
0090END-DEFINE
0100RESET #PARM-OUT #RESP
0110MOVE H'22' TO #Q
0120COMPRESS '{' #Q 'msg' #Q ':' #Q #PARM-IN #Q '}' INTO #PARM-OUT LEAVING NO SPACE
0130END
Respuesta: output trae siempre el texto crudo y data el objeto ya parseado:
{
"ok": true,
"rc": 0,
"output": "{\"msg\":\"hola\"}",
"duration_ms": 24,
"data": { "msg": "hola" }
}
Si el programa emite comillas simples en lugar de dobles, la llamada falla con 502 {"ok":false,"error":"invalid json from program"}.
Crear el endpoint, paso a paso
1. Campos del endpoint
| Campo | Obligatorio | Descripción |
|---|---|---|
name | Sí | Nombre legible (máx. 64, sin caracteres de control) |
method | Sí | GET, POST, PUT o DELETE |
path_template | Sí | Empieza por /; capturas con :nombre (únicas, letra inicial, máx. 32). Ej.: /v1/saldo/:cuenta |
library | Sí* | Librería Natural (mayúsculas). Si se omite, se usa la librería por defecto del entorno |
program | Sí | Subprograma existente (A-Z0-9-_, máx. 8) |
environment | No | Entorno Natural (DEV/TST/PRD); vacío = entorno por defecto |
fields_json | No | Campos extra por orden: query en GET/DELETE, JSON body en POST/PUT. Formato: [{"name":"importe","required":true}] |
separator | No | Un único carácter ASCII imprimible (defecto |) |
exec_mode | No | batch (defecto) o session |
response_mode | No | text (defecto), fields o json |
response_fields_json | Solo fields | Nombres y tipos de cada posición: [{"name":"saldo","type":"number"}]; tipos: text, number, int, bool, json |
timeout_sec | No | Timeout de la llamada (0 = defecto del servidor; tope configurable) |
Regla de oro del mapeo: capturas del path (en orden) + campos (en el orden de fields_json), unidos con el separador en una línea. Por eso, si el parámetro ya viaja en el path (:cuenta), fields_json debe ser []: no lo repitas ahí, o te pedirá además el mismo valor por query.
2. Crear por consola
Endpoints → Crear: método, plantilla, librería, entorno, programa, campos, separador, timeout y modo de ejecución. Pulsa Deploy y luego Try it para probarlo.
Ejemplo rápido: GET, plantilla /v1/saldo/:cuenta, librería NSNAT, entorno DEV, programa CPOLIZA, fields_json [], modo batch.
3. Crear por API (admin)
# Login de consola (guarda la cookie 'session')
curl -s -c cookies.txt -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"********"}' \
http://host:11032/api/auth/login
# Endpoint GET con captura de path (fields_json vacío: el valor ya viene en :cuenta)
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-d '{"name":"saldo","method":"GET","path_template":"/v1/saldo/:cuenta",
"library":"NSNAT","program":"CPOLIZA","environment":"DEV",
"exec_mode":"batch","response_mode":"text","fields_json":"[]",
"separator":"|","timeout_sec":30}' \
http://host:11032/api/endpoints
# Endpoint POST con dos campos de body
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-d '{"name":"pagos","method":"POST","path_template":"/v1/pagos",
"library":"NSNAT","program":"CPAGOS","environment":"DEV",
"exec_mode":"batch","response_mode":"text",
"fields_json":"[{\"name\":\"importe\",\"required\":true},{\"name\":\"concepto\",\"required\":false}]",
"separator":"|","timeout_sec":30}' \
http://host:11032/api/endpoints
# Desplegar (el id sale en la respuesta de creación)
curl -s -b cookies.txt -X POST http://host:11032/api/endpoints/1/deploy
4. Deploy: qué compila nsnatapi
- batch: Deploy genera y compila un stub
NX000042(reporting-mode) en la librería del entorno. El stub haceINPUT,CALLNAT '<tu-programa>'y escribe la respuesta con los centinelasNSNAT:OUT:<len>/NSNAT:C:<len>:<chunk>/NSNAT:END RC=<n>. - session: Deploy compila el driver compartido
NSRPDRV(structured mode) una vez por librería; el pool lo reutiliza y despacha a tus subprogramas conCALLNATdinámico.
Tus .NSN no cambian entre modos. No edites los NXnnnnnn a mano: se regeneran en cada Deploy.
5. Token con alcance
nsnatapi token ./data --name java-prd --endpoint saldo --days 365 \
--cidr 10.0.0.0/8,192.168.1.10
# → nstm_xxxxxxxx… (guárdalo; no se vuelve a mostrar)
nsnatapi token ./data --list
Sin --endpoint, el token vale para todos los endpoints. Puedes limitar por IP/CIDR, caducidad y revocación, además del rate limiting por token.
El secreto solo se muestra al crearlo (con botón Copiar en la consola); después es irrecuperable —en BD solo hay su hash— así que si se pierde se rota desde la consola (botón Rotar) o con POST /api/tokens/:id/rotate: mismo id y metadatos, secreto nuevo, el anterior deja de funcionar.
Entregar un token a una aplicación: 1) endpoint desplegado y probado; 2) un token por app, con scope mínimo, caducidad y CIDRs si aplica; 3) informa el email de contacto y la app le envía un aviso con un botón Get token: enlace de un solo uso (48 h) donde ve el secreto una vez (reenvíable con ✉️); 4) copia el secreto una vez, directo al almacén de secretos de la app (nunca notepad ni correo); 5) verifica con una llamada de prueba en Llamadas; 6) ante pérdida o filtración, rota; si la app se da de baja, revoca.
Cómo se construye la línea de parámetros
| Caso | De dónde sale cada valor | Ejemplo |
|---|---|---|
GET /v1/saldo/001234, fields_json: [] | Captura :cuenta | 001234 |
GET /v1/saldo/001234?formato=corto, fields: [formato] | Captura + query | 001234|corto |
POST /v1/pagos, body {"importe":"1500","concepto":"prima"}, fields: [importe, concepto] | Body en orden | 1500|prima |
PUT /v1/saldo/001234, body {"importe":"1500"} | Captura + body | 001234|1500 |
El separador por defecto es | y debe ser un único carácter ASCII imprimible. La línea total no puede superar 240 caracteres ni contener saltos de línea.
Respuestas JSON
Todas las llamadas máquina usan /api/ext/... con Authorization: Bearer nstm_....
Modo text (defecto)
Solo output con el texto crudo del programa:
{ "ok": true, "rc": 0, "output": "SALDO 1234.56", "duration_ms": 22 }
Modo fields: objeto tipado además del crudo
El programa devuelve posiciones separadas (123456|Juan Pérez|1520.75|ACTIVA) y el endpoint declara response_mode: fields con:
[{"name":"cuenta","type":"text"},{"name":"titular","type":"text"},
{"name":"saldo","type":"number"},{"name":"estado","type":"text"}]
Respuesta:
{
"ok": true,
"rc": 0,
"output": "123456|Juan Pérez|1520.75|ACTIVA",
"duration_ms": 22,
"data": {
"cuenta": "123456",
"titular": "Juan Pérez",
"saldo": 1520.75,
"estado": "ACTIVA"
}
}
Modo json: objeto validado
El programa emite JSON (ver ejemplo JECHO más arriba) y la respuesta incluye el objeto en data. Si no es JSON válido: 502 {"ok":false,"error":"invalid json from program"}.
Errores: forma común
Todos los errores macchina usan la misma forma:
{ "ok": false, "error": "<mensaje>" }
| HTTP | Ejemplo de error | Significado |
|---|---|---|
| 200 | n/a (rc: 4, output: "CUENTA NO ENCONTRADA") | Se ejecutó; el negocio dice que no |
| 400 | missing param cuenta | Falta un campo requerido (query o body) |
| 400 | json body required / invalid json body | POST/PUT sin body JSON válido |
| 400 | params too long (max 240 chars) | Línea de parámetros demasiado larga |
| 401 | invalid token | Token ausente, revocado o caducado |
| 404 | no such endpoint | No hay endpoint activo con ese método + path |
| 429 | rate limit exceeded | Límite por token/IP alcanzado |
| 502 | natural execution failed | Falló Natural (detalle en el log de llamadas) |
| 502 | invalid json from program | response_mode=json pero la salida no es JSON |
| 503 | endpoint not deployed | Falta el Deploy del stub/driver |
| 503 | server too busy, try again later | Concurrencia o pool de sesiones llenos |
| 504 | natural call timed out | El programa superó timeout_sec |
Llamadas desde clientes
# GET con captura de path
curl -H "Authorization: Bearer nstm_…" \
http://host:11032/api/ext/v1/saldo/001234
# POST con JSON body (campos definidos en el endpoint)
curl -H "Authorization: Bearer nstm_…" -H 'Content-Type: application/json' \
-d '{"importe":"1500","concepto":"prima"}' \
http://host:11032/api/ext/v1/pagos
Cliente Java mínimo (Java 11+, sin dependencias; ejemplo completo en examples/java/NatApiClient.java del proyecto):
var req = HttpRequest.newBuilder(URI.create(base + "/api/ext/v1/saldo/001234"))
.header("Authorization", "Bearer " + token).GET().build();
var body = client.send(req, BodyHandlers.ofString()).body();
// {"ok":true,"rc":0,"output":"SALDO 1234.56","duration_ms":22}
// HTTP 200 = se ejecutó (aunque rc≠0); comprueba siempre rc.
Referencia de la API
Fuentes de verdad: api/openapi.yaml (inglés) y api/openapi.es.yaml
(español), OpenAPI 3.1, en el proyecto. La app las sirve en /api/docs
(Swagger UI interactivo, con botón Try it out),
/api/openapi.json y /api/openapi.yaml, con ?lang=es|en o
negociación por Accept-Language. Cubre la API máquina (/api/ext/*,
Bearer) y la API consola (/api/*, cookie de sesión), con el permiso RBAC
(x-permission) de cada operación y ejemplos curl.
Además, cada endpoint activo aparece como operación propia (su plantilla,
método y parámetros reales): el desarrollador ve qué hay disponible, cómo
llamarlo, qué devuelve, y lo prueba con su token (rellénalo arriba o pasa
?token=nstm_…).
Visibilidad (en caliente, config.yaml o Configuration → API docs):
apiDocs:
access: private # public (sin login) | private (sesión de consola) | off (404)
Guía completa de uso (auth, convenciones, paginación, errores, versionado):
nsnatapi/doc/api.md.
Consola: panel, correo y sesiones
El Dashboard muestra telemetría viva del servidor (CPU, memoria, carga, disco, RSS/heap/goroutines del proceso, slots Natural en uso y uptime), muestreada de /proc sin dependencias externas y refrescada cada 5 s desde GET /api/system/stats (solo admin).
La sección correo (SMTP o sendmail, con botón Probar email) habilita la recuperación de contraseña: ¿Olvidaste tu contraseña? envía un enlace de un solo uso (60 min); las direcciones desconocidas reciben siempre la misma respuesta genérica (sin enumeración de usuarios). La clave SMTP puede ir por variable (NSNATAPI_SMTP_PASSWORD).
Las sesiones de consola rotan el refresh token en cada uso con detección de reuso (un token repetido revoca todas las sesiones); users.token_version invalida todo al cambiar/resetear la contraseña o forzar el logout. Se suma auth.tokenPepper (HMAC a los tokens API), cookies con Secure automático, server.trustedProxies (loopback por defecto) para la integridad de la IP en auditoría y anti-fuerza-bruta, y errores genéricos al cliente.
Endpoints y modos
- batch: al pulsar Deploy se genera y compila un stub
NX000042(reporting-mode) en la librería del entorno. - session: Deploy compila el driver compartido
NSRPDRV(structured mode) una vez por librería; el pool lo reutiliza.
Mapeo de parámetros: capturas del path (en orden) + campos (query en GET/DELETE, JSON body en POST/PUT), unidos con el separador en una línea.
Sesiones persistentes
natural:
numSessions: 4 # tamaño del pool por (entorno, librería)
maxSessions: 16 # tope (el límite real es NATBP MAXUSERS)
sessionIdleSec: 600 # cierra sesiones ociosas > 10 min (0 = off)
environments:
DEV: { bp: NATBP, numSessions: 6 }
TST: { bp: NATTST, numSessions: 2, sessionIdleSec: 300 }
Cada sesión es un proceso Natural (consume un usuario del buffer pool; deja margen en MAXUSERS). Un error fatal recicla la sesión automáticamente.
Evitar NAT3009: una sesión persistente que abrió Adabas puede caducar por no-actividad (TNAA/TNAE/TNAX) y la siguiente llamada falla con NAT3009 (subcode 3). Con sessionIdleSec se cierra la sesión antes de que caduque (debe ser menor que el TNAE efectivo, con margen): la siguiente petición crea una sesión nueva con Adabas fresco y nunca aparece el 3009. El cierre se registra en el log del servidor (recycled by idle timeout) y en la auditoría (SESSION_IDLE_TIMEOUT), y se cuenta por pool (recycled_idle).
Base vs entornos: environments es la fuente de verdad y cada entorno es autónomo. El bloque natural de nivel superior es solo la base por defecto (pre-rellena entornos nuevos y sirve de respaldo si no hay ninguno); no es un entorno. defaultEnvironment elige el entorno usado cuando un endpoint no especifica ninguno.
Se configura en la consola, Configuration → Natural environments: el campo Session pool size (numSessions) fija el valor por defecto y el modal de cada entorno el suyo (el valor del entorno manda sobre el global; es el número que arranca Precalentar). El cambio se aplica en caliente: el pool se redimensiona (al bajar, se reciclan las sesiones inactivas sobrantes). Los pools están separados por entorno (p. ej. 3 sesiones en DEV y 8 en TST, con valores propios); un endpoint sin entorno explícito usa el entorno por defecto. La página Sessions etiqueta cada pool con su entorno y el registro de llamadas guarda el entorno (columna y filtro Environment en Calls). El panel Natural RPC session pools (Configuration) lista todos los entornos y permite Nuevo pool (por librería), Editar límites, Precalentar, Reiniciar y Borrar. Por API: GET /api/natural/sessions, POST /api/natural/sessions/warm|restart|delete; las métricas también salen en GET /api/system/stats → natural.sessions.
La página Sessions (admin, auto-refresh cada 5 s) muestra en vivo cada sesión Natural: PID, estado (idle/busy/dead), uptime, última llamada, llamadas servidas (proxy de transacciones), errores, último programa, latencias (última/media), CPU (%) instantánea y media, y memoria (RSS), con acciones Precalentar / Reiniciar / Matar sesión.
Ejemplos de uso
1. Alta rápida de un endpoint (consola)
Endpoints → Crear: método GET, plantilla /v1/saldo/:cuenta, librería NSNAT, programa CPOLIZA, entorno DEV, modo batch, fields_json []. Pulsa Deploy para compilar el stub y Try it para probarlo.
2. La misma alta por API (admin)
Ver la sección «Crear por API» más arriba: el fields_json queda vacío porque el valor ya viaja en la captura :cuenta.
3. Endpoint en modo sesión (alta frecuencia)
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-d '{"name":"saldo-session","method":"GET","path_template":"/v1/saldo-session/:cuenta",
"library":"NSNAT","program":"CPOLIZA","exec_mode":"session",
"fields_json":"[]"}' \
http://host:11032/api/endpoints
# Deploy compila el driver NSRPDRV (una vez por librería)
curl -s -b cookies.txt -X POST http://host:11032/api/endpoints/2/deploy
4. Token de API con alcance y allowlist
Ver la sección «Token con alcance» más arriba (crea primero el endpoint cuyo nombre pasas en --endpoint).
5. Cliente que distingue rc de HTTP
var body = client.send(req, BodyHandlers.ofString()).body();
// HTTP 200 + {"ok":true,"rc":4,"output":"CUENTA NO ENCONTRADA",...}
// → no es un fallo técnico: el negocio dice que la cuenta no existe.
6. Observabilidad
# Métricas por endpoint (admin, cookie de consola)
curl -s -b cookies.txt http://host:11032/api/stats | jq '.[] | {endpoint_id,calls,errors,avg_ms}'
# Ocupación del pool de sesiones
curl -s -b cookies.txt http://host:11032/api/system/stats | jq '.natural.sessions'
# Llamadas filtradas por librería (la página Calls muestra Environment, Library y PID)
curl -s -b cookies.txt 'http://host:11032/api/calls?library=NSNAT' | jq '.items[] | {environment,library,exec_mode,session_pid,http_status}'
Configuración (config.yaml)
| Bloque | Clave | Descripción |
|---|---|---|
server | host / port / trustedProxies | Escucha (def. 0.0.0.0:11032) y proxies de confianza |
database | path | SQLite (RBAC, endpoints, tokens, auditoría) |
auth | jwtSecret / jwtExpiryMin / cookieName / tokenPepper / secureCookies | Sesión de consola y hash de tokens |
natural | naturalBin / sagenv / bp / natparm / fuserDbid / fuserFnr / fuserPath / numSessions / maxSessions / sessionIdleSec | Entorno por defecto |
defaultEnvironment | <DEV/TST/PRD> | Entorno usado cuando un endpoint no especifica ninguno |
environments | <DEV/TST/PRD> | Overrides por entorno (heredan del bloque natural) |
exec | defaultTimeoutSec / maxTimeoutSec / maxConcurrent / queueTimeoutSec / ratePerMin / callRetentionDays / maxBodyBytes / maxOutputBytes | Límites de ejecución |
correo | mailer, host, port, … | Envío de correo (reset de contraseña, avisos de token) |
logging | dir / maxSizeMB / maxAgeDays / maxBackups / compress | Rotación de logs |
license | trialDays | Días de trial si no hay license.key |
apiDocs | access | Visibilidad del spec: public / private / off |
updates | url | Reservado: se guarda y se edita, pero hoy no lo consume ningún componente |
Todo se edita también desde la consola (Configuration, solo admin). Se aplican
al instante: timeouts, concurrencia, cola, entornos y apiDocs.access.
Requieren reinicio: server.host, server.port, database.path,
auth.cookieName, logging.dir y auth.jwtSecret (la propia página lo
indica). El rate limit (exec.ratePerMin) se fija al arrancar: cambiarlo
no surte efecto hasta el siguiente reinicio.
Seguridad
- Tokens Bearer por endpoint con alcance, caducidad, revocación y allowlist de IP (
--cidr), más rate limiting por IP y por token. - Consola con JWT, bcrypt, TOTP 2FA y RBAC; rotación de refresh tokens con detección de reuso.
- Roles y grupos: los permisos se conceden a roles, los grupos asignan un rol y los usuarios pertenecen a grupos (permisos = unión). Catálogo:
dashboard,endpoints,tokens,calls,sessions(cada unoview/manage),users,config,logs. Página Roles & Groups; el grupo de sistema Administrators tiene todos los permisos yinitdeja ahí al usuarioadmin. - Recuperación de contraseña: en Users, Reset link genera un enlace de un solo uso (60 min) y lo envía por correo; si el correo no está configurado o falla, la consola muestra el motivo y copia el enlace para entrega manual.
- Validación estricta de todo valor que llega a Natural o al FUSER; sin shell con datos de usuario; sin path traversal.
- Auditoría de cada llamada (solo metadatos, nunca bodies); retención configurable.
Problemas típicos
| Síntoma | Causa probable |
|---|---|
502 protocol: missing OUT sentinel | Stub no desplegado o el programa no devuelve control (STOP, bucle) |
502 natural execution failed | Error de Natural; revisa el log de llamadas |
Sesión: session boot timeout | Falta el driver NSRPDRV o la librería/fuserPath/bp no resuelven |
Sesión: session error (RC=935) | La firma DEFINE DATA PARAMETER no sigue el convenio (3 parámetros) |
503 server too busy / no free Natural session | Concurrencia o pool llenos: sube maxConcurrent/numSessions |
504 timeout | El programa tarda más que timeout_sec |
502 invalid json from program | El programa emite comillas simples: usa H'22' (o GENERATE JSON) para las comillas dobles |
400 missing param X | Campo requerido sin valor (query en GET/DELETE, body en POST/PUT); revisa que las capturas del path no estén duplicadas en fields_json |
400 params too long (max 240 chars) | Demasiados campos o valores largos para una línea |
Límites (framing v2)
Entrada: una línea de params (≤240 caracteres por INPUT). Salida: las respuestas largas (>240) se transmiten fragmentadas (framing v2: NSNAT:OUT:<len> + NSNAT:C:<len>:<chunk>×N + NSNAT:END RC=<n>), así que el tope de 240 del WRITE de una línea no aplica al payload total; el tope real de salida es exec.maxOutputBytes (1 MB). Timeout por endpoint (def. 60 s, tope 300 s), concurrencia configurable y rate limit por token.
Referencia rápida
| Comando | Descripción |
|---|---|
nsnatapi init [dir] | Crea configuración, base de datos y usuario admin |
nsnatapi start|stop|status [dir] | Ciclo de vida del servicio |
nsnatapi token [dir] --name X --endpoint Y --days N [--cidr ...] | Crea un token de API (el endpoint Y debe existir) |
nsnatapi token [dir] --list | --revoke <prefix> | Lista o revoca tokens |
nsnatapi passwd [dir] <usuario> [clave] | Restablece la contraseña de consola (se genera si se omite) |
nsnatapi license [dir] | Estado de la licencia (código de salida 1 si no es válida) |
nsnatapi --help | Ayuda y flags globales |