Guía operativa completa para conectar landing pages, webhooks, n8n, scripts Python y cualquier cliente externo a tu agente Hermes Agent.
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.
Soporta /v1/chat/completions y /v1/responses con streaming.
Crea, bifurca y continúa conversaciones persistentes vía /api/sessions.
Las tools se ejecutan en el mismo host del gateway, no en el cliente.
Endpoints /v1/runs con eventos SSE para flujos largos o que requieren aprobación.
~/.hermes/config.yaml define el puerto y si está habilitado. ~/.hermes/.env contiene API_SERVER_KEY y variables de red.
api_server:
enabled: true
port: 8642
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.
API_SERVER_KEY no está configurada.| Método | Path | Descripción |
|---|---|---|
| GET | /health | Health check simple. Sin autenticación. |
| GET | /health/detailed | Estado enriquecido del gateway. Sin auth. |
| GET | /v1/models | Modelo disponible. |
| GET | /v1/capabilities | Capacidades y endpoints en JSON. |
| GET | /v1/skills | Skills cargadas por el agente. |
| GET | /v1/toolsets | Toolsets habilitados y sus tools. |
| Método | Path | Descripción |
|---|---|---|
| POST | /v1/chat/completions | Chat estilo OpenAI. Soporta streaming. |
| POST | /v1/responses | Responses API de OpenAI (stateful). |
| GET | /v1/responses/{id} | Recuperar respuesta almacenada. |
| DELETE | /v1/responses/{id} | Borrar respuesta almacenada. |
| Método | Path | Descripción |
|---|---|---|
| GET | /api/sessions | Listar sesiones persistidas. |
| POST | /api/sessions | Crear 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}/messages | Historial de mensajes. |
| POST | /api/sessions/{id}/fork | Bifurcar sesión. |
| POST | /api/sessions/{id}/chat | Chatear en sesión existente. |
| POST | /api/sessions/{id}/chat/stream | Versión streaming. |
| Método | Path | Descripción |
|---|---|---|
| POST | /v1/runs | Iniciar run. Devuelve run_id (202). |
| GET | /v1/runs/{id} | Estado actual del run. |
| GET | /v1/runs/{id}/events | Stream SSE de eventos. |
| POST | /v1/runs/{id}/approval | Resolver aprobación pendiente. |
| POST | /v1/runs/{id}/stop | Interrumpir run en curso. |
Todas las rutas excepto /health requieren este header:
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:
{
"error": {
"message": "Invalid API key",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
}
| Header | Uso |
|---|---|
Authorization | Bearer token obligatorio. |
X-Hermes-Session-Id | Continuar sesión existente de state.db. Requiere API key. |
X-Hermes-Session-Key | Identificador estable para memoria a largo plazo. |
Idempotency-Key | Cachear respuestas no-streaming de /v1/chat/completions. |
Origin | Para CORS; debe coincidir con API_SERVER_CORS_ORIGINS. |
X-Hermes-Session-Id solo se acepta si hay API_SERVER_KEY. Sin ella, cualquiera podría leer sesiones adivinando IDs.
curl http://localhost:PUERTO/health
curl -H "Authorization: Bearer tu-clave-secreta-aqui" \
http://localhost:PUERTO/v1/models
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."}
]
}'
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."}
]
}'
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
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."}'
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)
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)
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.
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.
Recibe un tema y devuelve titulares, esqueleto del post, hashtags y CTA para blog, LinkedIn o X.
Recibe push/PR, llama a /v1/runs con el prompt "revisa este diff" y consume /v1/runs/{id}/events para mostrar progreso.
Dashboards consultan /health/detailed para saber si el gateway default está vivo y cuántos agentes activos hay.
En un nodo HTTP Request:
http://localhost:PUERTO/v1/chat/completionsAuthorization: Bearer tu-clave-secreta-aquimodel y messages.X-Hermes-Session-Key para continuidad.Usa siempre un proxy intermedio con HTTPS y autenticación adicional.
Internet → HTTPS → nginx / Vercel / Cloudflare → tu middleware → localhost:PUERTO
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())
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.
127.0.0.1 (loopback).API_SERVER_KEY obligatoria; sin ella no arranca.hmac.compare_digest().openssl rand -hex 32
API_SERVER_CORS_ORIGINS="https://tu-dominio.es,https://app.tu-dominio.es"
/health/detailed para alertas de uptime.nohup hermes gateway run. Solo systemd:
systemctl --user start hermes-gateway-default.service
systemctl --user restart hermes-gateway-default.service
systemctl --user stop hermes-gateway-default.service
/api/jobs: con la API key se pueden crear cron jobs.| Síntoma | Causa probable | Solución |
|---|---|---|
Connection refused | Gateway parado. | systemctl --user start hermes-gateway-default.service |
Invalid API key | Key mal copiada o .env no recargado. | Verificar API_SERVER_KEY y reiniciar. |
No respeta X-Hermes-Session-Id | Sin API_SERVER_KEY. | Configurar key y reiniciar. |
| CORS blocked | Origen no permitido. | Añadir dominio o usar proxy propio. |
| Respuesta vacía | Mensaje vacío o solo system. | Incluir al menos un mensaje user con contenido. |
| Tool call no ejecuta | Toolset no habilitado. | Revisar platform_toolsets.api_server en config. |
/ruta/a/hermes-agent/gateway/platforms/api_server.pyskill_view(name='hermes-agent')