Hướng dẫn Claude API: app đầu tiên trên VPS
Tạo Claude API key, bảo vệ trên Ubuntu 24.04 và triển khai tool Python khoảng 60 dòng để giải thích log, streaming, xử lý lỗi typed và kiểm soát chi phí.
Bạn đang xây dựng gì
Một công cụ dòng lệnh trên VPS Ubuntu 24.04 mới cài. Bạn truyền thông báo lỗi hoặc một đoạn log vào công cụ qua pipe, rồi nhận lại phần chẩn đoán bằng tiếng Anh đơn giản: journalctl -u nginx -n 50 | explain. Công cụ này chỉ khoảng 60 dòng Python. Nó giúp bạn thực hành mọi thành phần mà một ứng dụng Claude API thực tế cần: lưu key đúng cách, dùng virtualenv, xử lý các dạng response của SDK, streaming, chuỗi exception có kiểu, và một unit systemd để công cụ tự chạy mà không cần bạn can thiệp.
Tôi cố ý chọn dự án này. Hầu hết tutorial về “ứng dụng API đầu tiên” đều hướng dẫn bạn xây chatbot mà sau đó bạn sẽ không bao giờ mở lại. Công cụ giải thích log có ích ngay trên server từ ngày đầu tiên. Nó cũng buộc bạn xử lý đúng 2 vấn đề mà người mới thường làm sai: đọc đúng response object và kiểm soát chi phí. API tính phí theo token, không có giới hạn nào ngoài các giới hạn bạn tự đặt. Vì vậy, kiểm soát chi phí là một yêu cầu thiết kế ngay từ đầu, không phải việc bổ sung sau này. Đây cũng là nguyên tắc cần thiết khi bạn chuyển sang chạy Claude Code trên chính VPS này trong tmux.
Lấy API key từ Console
Quyền truy cập API được quản lý trong Anthropic Console tại platform.claude.com. Hãy đăng ký, sau đó tạo key trong Settings → API Keys (tài liệu liên kết trực tiếp đến platform.claude.com/settings/keys). Key chỉ hiển thị một lần, bắt đầu bằng sk-ant- và không thể lấy lại. Hãy sao chép ngay hoặc xóa rồi tạo lại.
Về chi phí: tính đến tháng 7 năm 2026, API không có gói miễn phí liên tục. Tài liệu về giá của Anthropic cho biết người dùng mới nhận được một khoản free credit nhỏ để thử nghiệm. Số tiền chính xác là số tiền Console hiển thị khi bạn đăng ký. Khi khoản credit này hết, bạn phải nạp tiền vào tài khoản thì request mới thành công. Khoản này tách biệt với gói đăng ký claude.ai. Gói Pro hoặc Max không bao gồm API credit, và API key không cấp quyền dùng ứng dụng chat. Nếu bạn đang cân nhắc giữa gói đăng ký và API, đó là một chủ đề riêng: bạn thực sự cần gói Claude nào.
Hãy tạo key chỉ cho một project hoặc server. Khi một key bị lộ — và nếu đủ thời gian thì sẽ có key bị lộ — bạn cần thu hồi key đó mà không làm gián đoạn mọi tài nguyên khác bạn sở hữu.
Không để key trong .bashrc
Phản xạ đầu tiên là export ANTHROPIC_API_KEY=sk-ant-... trong ~/.bashrc. Đừng làm vậy. Có 3 vấn đề riêng biệt:
- Mọi process đều kế thừa nó. Biến môi trường được export trong login shell sẽ lan truyền đến mọi thứ bạn khởi chạy: web app, crash reporter vốn có thể ghi toàn bộ environment vào bug report, và trang
phpinfo()mà ai đó đã bật. Phạm vi lộ key trở thành “mọi thứ user này từng chạy”. - Gõ lệnh này sẽ ghi nó vào
~/.bash_history. Chạy lệnh export thủ công một lần khiến key nằm trong một file plaintext vĩnh viễn, đồng thời được sync vào mọi bản backup của home directory. - systemd không có biến này khi cần. Service không đọc
.bashrccủa bạn, nên cách này sẽ thất bại đúng lúc bạn chuyển script thành unit, thường biểu hiện bằng lỗi 401 khó hiểu lúc 6 giờ sáng.
Cách đúng trên server là dùng một environment file riêng với quyền 600, chỉ được process cần key nạp vào:
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/nullDùng tee từ printf thay vì editor nếu muốn tránh ghi key vào editor swap file. Dù dùng cách nào, hãy kiểm tra bằng ls -l /etc/claude-explain.env để xác nhận file đọc được -rw------- và thuộc sở hữu của root. Interactive shell nhận key cho từng lần chạy thông qua wrapper (bên dưới), còn systemd nhận key qua EnvironmentFile=. root đọc file trước khi hạ quyền, nên service user không cần quyền đọc file này. Key không bao giờ xuất hiện trong code, git, output của ps hoặc shell history.
Cài SDK trong venv
Ubuntu 24.04 phát hành cùng Python 3.12 và bật cơ chế thực thi PEP 668, nên chạy pip install anthropic trực tiếp với trình thông dịch của hệ thống sẽ thất bại với error: externally-managed-environment. Đây là hành vi đúng của OS. Hãy dùng virtualenv:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicTrên server không cần thực hiện bước activate: gọi trực tiếp /opt/explain/venv/bin/python luôn sử dụng các package trong venv.
Lần gọi đầu tiên và cách đọc response chính xác
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)Có 2 điểm trong 12 dòng này thể hiện phần lớn mô hình hoạt động của API. Thứ nhất, anthropic.Anthropic() không có đối số sẽ đọc key từ environment; không bao giờ truyền key dưới dạng string literal. Thứ hai, response.content là danh sách các content block, không phải một string. In trực tiếp biến này sẽ cho ra kết quả kinh điển với người dùng lần đầu:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Đó không phải bug; đây là repr của object. Response có thể chứa nhiều loại block (text, tool call, thinking), vì vậy bạn lặp qua danh sách và kiểm tra block.type == "text" trước khi truy cập .text. Thêm vòng lặp này ngay từ ngày đầu sẽ loại bỏ hoàn toàn một nhóm lỗi kiểu “nó in ra dữ liệu rác”.
Dùng chính xác model ID claude-opus-4-8. ID của thế hệ hiện tại không chứa ngày tháng. Đừng theo thói quen hoặc theo bài blog cũ mà nối thêm hậu tố ngày tháng; cách đó tạo ra lỗi 404, được giải thích bên dưới.
Công cụ thực tế: giải thích
Đây là toàn bộ chương trình: nhận dữ liệu từ stdin, xuất chẩn đoán theo luồng và xử lý lỗi:
#!/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())Lưu chương trình này thành /opt/explain/explain.py, sau đó thêm một wrapper để nạp key khi sử dụng tương tác:
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(Wrapper phải chạy qua sudo, hoặc env file phải thuộc một group mà admin user của bạn là thành viên. Hãy chủ động chọn một trong hai cách, thay vì nới quyền file thành 644.)
Vì sao dùng streaming. client.messages.stream in các token ngay khi chúng đến, thay vì im lặng trong toàn bộ thời gian generation. Cách này cũng tránh HTTP timeout khi output dài. SDK sẽ thực sự từ chối các giá trị max_tokens rất lớn trong các call không streaming vì chính lý do đó. Nếu cần object đã được ghép hoàn chỉnh sau đó, hãy gọi stream.get_final_message() bên trong block with.
Vì sao sắp xếp thứ tự exception như vậy. SDK phát sinh các exception có kiểu cụ thể, theo thứ tự từ cụ thể nhất: RateLimitError là lỗi 429 và chứa header retry-after cho biết cần chờ bao lâu; APIStatusError bao phủ các response non-2xx khác (kiểm tra e.status_code >= 500 để phát hiện sự cố phía server); APIConnectionError có nghĩa là request hoàn toàn không nhận được response. Trước khi tự xây dựng retry loop, hãy nhớ rằng SDK đã tự retry các lỗi 429 và 5xx, mặc định 2 lần với exponential backoff (max_retries trên client). Khi except của bạn chạy, các lần retry đã dùng hết. Vì vậy, cách đúng trong CLI là báo lỗi rồi thoát, không sleep và gửi request liên tục.
Kiểm soát chi phí
Phần này cần được tách riêng vì API không có giới hạn hàng tháng tích hợp sẵn ngoài mức bạn tự cấu hình, và mọi lỗi ở đây sẽ âm thầm làm chi phí tăng lên.
max_tokens là giới hạn chi phí cho mỗi lần gọi. Output tokens là phần đắt hơn, với Opus 4.8 có giá cao gấp 5 lần input, còn max_tokens là giới hạn cứng về số token mà model được phép tạo ra. Prompt chạy mất kiểm soát không thể tạo ra nhiều output hơn mức bạn cho phép. Đặt giới hạn theo công việc: 1,500 là đủ để chẩn đoán log; tác vụ phân loại chỉ cần 100. Nếu phản hồi dừng giữa câu với stop_reason: "max_tokens", giới hạn này quá thấp. Hãy chủ động tăng nó thay vì mặc định đặt một giá trị rất lớn.
Đếm trước khi gửi. Input cũng tính phí và log thường có dung lượng lớn. API có endpoint đếm token miễn phí (endpoint này có rate limit riêng, tách biệt với việc tạo message):
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Dùng endpoint này để tránh vô tình truyền một log 2 GB qua tool. Không dùng tiktoken cho việc này. Đây là tokenizer của OpenAI, thường đếm thiếu khoảng 15–20% số Claude tokens trên văn bản thông thường và thiếu nhiều hơn với code.
Chọn model theo tác vụ, không theo thói quen. Tính đến tháng 7 năm 2026, Opus 4.8 (claude-opus-4-8) có giá $5 cho mỗi một triệu input tokens và $25 cho mỗi một triệu output tokens; Haiku 4.5 (claude-haiku-4-5) có giá $1/$5 với context 200K; Sonnet 5 (claude-sonnet-5) nằm ở mức giữa, với giá $3/$15 và mức giá giới thiệu $2/$10 đến hết ngày 31 tháng 8 năm 2026. Cụ thể, một đoạn log 2,000 token kèm câu trả lời 500 token có chi phí khoảng $0.0225 trên Opus và $0.0045 trên Haiku. Bắt đầu với Opus khi bạn đánh giá chất lượng output, sau đó thử cùng các prompt trên Haiku. Với các phép biến đổi đơn giản, khối lượng lớn, kết quả thường gần như không khác biệt nhưng chi phí chỉ bằng một phần năm. Kiểm tra số liệu hiện tại trên trang pricing trước khi hard-code các giá trị này vào ngân sách.
Dùng Batches cho mọi việc có thể chờ. Batches API xử lý request bất đồng bộ với mức giá bằng 50% giá chuẩn, và hầu hết batch hoàn tất trong vòng một giờ. Digest chạy hàng đêm, backfill, phân loại hàng loạt và mọi tác vụ không có người chờ trực tiếp đều nên dùng cách này.
Dùng prompt caching cho context lặp lại. Nếu mọi lần gọi đều gửi lại cùng một system prompt lớn hoặc runbook, hãy đánh dấu phần đó để 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 onChi phí ghi cache khoảng 1.25 lần giá input, còn đọc cache khoảng 0.1 lần giá input, với TTL 5 phút. Vì vậy, lần gọi thứ hai trong khoảng thời gian này đã bù được chi phí của lần gọi đầu tiên. Có 2 điểm cần lưu ý. Prefix được cache phải vượt qua mức tối thiểu riêng của từng model, thường là vài nghìn token trên Opus. Vì vậy, system prompt ngắn sẽ âm thầm không được cache. Ngoài ra, nếu cache_read_input_tokens vẫn bằng 0 trong các lần gọi giống hệt nhau, có phần nào đó trong prefix đang thay đổi ở mỗi request (timestamp là nguyên nhân thường gặp).
Nhớ những gì được tính là input. System prompt, định nghĩa tool và toàn bộ history mà bạn gửi lại trong mỗi turn của cuộc hội thoại nhiều lượt đều được tính là input tokens. Chat loop không bao giờ rút gọn history sẽ làm chi phí tăng theo cấp số nhân. Bạn nên hiểu rõ toàn bộ cách tính này trước khi xây dựng bất kỳ hệ thống hội thoại nào: cách tính thực tế lượng Claude tokens được sử dụng và chi phí phát sinh.
Chạy dưới systemd
Lợi ích của việc quản lý environment file đúng cách là một timer tóm tắt lỗi của ngày hôm trước vào mỗi buổi sáng.
# /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 nowLưu ý lợi ích của EnvironmentFile=: systemd đọc file thuộc sở hữu của root, có mode-600, trước khi chuyển sang user không có đặc quyền explain. Vì vậy, process nhận được biến này, còn user không thể đọc key file. Group systemd-journal cấp quyền truy cập log. Kiểm tra bằng systemctl start thủ công và đọc journalctl -u log-digest.service, không chờ đến 06:15 mới phát hiện lỗi đánh máy. Khi pattern này vượt quá khả năng của một shell pipeline, cách dùng key trong env file có thể chuyển thẳng sang workflow n8n dùng Claude trên cùng máy.
Các chế độ lỗi và chuỗi bạn sẽ thấy
401 với key đang hoạt động. Exception có nội dung:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Nếu key hoạt động trong shell nhưng service trả về 401, service chưa bao giờ nhận được key đó. Lưu ý rằng systemd không đọc .bashrc; kiểm tra EnvironmentFile= trỏ đến đúng path. Các nguyên nhân khác gồm: dấu ngoặc kép được dán vào env file (ANTHROPIC_API_KEY="sk-ant-..."; systemd loại bỏ dấu ngoặc kép, nhưng . file của shell wrapper giữ chúng trong value nếu bạn đặt dấu ngoặc không đúng cách), khoảng trắng ở cuối dòng hoặc key đã bị bạn thu hồi trong Console tuần trước.
404 do gõ sai model. Trường hợp phổ biến nhất là thêm hậu tố ngày vào model ID hiện tại:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}ID của các model thế hệ hiện tại phải chính xác như được ghi trong tài liệu: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Hãy copy chúng từ tài liệu models, không nhập theo trí nhớ hoặc từ tutorial cũ.
429 rate_limit_error. Chuỗi error type là rate_limit_error và response có header retry-after chứa số giây cần chờ. SDK đã tự retry 2 lần với backoff trước khi bạn thấy exception. Vì vậy, lỗi 429 liên tục nghĩa là rate của bạn thực sự vượt tier hiện tại. Hãy batch công việc hoặc phân tán các request, không rút ngắn retry loop.
In ra object thay vì text. Output có dạng [TextBlock(citations=None, text='...', type='text')]. Bạn đã in response.content thay vì lặp qua các block và đọc .text từ những block có block.type == "text". Mọi ví dụ SDK ở trên đều xử lý đúng cách; hãy copy loop đó.
error: externally-managed-environment. Bạn đã chạy pip install bằng system Python của Ubuntu 24.04. Hãy dùng venv, không bao giờ chạy --break-system-packages trên server quan trọng.
Câu trả lời bị cắt ngắn. response.stop_reason == "max_tokens" nghĩa là model đã chạm giới hạn output khi đang tạo câu trả lời. Đây là hành vi đúng theo thiết kế; hãy tăng giới hạn một cách có chủ đích.
Sau khi app đầu tiên hoạt động, xây dựng AI agent bằng Claude sẽ biến các API call tương tự thành một agent có thể sử dụng tools.
FAQ
Thử Claude API tốn bao nhiêu?
Thực sự rất ít đối với một công cụ như thế này. Tính đến tháng 7 năm 2026, Opus 4.8 có giá $5 cho mỗi triệu input token và $25 cho mỗi triệu output token. Vì vậy, một lần chẩn đoán log thông thường, với vài nghìn token đầu vào và vài trăm token đầu ra, tốn khoảng hai xu. Với Haiku 4.5 ($1/$5), chi phí này chưa đến nửa xu. Dùng tính năng tổng hợp hằng ngày trong một tháng còn rẻ hơn một ly cà phê. Rủi ro không nằm ở giá mỗi lần gọi, mà ở các vòng lặp không giới hạn và max_tokens không giới hạn. Vì vậy, hướng dẫn này đặt rõ cả hai giới hạn.
Claude API có free tier không?
Tính đến tháng 7 năm 2026, không có free tier liên tục. Tài liệu giá của Anthropic cho biết người dùng mới nhận được một khoản free credits nhỏ để thử API. Đây là gói dùng thử một lần; số tiền chính xác hiển thị trong Console khi đăng ký. Sau đó, bạn phải nạp tiền vào tài khoản. Nếu mục tiêu của bạn là không phát sinh chi phí cho mỗi request thay vì đạt chất lượng cao nhất, lựa chọn khác là tự host model open-weight bằng Ollama và trả chi phí bằng RAM thay vì token.
Làm cách nào để bảo vệ API key trên server?
Không bao giờ đặt key trong code, trong git hoặc export key từ .bashrc. Cũng không nhập key vào shell nếu shell history sẽ lưu lại key. Đặt key trong một file do root sở hữu với quyền 600. Nạp key theo từng process: dùng wrapper script cho thao tác tương tác và EnvironmentFile= cho systemd. Giới hạn mỗi server hoặc project chỉ dùng một key, để khi key bị lộ, bạn có thể revoke key thay vì phải xử lý phạm vi ảnh hưởng lớn. Nếu key từng xuất hiện trên paste site hoặc trong git commit, hãy revoke key ngay trong Console. Xóa commit không làm key đã bị lộ trở nên an toàn.
Nên bắt đầu với Claude model nào?
Hãy bắt đầu với claude-opus-4-8 trong giai đoạn đánh giá chất lượng output và xem có đủ tốt để xây dựng tiếp hay không. Bạn cần đánh giá ý tưởng với chất lượng đầy đủ, còn ở mức sử dụng cá nhân thì chênh lệch chi phí chỉ vài xu. Khi prompt đã ổn định, hãy chạy lại input thực tế trên claude-haiku-4-5. Với các tác vụ tóm tắt, phân loại và phân tích sơ bộ log, model này thường đạt chất lượng tương đương với giá chỉ bằng một phần năm. Hãy chuyển sang Haiku hoặc Sonnet dựa trên số liệu đo được, không chuyển theo mặc định.