Tutorial da API Claude: seu primeiro app em um VPS
Crie um explicador de logs em Python no Ubuntu 24.04: proteja a chave da API Claude, use streaming, erros tipados e controle real de custos.
O que você vai criar
Uma ferramenta de linha de comandos num VPS Ubuntu 24.04 recém-criado. Você envia uma mensagem de erro ou um trecho de log para a ferramenta e recebe um diagnóstico em linguagem simples: journalctl -u nginx -n 50 | explain. O programa tem cerca de 60 linhas de Python. Ele cobre tudo o que uma aplicação real da API Claude precisa: uma chave armazenada corretamente, um virtualenv, as estruturas de resposta do SDK, streaming, a cadeia de exceções tipadas e uma unidade systemd para executar sem intervenção.
Escolhi este projeto de propósito. Na maioria dos tutoriais de "primeira aplicação de API", você cria um chatbot que nunca mais abre. Um explicador de logs é útil num servidor desde o primeiro dia. Ele também obriga você a lidar com os dois erros que os iniciantes realmente cometem: interpretar corretamente o objeto de resposta e controlar os gastos. A API cobra por token, sem limite além dos que você definir. Por isso, o controle de custos é um requisito de projeto, não algo a considerar depois. É a mesma disciplina necessária quando você avançar para executar o Claude Code neste mesmo VPS no tmux.
Obtenha uma chave de API no Console
O acesso à API é gerido no Anthropic Console, em platform.claude.com. Crie uma conta e depois crie uma chave em Settings → API Keys (a documentação direciona diretamente para platform.claude.com/settings/keys). A chave é apresentada uma única vez, começa com sk-ant- e não pode ser recuperada depois. Copie-a imediatamente ou elimine-a e emita outra.
Quanto aos custos: em julho de 2026, não existe um nível gratuito contínuo para a API. A documentação de preços da Anthropic informa que novos utilizadores recebem uma pequena quantidade de créditos gratuitos para testes. O valor exato é o que o Console apresentar durante o registo. Quando os créditos terminarem, terá de adicionar fundos à conta para que os pedidos sejam processados. Isto é separado de uma subscrição claude.ai. Um plano Pro ou Max não inclui créditos de API. Uma chave de API também não dá acesso à aplicação de chat. Se estiver a comparar uma subscrição com a API, essa decisão é um tema separado: qual é o plano Claude de que realmente precisa.
Crie a chave com âmbito limitado a um projeto ou servidor. Quando uma chave vaza, e isso acontecerá com tempo suficiente, deve poder revogá-la sem interromper tudo o que possui.
Mantenha a chave fora do .bashrc
O impulso habitual é export ANTHROPIC_API_KEY=sk-ant-... em ~/.bashrc. Não faça isso. Há três problemas distintos:
- Todos os processos a herdam. Uma variável de ambiente exportada na sua shell de login é propagada para tudo o que iniciar: a aplicação web, o sistema de relatórios de falhas que despeja convenientemente o ambiente num relatório de erro e a página
phpinfo()que alguém deixou ativada. A superfície de exposição da chave passa a ser "tudo o que este utilizador executar". - Ao introduzi-la, ela vai para
~/.bash_history. Execute a exportação manualmente uma vez e a chave ficará num ficheiro de texto simples para sempre, além de ser incluída em todas as cópias de segurança do seu diretório pessoal. - Ela não estará disponível quando o systemd precisar dela. Os serviços não leem o seu
.bashrc, por isso o padrão falha precisamente quando promove o script a uma unidade, normalmente como um 401 misterioso às 6 da manhã.
O padrão correto num servidor é um ficheiro de ambiente dedicado com permissões 600, carregado apenas pelo processo que precisa dele:
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 a partir de um printf, em vez de um editor, se quiser manter a chave fora dos ficheiros temporários do editor. Em qualquer caso, confirme com ls -l /etc/claude-explain.env que ele lê -rw------- e que pertence a root. As shells interativas recebem a chave por invocação através de um wrapper (abaixo), e o systemd recebe-a através de EnvironmentFile=. O root lê o ficheiro antes de remover privilégios, para que o utilizador do serviço nunca precise de ter acesso de leitura a ele. A chave nunca aparece no código, no git, na saída de ps nem no histórico da shell.
Instalar o SDK num venv
O Ubuntu 24.04 disponibiliza o Python 3.12 com a aplicação da PEP 668. Por isso, executar pip install anthropic diretamente no interpretador do sistema falha com error: externally-managed-environment. Esse erro indica que o sistema operativo está a funcionar conforme previsto. Use um virtualenv:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicNão é necessária uma ativação manual num servidor: chamar /opt/explain/venv/bin/python diretamente utiliza sempre os pacotes do venv.
Primeira chamada e leitura correta da resposta
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)Duas coisas nessas doze linhas explicam a maior parte do modelo mental da API. Primeiro, anthropic.Anthropic() sem argumentos lê a chave a partir do ambiente; nunca a passe como literal de string. Segundo, response.content é uma lista de blocos de conteúdo, não uma string. Se a imprimir diretamente, obterá o resultado clássico de quem está a começar:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Isto não é um erro; é a representação (repr) do objeto. As respostas podem conter vários tipos de blocos (texto, chamadas de ferramentas e raciocínio). Por isso, percorra a lista e verifique block.type == "text" antes de aceder a .text. Adicione esse ciclo desde o primeiro dia para evitar toda uma classe de confusões do tipo "imprime lixo".
Use o ID exato do modelo claude-opus-4-8. Os IDs atuais não incluem datas. Não siga o hábito, ou a indicação de um artigo antigo, de acrescentar um sufixo de data; isso produz um erro 404, explicado abaixo.
A ferramenta na prática: explicação
Este é o programa completo: recebe dados de stdin, produz o diagnóstico em fluxo e trata os erros:
#!/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())Guarde-o como /opt/explain/explain.py e adicione um wrapper que carregue a chave para utilização interativa:
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(O wrapper precisa de ser executado através de sudo, ou o ficheiro de ambiente precisa de ter um grupo ao qual o seu utilizador administrativo pertença. Escolha uma destas opções deliberadamente, em vez de alterar as permissões do ficheiro para 644.)
Por que usar streaming. client.messages.stream imprime os tokens à medida que chegam, em vez de permanecer silencioso durante toda a geração. Também evita timeouts HTTP em respostas longas. Por esse mesmo motivo, o SDK recusa valores muito grandes de max_tokens em chamadas sem streaming. Se precisar do objeto completo depois, chame stream.get_final_message() dentro do bloco with.
Por que esta ordem das exceções. O SDK gera exceções tipadas, da mais específica para a mais genérica: RateLimitError corresponde a um erro 429 e inclui um cabeçalho retry-after que indica quanto tempo deve esperar; APIStatusError abrange as outras respostas que não são 2xx (consulte e.status_code >= 500 para problemas no servidor); APIConnectionError significa que o pedido não recebeu resposta. Antes de criar um ciclo de novas tentativas, lembre-se de que o SDK já repete automaticamente os pedidos com erros 429 e 5xx, duas vezes por predefinição, com recuo exponencial (max_retries no cliente). Quando except for executado, as novas tentativas já terão sido esgotadas. Por isso, numa CLI, a opção correta é comunicar o erro e terminar, não aguardar e sobrecarregar o serviço.
Controlo de custos
Esta questão merece uma secção própria porque a API não tem um limite mensal incorporado além do que configurar, e cada erro aqui aumenta o custo silenciosamente.
max_tokens é o limite de custo por chamada. Os tokens de saída são a componente mais cara: no Opus 4.8, custam cinco vezes mais do que os tokens de entrada, e max_tokens é o limite máximo que o modelo pode produzir. Um prompt que entre em ciclo não pode gerar mais tokens de saída do que o limite definido. Ajuste-o à tarefa: 1,500 são suficientes para diagnosticar um log; uma tarefa de classificação precisa de 100. Se as respostas terminarem a meio da frase com stop_reason: "max_tokens", o limite ficou demasiado baixo. Aumente-o conscientemente em vez de usar um valor excessivamente grande por defeito.
Conte os tokens antes de enviar. Os tokens de entrada também têm custo, e os logs podem ser grandes. A API tem um endpoint de contagem gratuito (com limites de utilização próprios, separados da criação de mensagens):
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Use-o para evitar enviar acidentalmente um log de 2 GB através da ferramenta. Não use tiktoken para isto. Esse é o tokenizador da OpenAI e subestima os tokens do Claude em cerca de 15–20% em texto normal e ainda mais em código.
Escolha o modelo por tarefa, não por preferência. Em julho de 2026, o Opus 4.8 (claude-opus-4-8) custa $5 por milhão de tokens de entrada e $25 por milhão de tokens de saída; o Haiku 4.5 (claude-haiku-4-5) custa $1/$5 e tem um contexto de 200K; o Sonnet 5 (claude-sonnet-5) fica entre ambos, a $3/$15, com preços introdutórios de $2/$10 até 31 de agosto de 2026. Concretamente, um excerto de log com 2,000 tokens e uma resposta com 500 tokens custa cerca de $0.0225 no Opus e $0.0045 no Haiku. Comece pelo Opus enquanto avalia a qualidade das respostas. Depois, experimente os mesmos prompts no Haiku. Para transformações simples e de grande volume, a diferença é muitas vezes impercetível por um quinto do preço. Confirme os valores atuais na página de preços antes de os fixar num orçamento.
Use Batches para tudo o que possa esperar. A Batches API processa pedidos de forma assíncrona a 50% dos preços normais, e a maioria dos batches termina numa hora. Resumos noturnos, backfills, classificações em massa e qualquer tarefa que não tenha uma pessoa à espera devem usar este mecanismo.
Use o cache de prompts para contexto repetido. Se cada chamada reenviar o mesmo prompt de sistema grande ou o mesmo runbook, marque-o para cache:
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 onAs gravações no cache custam cerca de 1.25x o preço dos tokens de entrada, e as leituras do cache cerca de 0.1x, com um TTL de 5 minutos. Assim, a segunda chamada dentro desse período já compensa o custo da primeira. Existem duas limitações. O prefixo em cache tem de ultrapassar um mínimo por modelo, de alguns milhares de tokens no Opus. Por isso, um prompt de sistema curto pode não ser colocado em cache. Além disso, se cache_read_input_tokens permanecer a zero em chamadas idênticas, algo no prefixo está a mudar em cada pedido. Um timestamp é a causa mais comum.
Lembre-se do que conta como entrada. Os prompts de sistema, as definições de ferramentas e, nas conversas com várias interações, todo o histórico reenviado em cada turno são contabilizados como tokens de entrada. Um ciclo de chat que nunca reduz o histórico aumenta o custo de forma quadrática. É importante compreender a contabilidade completa antes de criar uma aplicação conversacional: como o uso de tokens e a faturação do Claude são calculados.
Execute com systemd
A vantagem de manter a disciplina com ficheiros de ambiente: um temporizador que resume todos os erros do dia anterior todas as manhãs.
# /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 o que EnvironmentFile= permite fazer: o systemd lê o ficheiro pertencente a root, com modo 600, antes de mudar para o utilizador sem privilégios explain. Assim, o processo recebe a variável, mas o utilizador não consegue ler o ficheiro de chave. O grupo systemd-journal concede acesso aos logs. Teste com um systemctl start manual e leia journalctl -u log-digest.service. Não espere pelas 06:15 para descobrir um erro de digitação. Quando este padrão deixar de ser suficiente para um pipeline de shell, a mesma abordagem de guardar a chave num ficheiro de ambiente pode ser usada diretamente em fluxos de trabalho do n8n com Claude no mesmo servidor.
Modos de falha e mensagens apresentadas
401 com uma key válida. A exceção é:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Se a key funciona na sua shell, mas o serviço devolve 401, o serviço nunca a recebeu. Lembre-se de que o systemd não lê .bashrc. Verifique se EnvironmentFile= aponta para o caminho correto. Outras causas possíveis são aspas coladas no ficheiro de ambiente (ANTHROPIC_API_KEY="sk-ant-..."; o systemd remove as aspas, mas o . file do seu wrapper da shell mantém-nas no valor se as aspas forem usadas de forma incorreta), espaços em branco no fim da linha ou uma key que revogou na Console na semana passada.
404 devido a um erro de digitação no modelo. A versão mais comum deste problema consiste em acrescentar um sufixo de data a um ID de modelo atual:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}Os IDs da geração atual devem ser usados exatamente como estão escritos: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Copie-os da documentação dos modelos. Nunca os escreva de memória nem os copie de um tutorial antigo.
429 rate_limit_error. A string do tipo de erro é rate_limit_error e a resposta inclui um cabeçalho retry-after com o número de segundos a aguardar. O SDK já tentou novamente duas vezes, com backoff, antes de apresentar a exceção. Por isso, 429 persistentes significam que a sua taxa sustentada excede efetivamente o limite do seu tier. Distribua o trabalho por lotes ou ao longo do tempo. Não reduza o intervalo entre tentativas.
O objeto é apresentado em vez do texto. A saída é semelhante a [TextBlock(citations=None, text='...', type='text')]. Imprimiu response.content em vez de iterar pelos blocos e ler .text nos blocos em que block.type == "text". Todos os exemplos do SDK acima fazem isto corretamente. Copie o loop.
error: externally-managed-environment. Executou pip install no Python do sistema do Ubuntu 24.04. Use o venv. Nunca use --break-system-packages num servidor importante.
Respostas truncadas. response.stop_reason == "max_tokens" significa que o modelo atingiu o limite de saída a meio da resposta. Este comportamento é esperado. Aumente o limite de forma deliberada.
Depois de a primeira aplicação funcionar, criar um agente de IA com Claude transforma essas mesmas chamadas à API num agente que utiliza ferramentas.
FAQ
Quanto custa experimentar a Claude API?
Muito pouco para uma ferramenta deste tipo. Em julho de 2026, Opus 4.8 custa $5 por milhão de tokens de entrada e $25 por milhão de tokens de saída. Um diagnóstico de logs típico, com alguns milhares de tokens de entrada e algumas centenas de saída, custa cerca de dois cêntimos. Com Haiku 4.5 ($1/$5), fica abaixo de meio cêntimo. Um mês de resumos diários custa menos do que um café. O risco não está no preço por chamada, mas em loops sem limite e em max_tokens sem limite. Por isso, ambos são definidos explicitamente neste guia.
Existe um nível gratuito para a Claude API?
Não existe um nível gratuito contínuo em julho de 2026. A documentação de preços da Anthropic informa que novos utilizadores recebem uma pequena quantidade de créditos gratuitos para testar a API, num teste único. O valor exato é apresentado na Console durante o registo. Depois disso, é necessário financiar a conta. Se o objetivo for custo marginal zero por pedido, em vez de qualidade de ponta, a alternativa é alojar localmente um modelo de pesos abertos com Ollama e pagar em RAM em vez de tokens.
Como mantenho a minha chave de API segura num servidor?
Nunca a coloque no código, no git, nem a exporte de .bashrc. Nunca a introduza numa shell cuja historial a possa guardar. Coloque-a num ficheiro propriedade de root, com permissões 600, e carregue-a por processo. Para utilização interativa, use um script wrapper. Para systemd, use EnvironmentFile=. Defina uma chave por servidor ou projeto, para que a revogação de uma chave exposta seja uma operação precisa, não uma interrupção total. Se a chave alguma vez aparecer num site de partilha de texto ou num commit do git, revogue-a imediatamente na Console. Apagar o commit não elimina a exposição da chave.
Com que modelo Claude devo começar?
Comece com claude-opus-4-8 enquanto avalia se os resultados têm qualidade suficiente para servirem de base. Assim, pode avaliar a ideia com a qualidade máxima. Em volumes de hobby, a diferença de custo é de apenas alguns cêntimos. Quando o prompt estiver definido, volte a executar as entradas reais em claude-haiku-4-5. Para sumarização, classificação e triagem de logs, este modelo é frequentemente tão bom por um quinto do preço. Mude para Haiku ou Sonnet com base em medições, não por predefinição.