API Security · IPSmart

Documentación de la API

El servicio se consume por HTTP desde cualquier lenguaje. Aquí están los 5 endpoints, qué devuelve cada uno, qué falla cuando falla, y el mismo código escrito en 9 lenguajes. Elija el suyo arriba y la página entera cambia con usted.

5 endpoints 9 lenguajes Base: https://api-security.ipsmart.app Autenticación: Bearer
Lenguaje

Autenticación

Token estático en cabecera

Todo lo que requiere token lo pide en la cabecera Authorization, con el esquema Bearer. No hay tokens en la cadena de consulta: acabarían en los registros del servidor y en el historial del navegador. La comparación del token se hace en tiempo constante, así que no se puede adivinar midiendo lo que tarda la respuesta.

La cabecera
Cabecera HTTP
Authorization: Bearer SU_TOKEN
Lo que necesita en su lenguaje

Incluido en macOS, Linux y Windows 10+. Nada que instalar.

Node.js 20+. fetch y el sistema de ficheros vienen incluidos, sin dependencias.

Cualquier navegador moderno. Solo alcanza lo público: el token no puede vivir en el front, lo lee cualquiera que abra el código fuente.

Python 3.9+ y pip install requests.

PHP 8.1+ con la extensión curl activa.

Go 1.21+. Solo biblioteca estándar.

Java 17+. HttpClient es estándar desde Java 11.

.NET 8+. HttpClient es estándar.

Ruby 3.0+. net/http es estándar.

Sin token no se cierra la puerta, se cambia la respuesta

GET /api/security/latest sin token devuelve el boletín público en JSON; con token devuelve el documento de reglas de pentest en Markdown. GET /api/security/rules y POST /api/security/sync sí exigen token y responden 401 sin él.

Endpoints

5 rutas · ejemplo en el lenguaje elegido
GET /health Sin token

Comprueba que el servicio está en pie

Sonda de vida para el balanceador, el monitor o el despliegue. No consulta el almacén ni las fuentes externas, así que responde aunque la sincronización esté fallando. Es el único endpoint que se puede llamar en bucle sin miramientos.

Respuestas
CódigoContent-TypeCuándo
200application/jsonSiempre que el proceso responda.
Qué devuelve
CampoTipoSignificado
statusstringVale "ok" cuando el proceso atiende peticiones.
Ejemplo
Ejemplo · cURL
curl -s https://api-security.ipsmart.app/health
Ejemplo · Node.js
const r = await fetch('https://api-security.ipsmart.app/health');
console.log(r.status, await r.json());
Ejemplo · JavaScript (navegador)
// El navegador puede llamar al servicio: CORS está abierto para lo público.
const r = await fetch('https://api-security.ipsmart.app/health');
console.log(r.status, await r.json());
Ejemplo · Python
import requests

r = requests.get("https://api-security.ipsmart.app/health", timeout=10)
print(r.status_code, r.json())
Ejemplo · PHP
<?php
$json = file_get_contents('https://api-security.ipsmart.app/health');
var_dump(json_decode($json, true));
Ejemplo · Go
resp, err := http.Get("https://api-security.ipsmart.app/health")
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
fmt.Println(resp.Status)
Ejemplo · Java
HttpClient c = HttpClient.newHttpClient();
HttpRequest r = HttpRequest.newBuilder(URI.create("https://api-security.ipsmart.app/health")).build();
System.out.println(c.send(r, BodyHandlers.ofString()).body());
Ejemplo · C#
using var http = new HttpClient();
var body = await http.GetStringAsync("https://api-security.ipsmart.app/health");
Console.WriteLine(body);
Ejemplo · Ruby
require "net/http"

puts Net::HTTP.get(URI("https://api-security.ipsmart.app/health"))
GET /api/security/latest Sin token

Boletín público de vulnerabilidades, sin token

El mismo recurso tiene dos caras. Sin cabecera de autorización devuelve, por cada vulnerabilidad, el problema y la recomendación de qué hacer con él, además del identificador, la severidad, la puntuación y los productos afectados. Lo que no sale es el detalle explotable ni los pasos de pentest: eso es lo que cuesta el token. Es lo que alimenta cualquier panel o web que quiera mostrar el estado del día y qué hacer al respecto.

