tutorial claude api en vps ubuntu
Aprende a crear una app en Python con Claude API en Ubuntu 24.04. Implementa streaming, manejo de errores tipados y control de costes en un VPS real.
Qué vas a construir
Una herramienta de línea de comandos en un VPS con Ubuntu 24.04 recién instalado. Al enviarle un mensaje de error o un fragmento de log mediante un pipe, obtendrás un diagnóstico en lenguaje sencillo: journalctl -u nginx -n 50 | explain. El código tiene aproximadamente sesenta líneas de Python y utiliza todos los elementos necesarios para una aplicación real de Claude API: una clave almacenada correctamente, un virtualenv, las estructuras de respuesta del SDK, streaming, la cadena de excepciones tipadas y una unidad de systemd para su ejecución autónoma.
He seleccionado este proyecto deliberadamente. La mayoría de los tutoriales de "primera aplicación con API" consisten en crear un chatbot que nunca volverás a abrir. Un explicador de logs es útil en un servidor desde el primer día y te obliga a resolver los dos errores comunes de los principiantes: leer correctamente el objeto de respuesta y controlar el gasto. La API factura por token sin límites automáticos, por lo que el control de costes es un requisito de diseño, no algo secundario; la misma disciplina necesaria cuando pases a ejecutar Claude Code en este mismo VPS en tmux.
Obtener una clave API desde la Console
El acceso a la API se gestiona en la Anthropic Console en platform.claude.com — regístrese y cree una clave en Settings → API Keys (el enlace de la documentación redirige directamente a platform.claude.com/settings/keys). La clave se muestra una sola vez, comienza con sk-ant- y no se puede recuperar después — cópiela inmediatamente o elimínela y emita una nueva.
Sobre el costo: a partir de julio de 2026 no existe un nivel gratuito permanente para la API. La documentación de precios de Anthropic indica que los nuevos usuarios reciben una pequeña cantidad de créditos gratuitos para pruebas; el monto exacto es el que muestra la Console al registrarse, y una vez agotados, debe recargar la cuenta para que las solicitudes tengan éxito. Esto es independiente de una suscripción a claude.ai — un plan Pro o Max no incluye créditos para la API, y una clave API no otorga acceso a la aplicación de chat. Si está comparando una suscripción frente al uso de la API, ese análisis es un tema aparte: qué plan de Claude necesita realmente.
Cree la clave con alcance limitado a un solo proyecto o servidor. Cuando una clave se filtre — y con el tiempo suficiente, esto ocurrirá — podrá revocarla sin afectar al resto de sus servicios.
No guarde la clave en .bashrc
El movimiento reflexivo es export ANTHROPIC_API_KEY=sk-ant-... en ~/.bashrc. No lo haga. Esto causa tres problemas distintos:
- Cada proceso la hereda. Una variable de entorno exportada en su shell de inicio se propaga a todo lo que ejecute: la aplicación web, el reporte de errores que incluye el entorno en un reporte de fallos, o la página
phpinfo()que alguien dejó habilitada. La superficie de exposición de la clave se convierte en "todo lo que este usuario ejecute". - Escribirla la guarda en
~/.bash_history. Si ejecuta el export manualmente, su clave quedará en un archivo de texto plano permanentemente y se sincronizará en cada respaldo de su directorio home. - No está disponible cuando systemd la necesita. Los servicios no leen su
.bashrc, por lo que el patrón falla justo cuando convierte el script en una unidad — generalmente como un error 401 inesperado a las 6 a.m.
El patrón correcto en un servidor es un archivo de entorno dedicado con permisos 600, cargado únicamente por el proceso que lo requiere:
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 mediante un printf en lugar de un editor si desea evitar que la clave se guarde en archivos swap del editor; en cualquier caso, verifique con ls -l /etc/claude-explain.env que el archivo lea -rw------- y pertenezca a root. Las shells interactivas obtienen la clave por invocación mediante un wrapper (abajo), y systemd la obtiene mediante EnvironmentFile= — root lee el archivo antes de reducir privilegios, por lo que el usuario del servicio nunca necesita acceso de lectura al mismo. La clave nunca aparece en el código, en git, en la salida de ps, ni en el historial de la shell.
Instalar el SDK en un venv
Ubuntu 24.04 incluye Python 3.12 con cumplimiento de PEP 668, por lo que un pip install anthropic directo contra el intérprete del sistema falla con error: externally-managed-environment. Ese error indica que el SO funciona correctamente; use un virtualenv:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicNo es necesario activar el entorno en un servidor: llamar a /opt/explain/venv/bin/python directamente siempre utiliza 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 elementos en esas doce líneas definen el modelo mental de la API. Primero, anthropic.Anthropic() sin argumentos lee la clave desde el entorno; nunca la pases como una cadena literal. Segundo, response.content es una lista de bloques de contenido, no una cadena. Si la imprimes directamente, obtendrás el error típico de principiante:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Esto no es un error; es el repr del objeto. Las respuestas pueden contener múltiples tipos de bloques (text, tool calls, thinking), por lo que debes iterar y verificar block.type == "text" antes de acceder a .text. Implementa ese bucle desde el inicio para evitar errores de tipo "imprime basura".
Usa el ID de modelo exacto claude-opus-4-8. Los IDs de la generación actual no incluyen fechas; no sigas la costumbre (o las entradas de blogs antiguos) de añadir un sufijo de fecha; eso genera un error 404, que se explica más adelante.
La herramienta real: explicación
Aquí está el programa completo: entrada por stdin, diagnóstico transmitido por streaming y manejo de 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 luego añada un wrapper que cargue la clave para 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 pertenecer a un grupo al que pertenezca su usuario administrador; elija una opción en lugar de cambiar los permisos del archivo a 644).
Por qué usar streaming. client.messages.stream imprime los tokens conforme llegan en lugar de esperar a que finalice la generación completa. Esto evita los timeouts de HTTP en salidas largas; de hecho, el SDK rechaza valores de max_tokens muy grandes en llamadas sin streaming por esa razón. Si necesita el objeto completo después, llame a stream.get_final_message() dentro del bloque with.
Por qué ese orden de excepciones. El SDK lanza excepciones tipadas, de la más específica a la más general: RateLimitError es un error 429 y contiene un encabezado retry-after que indica el tiempo de espera; APIStatusError cubre otras respuestas que no son 2xx (consulte e.status_code >= 500 para problemas del lado del servidor); APIConnectionError significa que la solicitud no recibió respuesta alguna. Antes de implementar un bucle de reintentos, considere esto: el SDK ya reintenta los errores 429 y 5xx automáticamente, dos veces por defecto con backoff exponencial (max_retries en el cliente). Para cuando su except se ejecute, los reintentos habrán terminado; por lo tanto, lo correcto en una CLI es reportar el error y salir, no pausar la ejecución y saturar el servicio.
Control de costes
Esta sección es necesaria porque la API no tiene un límite mensual integrado más allá de su propia configuración, y cualquier error aquí se acumula silenciosamente.
max_tokens es el límite de gasto por llamada. Los tokens de salida son la parte más costosa —en Opus 4.8, cuestan cinco veces más que la entrada— y max_tokens es el límite estricto de tokens que el modelo puede producir. Un prompt descontrolado no puede generar más salida de la permitida. Ajuste el valor según la tarea: 1,500 es suficiente para diagnosticar un log; una tarea de clasificación requiere 100. Si las respuestas se cortan a mitad de una frase con stop_reason: "max_tokens", el límite es demasiado bajo; auméntelo deliberadamente en lugar de usar valores enormes por defecto.
Calcule antes de enviar. El input también tiene coste y los logs son voluminosos. La API tiene un endpoint de conteo gratuito (tiene sus propios límites de tasa, 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)Utilícelo 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 subestima los tokens de Claude entre un 15–20% en texto típico, y más en código.
Seleccione el modelo según la tarea, no por preferencia. A partir de 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 salida; Haiku 4.5 (claude-haiku-4-5) cuesta $1/$5 con un contexto de 200K; Sonnet 5 (claude-sonnet-5) se sitúa entre ambos a $3/$15, con un precio de lanzamiento de $2/$10 hasta el 31 de agosto de 2026. En términos concretos: un extracto de log de 2,000 tokens con una respuesta de 500 tokens cuesta aproximadamente $0.0225 en Opus y $0.0045 en Haiku. Comience con Opus mientras evalúa la calidad de la salida, luego pruebe los mismos prompts en Haiku; para transformaciones simples de alto volumen, el resultado suele ser indistinguible a un quinto del precio. Verifique los valores actuales en la página de precios antes de fijar estos datos en un presupuesto.
Use Batches para procesos que puedan esperar. La API de Batches procesa peticiones de forma asíncrona al 50% del precio estándar, y la mayoría de los batches finalizan en una hora. Resúmenes nocturnos, rellenos de datos (backfills) o clasificación masiva: cualquier tarea que no requiera respuesta inmediata debe ir en Batches.
Prompt caching para contextos repetidos. Si cada llamada reenvía el mismo prompt de sistema extenso o un manual de procedimientos, márquelo como cacheable:
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 onLa escritura en caché cuesta aproximadamente 1.25x el precio de entrada y la lectura de caché aproximadamente 0.1x, con un TTL de 5 minutos; por tanto, la segunda llamada dentro de ese intervalo ya compensa el coste de la primera. Hay dos inconvenientes. El prefijo en caché debe superar un mínimo por modelo —unos pocos miles de tokens en Opus—; un prompt de sistema corto no se cacheará. Además, si cache_read_input_tokens permanece en cero en llamadas idénticas, algo en su prefijo cambia en cada petición (un timestamp es el culpable habitual).
Considere qué se contabiliza como input. Los prompts de sistema, las definiciones de herramientas y —en conversaciones de varios turnos— todo el historial que se reenvía en cada turno se factura como tokens de entrada. Un bucle de chat que nunca recorta el historial aumenta su coste de forma cuadrática. Es importante entender la contabilidad completa antes de desarrollar cualquier sistema conversacional: cómo se calculan realmente el uso de tokens y la facturación de Claude.
Ejecución bajo systemd
La ventaja de usar archivos de entorno es obtener un timer que resuma los errores de ayer cada mañana.
# /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 lo que permite EnvironmentFile=: systemd lee el archivo con propiedad root y modo 600 antes de cambiar al usuario sin privilegios explain, por lo que el proceso recibe la variable mientras el usuario no puede leer el archivo de claves. El grupo systemd-journal otorga acceso a los logs. Pruebe con un systemctl start manual y lea journalctl -u log-digest.service; no espere hasta las 06:15 para detectar un error de escritura. Cuando este patrón sea demasiado complejo para un pipeline de shell, el mismo método de claves en archivos de entorno se puede aplicar directamente en workflows de n8n con Claude en el mismo servidor.
Modos de fallo y los strings que verá
401 con una clave válida. La excepción indica:
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; verifique que EnvironmentFile= apunte a la ruta correcta. Otras causas: comillas pegadas en el archivo env (ANTHROPIC_API_KEY="sk-ant-..." — systemd elimina las comillas, pero el . file de su wrapper de shell las mantiene en el valor si las usó de forma incorrecta), espacios en blanco al final, o una clave que revocó en la Console la semana pasada.
404 por un error tipográfico en el modelo. La versión más común de esto es 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 IDs de la generación actual son exactos tal como se escriben: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Cópielos desde la documentación de los modelos; nunca desde la memoria o de un tutorial antiguo.
429 rate_limit_error. El string del tipo de error es rate_limit_error y la respuesta incluye un header retry-after con los segundos de espera. El SDK ya ha realizado dos reintentos con backoff antes de que vea la excepción; los 429 persistentes significan que su tasa sostenida excede realmente su nivel — procese el trabajo por lotes o espacícelo, no reduzca el intervalo del bucle de reintento.
Imprime el objeto, no el texto. La salida se ve como [TextBlock(citations=None, text='...', type='text')]. Imprimió response.content en lugar de iterar los bloques y leer .text de aquellos donde block.type == "text". Todos los ejemplos de 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 un venv — nunca use --break-system-packages en un servidor crítico.
Respuestas truncadas. response.stop_reason == "max_tokens" significa que el modelo alcanzó su límite de salida durante el procesamiento. Funciona según lo diseñado; aumente el límite deliberadamente.
Una vez que su primera aplicación funcione, construir un agente de IA con Claude convierte esas mismas llamadas de API en un agente que utiliza herramientas.
FAQ
¿Cuánto cuesta probar la API de Claude?
Es muy poco para una herramienta de este tipo. A partir de julio de 2026, Opus 4.8 cuesta $5 por millón de tokens de entrada y $25 por millón de salida; por tanto, un diagnóstico de logs típico —un par de miles de tokens de entrada y unos cientos de salida— cuesta alrededor de dos centavos, y en 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; son los bucles sin límite y el max_tokens sin límite, razón por la cual ambos se configuran explícitamente en esta guía.
¿Existe un nivel gratuito para la API de Claude?
No hay un nivel gratuito continuo a partir de julio de 2026. La documentación de precios de Anthropic indica que los nuevos usuarios reciben una pequeña cantidad de créditos gratuitos para probar la API —una prueba de un solo uso, con el monto exacto mostrado en la Console al registrarse— tras lo cual se debe fondear la cuenta. Si su objetivo es un costo marginal de cero por solicitud en lugar de calidad de vanguardia, la alternativa es self-host an open-weight model with Ollama y pagar con RAM en lugar de tokens.
¿Cómo mantengo segura mi API key en un servidor?
Nunca en el código, nunca en git, nunca exportada desde .bashrc, nunca escrita en una shell donde el historial la guarde. Colóquela en un archivo propiedad de root con permisos 600, cárguela por proceso —un script wrapper para uso interactivo, EnvironmentFile= para systemd— y limite una clave por servidor o proyecto para que revocar una clave filtrada sea una cirugía y no una amputación. Si la clave llega a publicarse en un sitio de paste o en un commit de git, revóquela en la Console inmediatamente; borrar el commit no elimina la filtración.
¿Con qué modelo de Claude debería empezar?
Comience con claude-opus-4-8 mientras evalúa si los resultados son lo suficientemente buenos para construir sobre ellos; debe juzgar la idea con calidad total, y con un volumen de uso personal la diferencia de costo es de centavos. Una vez que el prompt esté definido, ejecute sus entradas reales en claude-haiku-4-5; para tareas de resumen, clasificación y triaje de logs, suele ser igual de bueno a una quinta parte del precio. Cambie a Haiku o Sonnet basándose en mediciones, no por defecto.