🚀 API Server de Hermes

Guía operativa completa para conectar landing pages, webhooks, n8n, scripts Python y cualquier cliente externo a tu agente Hermes Agent.

http://localhost:PUERTO tu-modelo perfil: default

🧠 ¿Qué es el API Server?

El API Server es un adaptador de plataforma de Hermes Agent que expone un servidor HTTP compatible con la API de OpenAI. Permite que cualquier cliente externo converse con tu agente sin depender de Telegram ni del contexto de memoria del asistente principal.

OpenAI-compatible

Soporta /v1/chat/completions y /v1/responses con streaming.

Sesiones nativas

Crea, bifurca y continúa conversaciones persistentes vía /api/sessions.

Ejecución de herramientas

Las tools se ejecutan en el mismo host del gateway, no en el cliente.

Runs estructurados

Endpoints /v1/runs con eventos SSE para flujos largos o que requieren aprobación.

⚙️ Configuración en el VPS

📁 Archivos relevantes

~/.hermes/config.yaml define el puerto y si está habilitado. ~/.hermes/.env contiene API_SERVER_KEY y variables de red.

~/.hermes/config.yaml
api_server:
  enabled: true
  port: 8642
~/.hermes/.env
API_SERVER_KEY="tu-clave-secreta-aqui"   # Obligatoria. Bearer token para autenticar.
API_SERVER_HOST="127.0.0.1"              # Bind. Por defecto loopback.
API_SERVER_PORT="8642"                   # Puerto.
API_SERVER_CORS_ORIGINS=""               # Opcional. Ej: "https://tu-dominio.es"
API_SERVER_MODEL_NAME=""                 # Opcional. Sobrescribe nombre en /v1/models.
⚠️ Reglas duras del servidor
  • Se niega a arrancar si API_SERVER_KEY no está configurada.
  • Si el bind es accesible por red, rechaza claves placeholder (mínimo 8 caracteres).
  • Límite de body: 10 MB.
  • Longitud máxima de headers de sesión: 256 caracteres.

📡 Endpoints disponibles

Health y metadatos

MétodoPathDescripción
GET/healthHealth check simple. Sin autenticación.
GET/health/detailedEstado enriquecido del gateway. Sin auth.
GET/v1/modelsModelo disponible.
GET/v1/capabilitiesCapacidades y endpoints en JSON.
GET/v1/skillsSkills cargadas por el agente.
GET/v1/toolsetsToolsets habilitados y sus tools.

Chat Completions / Responses

MétodoPathDescripción
POST/v1/chat/completionsChat estilo OpenAI. Soporta streaming.
POST/v1/responsesResponses API de OpenAI (stateful).
GET/v1/responses/{id}Recuperar respuesta almacenada.
DELETE/v1/responses/{id}Borrar respuesta almacenada.

Sesiones nativas

MétodoPathDescripción
GET/api/sessionsListar sesiones persistidas.
POST/api/sessionsCrear sesión vacía.
GET/api/sessions/{id}Leer una sesión.
PATCH/api/sessions/{id}Actualizar título o end_reason.
DELETE/api/sessions/{id}Borrar sesión.
GET/api/sessions/{id}/messagesHistorial de mensajes.
POST/api/sessions/{id}/forkBifurcar sesión.
POST/api/sessions/{id}/chatChatear en sesión existente.
POST/api/sessions/{id}/chat/streamVersión streaming.

Runs estructurados

MétodoPathDescripción
POST/v1/runsIniciar run. Devuelve run_id (202).
GET/v1/runs/{id}Estado actual del run.
GET/v1/runs/{id}/eventsStream SSE de eventos.
POST/v1/runs/{id}/approvalResolver aprobación pendiente.
POST/v1/runs/{id}/stopInterrumpir run en curso.

🔐 Autenticación

Todas las rutas excepto /health requieren este header:

Header obligatorio
Authorization: Bearer tu-clave-secreta-aqui

El servidor compara el token con API_SERVER_KEY usando hmac.compare_digest() para evitar timing attacks. Si fallas, devuelve:

Respuesta 401
{
  "error": {
    "message": "Invalid API key",
    "type": "invalid_request_error",
    "code": "invalid_api_key"
  }
}

🪪 Headers especiales de Hermes

HeaderUso
AuthorizationBearer token obligatorio.
X-Hermes-Session-IdContinuar sesión existente de state.db. Requiere API key.
X-Hermes-Session-KeyIdentificador estable para memoria a largo plazo.
Idempotency-KeyCachear respuestas no-streaming de /v1/chat/completions.
OriginPara CORS; debe coincidir con API_SERVER_CORS_ORIGINS.
🛡️ Seguridad de sesión

X-Hermes-Session-Id solo se acepta si hay API_SERVER_KEY. Sin ella, cualquiera podría leer sesiones adivinando IDs.

🔧 Ejemplos con curl

Health check

bash
curl http://localhost:PUERTO/health

Listar modelos

bash
curl -H "Authorization: Bearer tu-clave-secreta-aqui" \
  http://localhost:PUERTO/v1/models

Chat completion simple

bash
curl -X POST http://localhost:PUERTO/v1/chat/completions \
  -H "Authorization: Bearer tu-clave-secreta-aqui" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tu-modelo",
    "messages": [
      {"role": "system", "content": "Eres un asistente creativo."},
      {"role": "user", "content": "Dame 3 ideas de nombres para un producto de automatización con IA."}
    ]
  }'

Streaming

bash
curl -X POST http://localhost:PUERTO/v1/chat/completions \
  -H "Authorization: Bearer tu-clave-secreta-aqui" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tu-modelo",
    "stream": true,
    "messages": [
      {"role": "user", "content": "Escribe un slogan para mi marca."}
    ]
  }'

Continuar una sesión existente

bash
SESSION_ID="api_1234abcd"

curl -X POST http://localhost:PUERTO/v1/chat/completions \
  -H "Authorization: Bearer tu-clave-secreta-aqui" \
  -H "X-Hermes-Session-Id: $SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tu-modelo",
    "messages": [
      {"role": "user", "content": "Ahora dame una versión más corta."}
    ]
  }'

Crear sesión y chatear

bash
# Crear sesión
SESSION=$(curl -X POST http://localhost:PUERTO/api/sessions \
  -H "Authorization: Bearer tu-clave-secreta-aqui" \
  -H "Content-Type: application/json" \
  -d '{"title": "Brainstorm landing Junio", "system_prompt": "Eres experto en copy para desarrolladores."}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['session']['id'])")

# Enviar mensaje dentro de la sesión
curl -X POST "http://localhost:PUERTO/api/sessions/$SESSION/chat" \
  -H "Authorization: Bearer tu-clave-secreta-aqui" \
  -H "Content-Type: application/json" \
  -d '{"message": "Propón 3 hero sections para una landing de IA."}'

🐍 Ejemplos con Python

Con requests

python
import requests, json

BASE = "http://localhost:PUERTO"
API_KEY = "tu-clave-secreta-aqui"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# Chat simple
resp = requests.post(
    f"{BASE}/v1/chat/completions",
    headers=HEADERS,
    json={
        "model": "tu-modelo",
        "messages": [
            {"role": "user", "content": "Resume en 3 bullets por qué una PWA vende más que una web tradicional."}
        ],
    },
)
print(resp.json()["choices"][0]["message"]["content"])

# Streaming
with requests.post(
    f"{BASE}/v1/chat/completions",
    headers=HEADERS,
    json={"model": "tu-modelo", "stream": True, "messages": [{"role": "user", "content": "Hola"}]},
    stream=True,
) as r:
    for line in r.iter_lines():
        if line and line.startswith(b"data: "):
            payload = line[6:].decode()
            if payload == "[DONE]":
                break
            chunk = json.loads(payload)
            delta = chunk["choices"][0].get("delta", {}).get("content", "")
            print(delta, end="", flush=True)

Con el SDK oficial de OpenAI

python
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:PUERTO/v1",
    api_key="tu-clave-secreta-aqui",
    default_headers={
        "X-Hermes-Session-Key": "tu-session-key:formulario:contacto",
    },
)