Respuestas
CódigoContent-TypeCuándo
200application/jsonSiempre, incluso si aún no hay datos: devuelve la lista vacía.
Qué devuelve
CampoTipoSignificado
modostringVale "publico". Sirve para detectar que el token no llegó.
totalnumberCuántos registros trae el arreglo data.
data[].cveIdstringIdentificador CVE, por ejemplo CVE-2026-1234.
data[].titulostringTitular corto de la vulnerabilidad.
data[].descripcionstringEl problema, tal y como lo publica la fuente: qué falla y bajo qué condiciones.
data[].severidadstringcritical, high, medium o low.
data[].cvssnumberPuntuación CVSS de 0 a 10.
data[].productosAfectadosstring[]Productos y componentes señalados por la fuente. Sirve para cruzar con su propio inventario.
data[].recomendacionstringQué hacer, derivado del vector CVSS: qué actualizar, qué cerrar mientras tanto, a quién avisar y qué registrar.
data[].publicadastringFecha de publicación en ISO 8601.
Ejemplo
Ejemplo · cURL
curl -s https://api-security.ipsmart.app/api/security/latest
Ejemplo · Node.js
const r = await fetch('https://api-security.ipsmart.app/api/security/latest');
const { total, data } = await r.json();
for (const v of data) console.log(v.severidad, v.cveId, v.titulo);
Ejemplo · JavaScript (navegador)
const r = await fetch('https://api-security.ipsmart.app/api/security/latest');
const { data } = await r.json();

const lista = document.querySelector('#boletin');
for (const v of data) {
  const li = document.createElement('li');
  // textContent y no innerHTML: el titulo viene de fuera y no se pinta como marcado.
  li.textContent = v.severidad + ' ' + v.cveId + ' ' + v.titulo;
  lista.append(li);
}
Ejemplo · Python
import requests

r = requests.get("https://api-security.ipsmart.app/api/security/latest", timeout=15)
for v in r.json()["data"]:
    print(v["severidad"], v["cveId"], v["titulo"])
Ejemplo · PHP
<?php
$r = json_decode(file_get_contents('https://api-security.ipsmart.app/api/security/latest'), true);
foreach ($r['data'] as $v) {
    echo $v['severidad'], ' ', $v['cveId'], ' ', $v['titulo'], PHP_EOL;
}
Ejemplo · Go
resp, err := http.Get("https://api-security.ipsmart.app/api/security/latest")
if err != nil { log.Fatal(err) }
defer resp.Body.Close()

var out map[string]any
json.NewDecoder(resp.Body).Decode(&out)
fmt.Println(out["total"], "vulnerabilidades")

for _, v := range out["data"].([]any) {
    fila := v.(map[string]any)
    fmt.Println(fila["severidad"], fila["cveId"], fila["titulo"])
}
Ejemplo · Java
HttpClient c = HttpClient.newHttpClient();
HttpRequest r = HttpRequest.newBuilder(
        URI.create("https://api-security.ipsmart.app/api/security/latest")).build();
HttpResponse<String> resp = c.send(r, BodyHandlers.ofString());
System.out.println(resp.body());
Ejemplo · C#
using var http = new HttpClient();
var json = await http.GetStringAsync("https://api-security.ipsmart.app/api/security/latest");
using var doc = JsonDocument.Parse(json);
Console.WriteLine(doc.RootElement.GetProperty("total"));
Ejemplo · Ruby
require "net/http"
require "json"

body = Net::HTTP.get(URI("https://api-security.ipsmart.app/api/security/latest"))
JSON.parse(body)["data"].each { |v| puts "#{v['severidad']} #{v['cveId']} #{v['titulo']}" }
GET /api/security/latest Requiere token

Reglas de pentest en Markdown, con token

La misma ruta con cabecera Authorization devuelve otra cosa: el documento de reglas de pentest del día en Markdown, generado a partir de las vulnerabilidades más graves. Es lo que consume un agente para saber qué probar hoy. Guarde el valor de x-rules-hash: si no cambió, el documento es el mismo y no hace falta reprocesarlo.

