Tutorial de Claude API en Ubuntu 24.04 con Python
Crea en un VPS Ubuntu 24.04 un analizador de registros con Claude API: clave segura, virtualenv, streaming, errores tipados y control real del coste.
Qué va a construir
Una herramienta de línea de comandos en un VPS nuevo con Ubuntu 24.04. Puede pasarle un mensaje de error o un fragmento de registro mediante una tubería y obtener un diagnóstico en inglés sencillo: journalctl -u nginx -n 50 | explain. El programa tiene unas sesenta líneas de Python. Incluye los elementos que necesita una aplicación real de la API de Claude: una clave almacenada correctamente, un virtualenv, las estructuras de respuesta del SDK, el streaming, la cadena de excepciones tipadas y una unidad de systemd para que se ejecute sin intervención manual.
He elegido este proyecto de forma deliberada. La mayoría de los tutoriales de «primera aplicación de API» le hacen crear un chatbot que probablemente no volverá a abrir. Un analizador de registros resulta útil en un servidor desde el primer día y le obliga a resolver los dos problemas que los principiantes suelen tener: leer correctamente el objeto de respuesta y controlar el gasto. La API factura por token y no tiene otro límite que los que usted establezca. Por eso, aquí el control de costes es un requisito de diseño, no algo que se añade después. Es la misma disciplina que necesitará cuando pase a ejecutar Claude Code en este mismo VPS dentro de tmux.
Obtenga una clave de API desde la Console
El acceso a la API se gestiona en Anthropic Console, en platform.claude.com. Regístrese y cree una clave en Settings → API Keys (la documentación enlaza directamente a platform.claude.com/settings/keys). La clave se muestra una sola vez, comienza por sk-ant- y no se puede recuperar después. Cópiela de inmediato o elimínela y genere otra.
En cuanto al coste: a fecha de julio de 2026 no existe un nivel gratuito permanente para la API. La documentación de precios de Anthropic indica que los usuarios nuevos reciben una pequeña cantidad de créditos gratuitos para realizar pruebas. La cantidad exacta es la que muestra la Console durante el registro. Cuando se agotan, debe añadir fondos a la cuenta para que las solicitudes se procesen correctamente. Esto es independiente de una suscripción a claude.ai. Los planes Pro o Max no incluyen crédito para la API, y una clave de API no proporciona acceso a la aplicación de chat. Si está comparando una suscripción con la API, esa decisión es un tema independiente: qué plan de Claude necesita realmente.
Cree la clave con alcance limitado a un único proyecto o servidor. Si una clave se filtra, algo que puede ocurrir con el tiempo, querrá revocarla sin interrumpir todo lo demás que administra.
Mantenga la clave fuera de .bashrc
La reacción habitual es export ANTHROPIC_API_KEY=sk-ant-... en ~/.bashrc. No lo haga. Esto plantea tres problemas independientes:
- Todos los procesos la heredan. Una variable de entorno exportada en su shell de inicio de sesión se propaga a todo lo que ejecute: la aplicación web, el informador de fallos que vuelca convenientemente el entorno en un informe de errores y la página
phpinfo()que alguien dejó habilitada. La superficie de exposición de la clave pasa a ser «todo lo que este usuario ejecute». - Al escribirla, termina en
~/.bash_history. Ejecute la exportación manualmente una vez y la clave quedará en un archivo de texto plano para siempre. Además, se incluirá en todas las copias de seguridad de su directorio personal. - No está disponible cuando systemd la necesita. Los servicios no leen su
.bashrc, por lo que este patrón falla justo cuando convierte el script en una unidad, normalmente con un 401 inexplicable a las 6 de la mañana.
El patrón correcto en un servidor es un archivo de entorno dedicado con permisos 600, que sólo cargue el proceso que lo necesita:
sudo mkdir -p /opt/explain
sudo install -m 600 -o root -g root /dev/null /etc/claude-explain.env
printf 'ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE\n' | sudo tee /etc/claude-explain.env >/dev/nullUse tee desde un printf en lugar de un editor si quiere evitar que la clave quede en archivos de intercambio del editor. En cualquier caso, compruebe con ls -l /etc/claude-explain.env que lee -rw------- y que pertenece a root. Los shells interactivos reciben la clave en cada invocación mediante un wrapper (más adelante), y systemd la recibe mediante EnvironmentFile=. root lee el archivo antes de reducir privilegios, por lo que el usuario del servicio no necesita permisos de lectura. La clave nunca aparece en el código, en git, en la salida de ps ni en el historial del shell.
Instalar el SDK en un venv
Ubuntu 24.04 incluye Python 3.12 con la aplicación de PEP 668, por lo que ejecutar pip install anthropic directamente con el intérprete del sistema falla con error: externally-managed-environment. El sistema operativo funciona como debe. Use un entorno virtual:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicEn un servidor no es necesario activar el entorno: llamar directamente a /opt/explain/venv/bin/python siempre usa los paquetes del venv.
Primera llamada y lectura correcta de la respuesta
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
messages=[{"role": "user", "content": "Explain what a systemd unit file is in three sentences."}],
)
for block in response.content:
if block.type == "text":
print(block.text)Dos aspectos de esas doce líneas contienen la mayor parte del modelo mental de la API. Primero, anthropic.Anthropic() sin argumentos lee la clave desde el entorno; no la pase nunca como una cadena literal. Segundo, response.content es una lista de bloques de contenido, no una cadena. Si la imprime directamente, obtiene el resultado clásico de quien empieza:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]No es un error; es la representación repr del objeto. Las respuestas pueden contener varios tipos de bloques (texto, llamadas a herramientas y razonamiento), por lo que debe iterar y comprobar block.type == "text" antes de acceder a .text. Si incorpora ese bucle desde el primer día, evita toda una clase de confusiones del tipo «imprime basura».
Use el ID exacto del modelo claude-opus-4-8. Los ID de la generación actual no incluyen fechas. No siga la costumbre de añadir un sufijo de fecha, aunque lo indique una publicación antigua del blog; eso produce un error 404, como se explica más adelante.
La herramienta real: explicación
Este es el programa completo: recibe datos por stdin, emite el diagnóstico de forma progresiva y gestiona los errores:
#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic
MODEL = "claude-opus-4-8"
def main() -> int:
text = sys.stdin.read().strip()
if not text:
print("usage: journalctl -u nginx -n 50 | explain", file=sys.stderr)
return 1
client = anthropic.Anthropic()
try:
with client.messages.stream(
model=MODEL,
max_tokens=1500,
system=(
"You are a senior Linux sysadmin. The user pipes you server "
"logs or error output. Name the most likely cause outright, "
"then give the commands to confirm and fix it. Be terse."
),
messages=[{"role": "user", "content": text}],
) as stream:
for chunk in stream.text_stream:
print(chunk, end="", flush=True)
print()
except anthropic.RateLimitError as e:
retry_after = e.response.headers.get("retry-after", "60")
print(f"rate limited; retry in {retry_after}s", file=sys.stderr)
return 2
except anthropic.APIStatusError as e:
print(f"API error {e.status_code}: {e.message}", file=sys.stderr)
return 2
except anthropic.APIConnectionError:
print("network error reaching the API", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())Guárdelo como /opt/explain/explain.py y añada después un wrapper que cargue la clave para el uso interactivo:
sudo tee /usr/local/bin/explain >/dev/null <<'EOF'
#!/bin/sh
set -a; . /etc/claude-explain.env; set +a
exec /opt/explain/venv/bin/python /opt/explain/explain.py "$@"
EOF
sudo chmod 755 /usr/local/bin/explain(El wrapper debe ejecutarse mediante sudo o el archivo de entorno debe tener un grupo al que pertenezca su usuario administrador. Elija una opción de forma deliberada en lugar de cambiar los permisos del archivo a 644).
Por qué usar streaming. client.messages.stream imprime los tokens a medida que llegan en lugar de permanecer en silencio durante toda la generación. Además, evita los tiempos de espera agotados de HTTP con respuestas largas. Por este mismo motivo, el SDK rechaza valores muy grandes de max_tokens en las llamadas que no usan streaming. Si necesita el objeto completo después, llame a stream.get_final_message() dentro del bloque with.
Por qué este orden de excepciones. El SDK genera excepciones tipadas, ordenadas de la más específica a la más general: RateLimitError corresponde a un error 429 e incluye una cabecera retry-after que indica cuánto debe esperar; APIStatusError cubre otras respuestas que no sean 2xx (consulte e.status_code >= 500 para detectar problemas del servidor); APIConnectionError indica que la solicitud no recibió ninguna respuesta. Antes de crear un bucle de reintento, tenga en cuenta que el SDK ya reintenta por sí solo los errores 429 y 5xx, dos veces de forma predeterminada con retroceso exponencial (max_retries en el cliente). Cuando se ejecuta except, los reintentos ya se han agotado. Por tanto, en una CLI lo correcto es informar del error y salir, no esperar y seguir generando carga.
Control de costes
Esta cuestión merece una sección propia porque la API no tiene un límite mensual integrado más allá de lo que configure, y cualquier error en este punto se acumula silenciosamente.
max_tokens es el límite máximo de gasto por llamada. Los tokens de salida son la parte más cara: en Opus 4.8 cuestan cinco veces más que los tokens de entrada, y max_tokens establece un límite estricto sobre cuántos puede generar el modelo. Un prompt descontrolado no puede generar más costes de salida de los que haya permitido. Ajuste este valor a la tarea: 1,500 son suficientes para diagnosticar un log; una tarea de clasificación necesita 100. Si las respuestas terminan a mitad de una frase con stop_reason: "max_tokens", el límite es demasiado bajo. Auméntelo de forma consciente en lugar de usar siempre valores muy grandes.
Cuente los tokens antes de enviar la solicitud. La entrada también tiene un coste, y los logs pueden ser voluminosos. La API tiene un endpoint de conteo gratuito (con sus propios límites de frecuencia, independientes de la creación de mensajes):
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Úselo para evitar enviar accidentalmente un log de 2 GB a través de la herramienta. No use tiktoken para esto: es el tokenizer de OpenAI y suele contar aproximadamente un 15–20% menos de tokens de Claude en textos normales, y una proporción aún menor en código.
Elija el modelo según la tarea, no por fidelidad a una opción. En julio de 2026, Opus 4.8 (claude-opus-4-8) cuesta $5 por millón de tokens de entrada y $25 por millón de tokens de salida; Haiku 4.5 (claude-haiku-4-5) cuesta $1/$5 y tiene un contexto de 200K; Sonnet 5 (claude-sonnet-5) queda entre ambos, con un precio de $3/$15 y un precio introductorio de $2/$10 hasta el 31 de agosto de 2026. En concreto: un fragmento de log de 2,000 tokens con una respuesta de 500 tokens cuesta aproximadamente $0.0225 en Opus y $0.0045 en Haiku. Empiece con Opus mientras evalúa la calidad de las respuestas y después pruebe los mismos prompts en Haiku. Para transformaciones sencillas y de gran volumen, a menudo resulta indistinguible por una quinta parte del precio. Verifique los valores actuales en la página de precios antes de incorporarlos de forma fija a un presupuesto.
Use Batches para todo lo que pueda esperar. La API de Batches procesa las solicitudes de forma asíncrona al 50% de los precios estándar, y la mayoría de los lotes termina en una hora. Los resúmenes nocturnos, las cargas históricas, la clasificación masiva y cualquier tarea que no requiera que una persona espere pertenecen a este sistema.
Use el almacenamiento en caché de prompts para el contexto repetido. Si cada llamada vuelve a enviar el mismo prompt de sistema extenso o el mismo runbook, márquelo como almacenable en caché:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
system=[{
"type": "text",
"text": RUNBOOK_TEXT, # the same 30K tokens on every call
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": question}],
)
print(response.usage.cache_read_input_tokens) # non-zero from the second call onLas escrituras en caché cuestan aproximadamente 1.25 veces el precio de entrada y las lecturas, aproximadamente 0.1 veces, con un TTL de 5 minutos. Por tanto, la segunda llamada dentro de ese intervalo ya compensa el coste de la primera. Hay dos aspectos que debe tener en cuenta. El prefijo almacenado en caché debe superar un mínimo por modelo, de varios miles de tokens en Opus, por lo que un prompt de sistema corto no se almacenará en caché. Además, si cache_read_input_tokens permanece en cero en llamadas idénticas, algo del prefijo cambia en cada solicitud (una marca de tiempo suele ser la causa).
Recuerde qué se contabiliza como entrada. Los prompts de sistema, las definiciones de herramientas y, en conversaciones de varios turnos, todo el historial que vuelva a enviar en cada turno se factura como tokens de entrada. Un bucle de chat que nunca recorta el historial aumenta el coste de forma cuadrática. Conviene entender toda la contabilidad antes de crear una aplicación conversacional: cómo se calculan realmente el uso de tokens y la facturación de Claude.
Ejecutarlo con systemd
La ventaja de mantener los archivos de entorno de forma rigurosa es un temporizador que resume cada mañana los errores del día anterior.
# /etc/systemd/system/log-digest.service
[Unit]
Description=Daily error-log digest via the Claude API
[Service]
Type=oneshot
User=explain
Group=systemd-journal
EnvironmentFile=/etc/claude-explain.env
ExecStart=/bin/sh -c 'journalctl -p err --since yesterday | /opt/explain/venv/bin/python /opt/explain/explain.py >> /var/log/log-digest.txt'# /etc/systemd/system/log-digest.timer
[Unit]
Description=Run the log digest every morning
[Timer]
OnCalendar=06:15
Persistent=true
[Install]
WantedBy=timers.targetsudo useradd -r -s /usr/sbin/nologin explain
sudo touch /var/log/log-digest.txt && sudo chown explain /var/log/log-digest.txt
sudo systemctl daemon-reload
sudo systemctl enable --now log-digest.timer
sudo systemctl start log-digest.service # test it once, right nowObserve qué permite EnvironmentFile=: systemd lee el archivo propiedad de root, con modo 600, antes de cambiar al usuario sin privilegios explain. Así, el proceso recibe la variable, pero el usuario no puede leer el archivo de claves. El grupo systemd-journal concede acceso a los registros. Pruebe con un systemctl start manual y lea journalctl -u log-digest.service. No espere hasta las 06:15 para descubrir un error tipográfico. Cuando este patrón ya no sea suficiente para una canalización de shell, el mismo enfoque de usar una clave en un archivo de entorno se puede trasladar directamente a los flujos de trabajo de n8n con Claude en el mismo equipo.
Modos de fallo y cadenas que verá
401 con una clave válida. La excepción muestra:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Si la clave funciona en su shell, pero el servicio devuelve 401, el servicio nunca la recibió. Recuerde que systemd no lee .bashrc; compruebe que EnvironmentFile= apunte a la ruta correcta. Otras causas son las comillas pegadas en el archivo de entorno (ANTHROPIC_API_KEY="sk-ant-..."; systemd elimina las comillas, pero el . file de su wrapper de shell las conserva en el valor si las citó de forma incorrecta), espacios en blanco finales o una clave que revocó en la Console la semana pasada.
404 por un error tipográfico en el modelo. La variante más común consiste en añadir un sufijo de fecha a un ID de modelo actual:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}Los ID de la generación actual deben coincidir exactamente con lo escrito: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Cópielos de la documentación de modelos. No los escriba de memoria ni los copie de un tutorial antiguo.
429 rate_limit_error. La cadena del tipo de error es rate_limit_error y la respuesta incluye una cabecera retry-after con los segundos que debe esperar. El SDK ya ha reintentado dos veces con backoff antes de mostrar la excepción. Por tanto, los 429 persistentes indican que su tasa sostenida supera realmente el límite de su nivel. Agrupe el trabajo o distribúyalo en el tiempo. No reduzca el intervalo del bucle de reintentos.
Imprime el objeto en lugar del texto. La salida tiene el aspecto de [TextBlock(citations=None, text='...', type='text')]. Imprimió response.content en lugar de iterar por los bloques y leer .text de aquellos en los que block.type == "text". Todos los ejemplos del SDK anteriores lo hacen correctamente. Copie el bucle.
error: externally-managed-environment. Ejecutó pip install contra el Python del sistema de Ubuntu 24.04. Use el entorno virtual. No use --break-system-packages en un servidor que deba proteger.
Respuestas truncadas. response.stop_reason == "max_tokens" significa que el modelo alcanzó el límite de salida en mitad de la respuesta. Es el comportamiento previsto. Aumente el límite de forma deliberada.
Cuando la primera aplicación funcione, crear un agente de IA con Claude convierte esas mismas llamadas a la API en un agente que utiliza herramientas.
FAQ
¿Cuánto cuesta probar la API de Claude?
Muy poco para una herramienta de este tipo. En julio de 2026, Opus 4.8 cuesta $5 por millón de tokens de entrada y $25 por millón de tokens de salida. Por tanto, un diagnóstico de logs habitual, con un par de miles de tokens de entrada y unos cientos de salida, cuesta alrededor de dos centavos. Con Haiku 4.5 ($1/$5), cuesta menos de medio centavo. Un mes de resúmenes diarios cuesta menos que un café. El riesgo no es el precio por llamada, sino los bucles sin límite y el max_tokens sin límite. Por eso ambos se establecen explícitamente en esta guía.
¿La API de Claude tiene un nivel gratuito?
No hay un nivel gratuito permanente en julio de 2026. La documentación de precios de Anthropic indica que los usuarios nuevos reciben una pequeña cantidad de créditos gratuitos para probar la API. Es una prueba única. El importe exacto aparece en la Console durante el registro. Después, debe añadir fondos a la cuenta. Si su objetivo es no tener ningún coste marginal por solicitud, en lugar de obtener la máxima calidad disponible, la alternativa es alojar un modelo de pesos abiertos con Ollama y pagar en memoria RAM en vez de tokens.
¿Cómo protejo mi API key en un servidor?
Nunca la guarde en el código, en git ni exportada desde .bashrc. Tampoco la escriba en un shell cuyo historial vaya a conservarla. Guárdela en un archivo propiedad de root con permisos 600 y cárguela por proceso. Para uso interactivo, utilice un script envolvente. Para systemd, utilice EnvironmentFile=. Asigne una clave por servidor o proyecto para poder revocar una clave filtrada de forma controlada. Si la clave aparece alguna vez en un sitio de pegado o en un commit de git, revóquela inmediatamente en la Console. Eliminar el commit no elimina la filtración.
¿Con qué modelo de Claude debería empezar?
Empiece con claude-opus-4-8 mientras evalúa si los resultados son suficientemente buenos para usarlos como base. Así podrá valorar la idea con la máxima calidad. Además, con un volumen de uso personal, la diferencia de coste es de unos centavos. Cuando el prompt esté definido, vuelva a ejecutar sus entradas reales con claude-haiku-4-5. Para resumir, clasificar y hacer una primera evaluación de logs, con frecuencia ofrece resultados igual de buenos por una quinta parte del precio. Cambie a Haiku o Sonnet basándose en mediciones, no por defecto.