response = client.chat.completions.create(
    model="tu-modelo",
    messages=[
        {"role": "system", "content": "Eres un asistente creativo."},
        {"role": "user", "content": "¿Qué servicios ofrece tu marca?"},
    ],
)
print(response.choices[0].message.content)

🎯 Casos de uso

1

Chatbot embebido en la landing page

Un widget en tu-dominio.es apunta a tu backend /api/chat, que reenvía a Hermes usando X-Hermes-Session-Key para que cada visitante conserve contexto.

2

Clasificación automática de leads

El formulario de contacto dispara un POST que pide al agente clasificar intención, urgencia y presupuesto. Respuesta estructurada para guardar en tu base de datos.

3

Asistente de contenido

Recibe un tema y devuelve titulares, esqueleto del post, hashtags y CTA para blog, LinkedIn o X.

4

Webhook de GitHub / CI

Recibe push/PR, llama a /v1/runs con el prompt "revisa este diff" y consume /v1/runs/{id}/events para mostrar progreso.

5

Panel de salud

Dashboards consultan /health/detailed para saber si el gateway default está vivo y cuántos agentes activos hay.

🔗 Integración con n8n, webhooks y landing pages

n8n

En un nodo HTTP Request:

Webhook → middleware propio

🚫 Nunca expongas el API Server directamente

Usa siempre un proxy intermedio con HTTPS y autenticación adicional.

Internet → HTTPS → nginx / Vercel / Cloudflare → tu middleware → localhost:PUERTO
middleware mínimo en Flask
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)
HERMES_URL = "http://localhost:PUERTO/v1/chat/completions"
API_KEY = "tu-clave-secreta-aqui"

@app.route("/lead-webhook", methods=["POST"])
def lead_webhook():
    data = request.json
    prompt = f"Lead: {data.get('name')} ({data.get('email')})\nMensaje: {data.get('message')}\nClasifica y responde."
    r = requests.post(HERMES_URL, headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }, json={
        "model": "tu-modelo",
        "messages": [{"role": "user", "content": prompt}],
    })
    return jsonify(r.json())

Landing page con JavaScript

javascript
async function askHermes(message) {
  const res = await fetch("/api/chat", {  // tu proxy backend
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message }),
  });
  return await res.json();
}

El proxy backend añade el Authorization y reenvía a Hermes. La API key nunca llega al navegador.

🛡️ Límites de seguridad

Lo que Hermes ya hace por defecto

Lo que DEBES hacer tú

  1. No expongas el puerto del API Server a Internet. Usa proxy reverso con HTTPS y auth extra.
  2. No compartas la API key con el frontend. Solo en servidor.
  3. Usa claves fuertes:
    bash
    openssl rand -hex 32
  4. Restringe CORS a dominios concretos:
    bash
    API_SERVER_CORS_ORIGINS="https://tu-dominio.es,https://app.tu-dominio.es"
  5. Añade rate limiting en el proxy o middleware para evitar facturas sorpresa.
  6. Monitorea /health/detailed para alertas de uptime.
  7. No uses nohup hermes gateway run. Solo systemd:
    bash
    systemctl --user start hermes-gateway-default.service
    systemctl --user restart hermes-gateway-default.service
    systemctl --user stop hermes-gateway-default.service
  8. Audita /api/jobs: con la API key se pueden crear cron jobs.

🩺 Diagnóstico rápido

SíntomaCausa probableSolución
Connection refusedGateway parado.systemctl --user start hermes-gateway-default.service
Invalid API keyKey mal copiada o .env no recargado.Verificar API_SERVER_KEY y reiniciar.
No respeta X-Hermes-Session-IdSin API_SERVER_KEY.Configurar key y reiniciar.
CORS blockedOrigen no permitido.Añadir dominio o usar proxy propio.
Respuesta vacíaMensaje vacío o solo system.Incluir al menos un mensaje user con contenido.
Tool call no ejecutaToolset no habilitado.Revisar platform_toolsets.api_server en config.

📚 Referencias