Cabeceras
CabeceraTipoPara qué
AuthorizationstringBearer seguido del token. Obligatoria para obtener el Markdown.
Respuestas
CódigoContent-TypeCuándo
200text/markdown; charset=utf-8Token válido y documento ya generado.
503application/jsonToken válido pero la primera sincronización aún no terminó. code RULES_NOT_READY.
Qué devuelve
CampoTipoSignificado
x-rules-hashcabeceraSHA-256 del documento. Compárelo para no descargar dos veces lo mismo.
x-rules-generated-atcabeceraMomento de generación en ISO 8601.
cuerpotext/markdownEl documento completo, listo para guardar como fichero .md.
Ejemplo
Ejemplo · cURL
curl -s -H "Authorization: Bearer $API_SECURITY_TOKEN" https://api-security.ipsmart.app/api/security/latest -o reglas.md -D cabeceras.txt
Ejemplo · Node.js
const r = await fetch('https://api-security.ipsmart.app/api/security/latest', {
  headers: { Authorization: 'Bearer ' + process.env.API_SECURITY_TOKEN },
});
console.log(r.headers.get('x-rules-hash'));
console.log(await r.text());
Ejemplo · JavaScript (navegador)
// El token no puede vivir en el navegador: quien abre la pagina lo lee.
// Su servidor habla con api-security, y el navegador habla con su servidor.
const r = await fetch('/api/reglas', { credentials: 'same-origin' });
document.querySelector('#reglas').textContent = await r.text();
Ejemplo · Python
import os, requests

r = requests.get(
    "https://api-security.ipsmart.app/api/security/latest",
    headers={"Authorization": "Bearer " + os.environ["API_SECURITY_TOKEN"]},
    timeout=30,
)
print(r.headers["x-rules-hash"])
print(r.text)
Ejemplo · PHP
<?php
$ch = curl_init('https://api-security.ipsmart.app/api/security/latest');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('API_SECURITY_TOKEN')]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$markdown = curl_exec($ch);
curl_close($ch);
echo $markdown;
Ejemplo · Go
req, _ := http.NewRequest("GET", "https://api-security.ipsmart.app/api/security/latest", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("API_SECURITY_TOKEN"))

resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()

md, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Header.Get("x-rules-hash"))
os.WriteFile("reglas.md", md, 0o600)
Ejemplo · Java
HttpRequest r = HttpRequest.newBuilder(
        URI.create("https://api-security.ipsmart.app/api/security/latest"))
    .header("Authorization", "Bearer " + System.getenv("API_SECURITY_TOKEN"))
    .build();

HttpResponse<String> resp = HttpClient.newHttpClient().send(r, BodyHandlers.ofString());
System.out.println(resp.headers().firstValue("x-rules-hash").orElse("sin hash"));
Files.writeString(Path.of("reglas.md"), resp.body());
Ejemplo · C#
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("API_SECURITY_TOKEN"));

var resp = await http.GetAsync("https://api-security.ipsmart.app/api/security/latest");
Console.WriteLine(resp.Headers.GetValues("x-rules-hash").First());
await File.WriteAllTextAsync("reglas.md", await resp.Content.ReadAsStringAsync());
Ejemplo · Ruby
require "net/http"

uri = URI("https://api-security.ipsmart.app/api/security/latest")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('API_SECURITY_TOKEN')}"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res["x-rules-hash"]
File.write("reglas.md", res.body)
GET /api/security/rules Requiere token

El Markdown sin negociación de contenido

Para clientes que solo quieren el fichero y no quieren depender de si mandaron o no la cabecera. Sin token responde 401, nunca contenido público. Es la ruta recomendada para un cron o un agente de sincronización: si un día el token caduca, el fallo es ruidoso en vez de silencioso.

Cabeceras
CabeceraTipoPara qué
AuthorizationstringBearer seguido del token. Obligatoria.
Respuestas
CódigoContent-TypeCuándo
200text/markdown; charset=utf-8Token válido y documento generado.
401application/jsonSin token o con token incorrecto. code AUTH_REQUIRED.
503application/jsonAún no hay documento generado. code RULES_NOT_READY.
Qué devuelve
CampoTipoSignificado
x-rules-hashcabeceraSHA-256 del documento servido.
cuerpotext/markdownEl documento de reglas vigente.
Ejemplo
Ejemplo · cURL
curl -sS -f -H "Authorization: Bearer $API_SECURITY_TOKEN" https://api-security.ipsmart.app/api/security/rules > reglas.md
Ejemplo · Node.js
const r = await fetch('https://api-security.ipsmart.app/api/security/rules', {
  headers: { Authorization: 'Bearer ' + process.env.API_SECURITY_TOKEN },
});
if (!r.ok) throw new Error('api-security respondio ' + r.status);
await writeFile('reglas.md', await r.text());
Ejemplo · JavaScript (navegador)
// Igual que arriba: desde el navegador nunca sale la cabecera Authorization.
// Su backend descarga el documento y lo sirve a quien ya tenga sesion abierta.
const r = await fetch('/api/reglas', { credentials: 'same-origin' });
if (!r.ok) throw new Error('su backend respondio ' + r.status);
document.querySelector('#reglas').textContent = await r.text();
Ejemplo · Python
import os, requests

r = requests.get(
    "https://api-security.ipsmart.app/api/security/rules",
    headers={"Authorization": "Bearer " + os.environ["API_SECURITY_TOKEN"]},
    timeout=30,
)
r.raise_for_status()
open("reglas.md", "w", encoding="utf-8").write(r.text)
Ejemplo · PHP
<?php
$ch = curl_init('https://api-security.ipsmart.app/api/security/rules');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('API_SECURITY_TOKEN')],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
]);
$md = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 200) {
    throw new RuntimeException('api-security no devolvio las reglas');
}
file_put_contents('reglas.md', $md);
Ejemplo · Go
req, _ := http.NewRequest("GET", "https://api-security.ipsmart.app/api/security/rules", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("API_SECURITY_TOKEN"))

resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
    log.Fatal("api-security respondio ", resp.Status)
}
md, _ := io.ReadAll(resp.Body)
os.WriteFile("reglas.md", md, 0o600)
Ejemplo · Java
HttpRequest r = HttpRequest.newBuilder(
        URI.create("https://api-security.ipsmart.app/api/security/rules"))
    .header("Authorization", "Bearer " + System.getenv("API_SECURITY_TOKEN"))
    .timeout(Duration.ofSeconds(30))
    .build();

HttpResponse<String> resp = HttpClient.newHttpClient().send(r, BodyHandlers.ofString());
if (resp.statusCode() != 200) throw new IllegalStateException("HTTP " + resp.statusCode());
Files.writeString(Path.of("reglas.md"), resp.body());
Ejemplo · C#
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("API_SECURITY_TOKEN"));

var resp = await http.GetAsync("https://api-security.ipsmart.app/api/security/rules");
resp.EnsureSuccessStatusCode();
await File.WriteAllTextAsync("reglas.md", await resp.Content.ReadAsStringAsync());
Ejemplo · Ruby
require "net/http"

uri = URI("https://api-security.ipsmart.app/api/security/rules")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('API_SECURITY_TOKEN')}"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
raise "api-security respondio #{res.code}" unless res.code == "200"
File.write("reglas.md", res.body)
POST /api/security/sync Requiere token

Fuerza la sincronización con las fuentes

Dispara a mano lo que el servicio hace solo al arrancar: consultar las fuentes públicas de vulnerabilidades y regenerar el documento de reglas. Úselo cuando no quiera esperar al ciclo automático. No lo llame en bucle: cada llamada golpea a las fuentes externas y estas aplican límite de peticiones.

Cabeceras
CabeceraTipoPara qué
AuthorizationstringBearer seguido del token. Obligatoria.
Respuestas
CódigoContent-TypeCuándo
200application/jsonLa sincronización terminó, aunque alguna fuente haya fallado.
401application/jsonSin token o con token incorrecto. code AUTH_REQUIRED.
Qué devuelve
CampoTipoSignificado
obtenidasnumberCuántas vulnerabilidades se trajeron en total.
fuentesOkstring[]Nombres de las fuentes que respondieron bien.
fuentesFallidasstring[]Fuentes caídas. Que la lista no esté vacía no es un error de la llamada.
hashReglasstringHash del documento recién generado.
Ejemplo
Ejemplo · cURL
curl -sS -X POST -H "Authorization: Bearer $API_SECURITY_TOKEN" https://api-security.ipsmart.app/api/security/sync
Ejemplo · Node.js
const r = await fetch('https://api-security.ipsmart.app/api/security/sync', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + process.env.API_SECURITY_TOKEN },
});
const res = await r.json();
console.log(res.obtenidas, 'obtenidas, fallaron', res.fuentesFallidas.length);
Ejemplo · JavaScript (navegador)
// Sincronizar es una accion privilegiada: va por su backend, con la sesion
// del usuario y con proteccion CSRF. El navegador no dispara esto directamente.
const r = await fetch('/api/reglas/sincronizar', {
  method: 'POST',
  credentials: 'same-origin',
  headers: { 'X-CSRF-Token': tokenCsrfDeSuFormulario },
});
console.log(await r.json());
Ejemplo · Python
import os, requests

r = requests.post(
    "https://api-security.ipsmart.app/api/security/sync",
    headers={"Authorization": "Bearer " + os.environ["API_SECURITY_TOKEN"]},
    timeout=60,
)
print(r.json())
Ejemplo · PHP
<?php
$ch = curl_init('https://api-security.ipsmart.app/api/security/sync');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('API_SECURITY_TOKEN')],
    CURLOPT_RETURNTRANSFER => true,
]);
print_r(json_decode(curl_exec($ch), true));
Ejemplo · Go
req, _ := http.NewRequest("POST", "https://api-security.ipsmart.app/api/security/sync", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("API_SECURITY_TOKEN"))

resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Ejemplo · Java
HttpRequest r = HttpRequest.newBuilder(
        URI.create("https://api-security.ipsmart.app/api/security/sync"))
    .header("Authorization", "Bearer " + System.getenv("API_SECURITY_TOKEN"))
    .POST(BodyPublishers.noBody())
    .build();

System.out.println(HttpClient.newHttpClient().send(r, BodyHandlers.ofString()).body());
Ejemplo · C#
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("API_SECURITY_TOKEN"));

var resp = await http.PostAsync("https://api-security.ipsmart.app/api/security/sync", null);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
Ejemplo · Ruby
require "net/http"

uri = URI("https://api-security.ipsmart.app/api/security/sync")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('API_SECURITY_TOKEN')}"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.body

Cliente completo

Descarga las reglas solo si cambiaron

El caso real: un proceso que corre a diario, compara el hash del documento con el que guardó la última vez y solo escribe en disco si hay algo nuevo. Lee el token del entorno, pone tiempo de espera y falla ruidosamente. Cópielo tal cual.

Sincronizador de reglas
Cliente · cURL
#!/bin/sh
# sincronizar-reglas.sh — ejecútelo desde cron. El token va en el entorno,
# nunca escrito dentro del fichero.
set -eu

BASE="https://api-security.ipsmart.app"
DESTINO="reglas.md"
HASH_PREVIO=$(cat reglas.hash 2>/dev/null || echo "")

HASH_ACTUAL=$(curl -sS -f -I -H "Authorization: Bearer $API_SECURITY_TOKEN" "$BASE/api/security/rules" | awk 'tolower($1) == "x-rules-hash:" { gsub(/[[:space:]]/, "", $2); print $2 }')

if [ "$HASH_ACTUAL" = "$HASH_PREVIO" ]; then
  echo "sin cambios"
  exit 0
fi

curl -sS -f -H "Authorization: Bearer $API_SECURITY_TOKEN" "$BASE/api/security/rules" -o "$DESTINO"
echo "$HASH_ACTUAL" > reglas.hash
echo "reglas actualizadas: $HASH_ACTUAL"
Cliente · Node.js
// sincronizar-reglas.mjs — Node.js 20+, sin dependencias.
import { readFile, writeFile } from 'node:fs/promises';

const BASE = 'https://api-security.ipsmart.app';
const TOKEN = process.env.API_SECURITY_TOKEN;
if (!TOKEN) throw new Error('falta API_SECURITY_TOKEN en el entorno');

const previo = await readFile('reglas.hash', 'utf8').catch(() => '');

const r = await fetch(BASE + '/api/security/rules', {
  headers: { Authorization: 'Bearer ' + TOKEN },
  signal: AbortSignal.timeout(30000),
});

if (r.status === 401) throw new Error('token rechazado');
if (r.status === 503) { console.log('reglas aun no generadas'); process.exit(0); }
if (!r.ok) throw new Error('api-security respondio ' + r.status);

const hash = r.headers.get('x-rules-hash') ?? '';
if (hash === previo.trim()) { console.log('sin cambios'); process.exit(0); }

await writeFile('reglas.md', await r.text());
await writeFile('reglas.hash', hash);
console.log('reglas actualizadas:', hash);
Cliente · JavaScript (navegador)
// boletin.js — modulo ES que pinta el boletin publico. Sin token, porque el
// navegador no tiene donde guardar un secreto y este endpoint no lo necesita.
const BASE = 'https://api-security.ipsmart.app';

export async function pintarBoletin(selector) {
  const lista = document.querySelector(selector);
  lista.textContent = 'cargando...';

  let datos;
  try {
    const r = await fetch(BASE + '/api/security/latest', {
      signal: AbortSignal.timeout(15000),
    });
    if (!r.ok) throw new Error('api-security respondio ' + r.status);
    datos = await r.json();
  } catch (error) {
    lista.textContent = 'no se pudo cargar el boletin';
    console.error(error);
    return;
  }

  lista.textContent = '';
  for (const v of datos.data) {
    const li = document.createElement('li');

    const etiqueta = document.createElement('strong');
    etiqueta.textContent = v.severidad + ' ' + v.cvss;

    // Todo lo que llega de fuera entra como texto, nunca como marcado.
    const titulo = document.createTextNode(' ' + v.cveId + ' — ' + v.titulo);

    li.append(etiqueta, titulo);
    lista.append(li);
  }
}
Cliente · Python
#!/usr/bin/env python3
"""sincronizar_reglas.py — pip install requests"""
import os
import pathlib
import sys

import requests

BASE = "https://api-security.ipsmart.app"
token = os.environ.get("API_SECURITY_TOKEN")
if not token:
    sys.exit("falta API_SECURITY_TOKEN en el entorno")

destino = pathlib.Path("reglas.md")
fichero_hash = pathlib.Path("reglas.hash")
previo = fichero_hash.read_text().strip() if fichero_hash.exists() else ""

r = requests.get(
    BASE + "/api/security/rules",
    headers={"Authorization": "Bearer " + token},
    timeout=30,
)

if r.status_code == 401:
    sys.exit("token rechazado")
if r.status_code == 503:
    sys.exit(0)
r.raise_for_status()

actual = r.headers.get("x-rules-hash", "")
if actual == previo:
    print("sin cambios")
    sys.exit(0)

destino.write_text(r.text, encoding="utf-8")
fichero_hash.write_text(actual)
print("reglas actualizadas:", actual)
Cliente · PHP
<?php
// sincronizar-reglas.php — PHP 8.1+ con la extensión curl.
declare(strict_types=1);

const BASE = 'https://api-security.ipsmart.app';

$token = getenv('API_SECURITY_TOKEN');
if ($token === false || $token === '') {
    fwrite(STDERR, "falta API_SECURITY_TOKEN en el entorno" . PHP_EOL);
    exit(1);
}

$previo = is_file('reglas.hash') ? trim(file_get_contents('reglas.hash')) : '';

$cabeceras = [];
$ch = curl_init(BASE . '/api/security/rules');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HEADERFUNCTION => function ($ch, $linea) use (&$cabeceras) {
        $partes = explode(':', $linea, 2);
        if (count($partes) === 2) {
            $cabeceras[strtolower(trim($partes[0]))] = trim($partes[1]);
        }
        return strlen($linea);
    },
]);

$cuerpo = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($codigo === 401) { exit('token rechazado'); }
if ($codigo === 503) { exit(0); }
if ($codigo !== 200) { exit('api-security respondio ' . $codigo); }

$actual = $cabeceras['x-rules-hash'] ?? '';
if ($actual === $previo) {
    echo 'sin cambios', PHP_EOL;
    exit(0);
}

file_put_contents('reglas.md', $cuerpo);
file_put_contents('reglas.hash', $actual);
echo 'reglas actualizadas: ', $actual, PHP_EOL;
Cliente · Go
// sincronizar-reglas.go — Go 1.21+, solo biblioteca estándar.
package main

import (
    "io"
    "log"
    "net/http"
    "os"
    "strings"
    "time"
)

const base = "https://api-security.ipsmart.app"

func main() {
    token := os.Getenv("API_SECURITY_TOKEN")
    if token == "" {
        log.Fatal("falta API_SECURITY_TOKEN en el entorno")
    }

    previo, _ := os.ReadFile("reglas.hash")

    req, _ := http.NewRequest(http.MethodGet, base+"/api/security/rules", nil)
    req.Header.Set("Authorization", "Bearer "+token)

    cliente := &http.Client{Timeout: 30 * time.Second}
    resp, err := cliente.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    switch resp.StatusCode {
    case http.StatusUnauthorized:
        log.Fatal("token rechazado")
    case http.StatusServiceUnavailable:
        log.Println("reglas aun no generadas")
        return
    case http.StatusOK:
    default:
        log.Fatal("api-security respondio ", resp.Status)
    }

    actual := resp.Header.Get("x-rules-hash")
    if actual == strings.TrimSpace(string(previo)) {
        log.Println("sin cambios")
        return
    }

    md, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("reglas.md", md, 0o600); err != nil {
        log.Fatal(err)
    }
    os.WriteFile("reglas.hash", []byte(actual), 0o600)
    log.Println("reglas actualizadas:", actual)
}
Cliente · Java
// SincronizarReglas.java — Java 17+, solo biblioteca estándar.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;

public class SincronizarReglas {
    private static final String BASE = "https://api-security.ipsmart.app";

    public static void main(String[] args) throws Exception {
        String token = System.getenv("API_SECURITY_TOKEN");
        if (token == null || token.isBlank()) {
            throw new IllegalStateException("falta API_SECURITY_TOKEN en el entorno");
        }

        Path ficheroHash = Path.of("reglas.hash");
        String previo = Files.exists(ficheroHash) ? Files.readString(ficheroHash).trim() : "";

        HttpRequest peticion = HttpRequest.newBuilder(URI.create(BASE + "/api/security/rules"))
                .header("Authorization", "Bearer " + token)
                .timeout(Duration.ofSeconds(30))
                .build();

        HttpResponse<String> resp = HttpClient.newHttpClient()
                .send(peticion, BodyHandlers.ofString());

        if (resp.statusCode() == 401) throw new IllegalStateException("token rechazado");
        if (resp.statusCode() == 503) { System.out.println("reglas aun no generadas"); return; }
        if (resp.statusCode() != 200) throw new IllegalStateException("HTTP " + resp.statusCode());

        String actual = resp.headers().firstValue("x-rules-hash").orElse("");
        if (actual.equals(previo)) {
            System.out.println("sin cambios");
            return;
        }

        Files.writeString(Path.of("reglas.md"), resp.body());
        Files.writeString(ficheroHash, actual);
        System.out.println("reglas actualizadas: " + actual);
    }
}
Cliente · C#
// SincronizarReglas.cs — .NET 8+, solo biblioteca estándar.
using System.Net;
using System.Net.Http.Headers;

const string Base = "https://api-security.ipsmart.app";

var token = Environment.GetEnvironmentVariable("API_SECURITY_TOKEN");
if (string.IsNullOrWhiteSpace(token))
{
    Console.Error.WriteLine("falta API_SECURITY_TOKEN en el entorno");
    return 1;
}

var previo = File.Exists("reglas.hash") ? File.ReadAllText("reglas.hash").Trim() : "";

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

var resp = await http.GetAsync(Base + "/api/security/rules");

if (resp.StatusCode == HttpStatusCode.Unauthorized)
{
    Console.Error.WriteLine("token rechazado");
    return 1;
}
if (resp.StatusCode == HttpStatusCode.ServiceUnavailable)
{
    Console.WriteLine("reglas aun no generadas");
    return 0;
}
resp.EnsureSuccessStatusCode();

var actual = resp.Headers.TryGetValues("x-rules-hash", out var valores)
    ? valores.First()
    : "";

if (actual == previo)
{
    Console.WriteLine("sin cambios");
    return 0;
}

await File.WriteAllTextAsync("reglas.md", await resp.Content.ReadAsStringAsync());
await File.WriteAllTextAsync("reglas.hash", actual);
Console.WriteLine("reglas actualizadas: " + actual);
return 0;
Cliente · Ruby
#!/usr/bin/env ruby
# sincronizar_reglas.rb — Ruby 3.0+, solo biblioteca estándar.
require "net/http"

BASE = "https://api-security.ipsmart.app"

token = ENV["API_SECURITY_TOKEN"]
abort "falta API_SECURITY_TOKEN en el entorno" if token.nil? || token.empty?

previo = File.exist?("reglas.hash") ? File.read("reglas.hash").strip : ""

uri = URI("#{BASE}/api/security/rules")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 30) do |http|
  http.request(req)
end

case res.code
when "401" then abort "token rechazado"
when "503" then (puts "reglas aun no generadas"; exit 0)
when "200" then nil
else abort "api-security respondio #{res.code}"
end

actual = res["x-rules-hash"].to_s
if actual == previo
  puts "sin cambios"
  exit 0
end

File.write("reglas.md", res.body)
File.write("reglas.hash", actual)
puts "reglas actualizadas: #{actual}"

Códigos de respuesta

4 casos
CódigoClaveQué significaQué hacer
200 — Todo bien. Mire el content-type para saber si le llegó JSON o Markdown. Nada.
401 AUTH_REQUIRED No llegó la cabecera Authorization, o el token no coincide. Revise que manda Bearer y un espacio antes del token. No reintente: reintentar con un token malo no lo arregla.
503 RULES_NOT_READY El servicio está en pie pero todavía no ha generado el documento de reglas. Espere y vuelva a intentar, o fuerce la sincronización con POST /api/security/sync.
404 — La ruta no existe. Casi siempre es una barra de más o de menos. Compare con la tabla de endpoints de esta página.

Cómo integrarlo sin hacerse daño

6 reglas

El token va en el entorno, nunca en el código

Un token escrito en el fuente acaba en el repositorio y de ahí no sale nunca. Léalo de una variable de entorno o de un gestor de secretos, y si alguna vez pasó por un chat, un correo o una captura, rótelo.

No mande el token en la URL

La cadena de consulta queda en los registros del servidor, en el historial del navegador y en las cabeceras Referer. La autorización viaja en la cabecera, que no se registra.

Compare x-rules-hash antes de reprocesar

Si el hash es el que ya tiene guardado, el documento no cambió. Ahorra ancho de banda y evita que su agente rehaga el mismo trabajo cada hora.

Ponga tiempo de espera y falle ruidosamente

Todo cliente de los ejemplos lleva timeout. Un cliente sin límite de espera se cuelga cuando la red se degrada, y un fallo silencioso en la sincronización de reglas significa trabajar semanas con reglas viejas creyendo que están al día.

No llame a la sincronización en bucle

POST /api/security/sync consulta fuentes externas que aplican límite de peticiones. Una vez al día basta; el servicio ya sincroniza solo al arrancar.

El boletín público es público de verdad

Sin token solo salen titulares: identificador, título, severidad, puntuación y fecha. Si necesita el detalle para probar algo, ese detalle está en el documento de reglas, y el documento requiere token.