Hướng dẫn Claude API: ứng dụng đầu tiên trên VPS
Lấy API key của Claude, cất an toàn trên Ubuntu 24.04, rồi viết công cụ Python đọc log có streaming, xử lý exception có kiểu và kiểm soát chi phí thật sự.
Bạn sẽ dựng cái gì
Một công cụ dòng lệnh chạy trên VPS Ubuntu 24.04 còn mới tinh. Bạn đẩy một thông báo lỗi hoặc một đoạn log vào nó và nhận lại lời chẩn đoán bằng câu chữ dễ hiểu: journalctl -u nginx -n 50 | explain. Chương trình chỉ khoảng sáu mươi dòng Python, nhưng nó đụng tới đủ mọi thứ mà một ứng dụng Claude API thật sự cần: một key được cất đúng chỗ, một virtualenv, cấu trúc response của SDK, streaming, chuỗi exception có kiểu, và một unit systemd để nó tự chạy mà không cần bạn ngồi canh.
Tôi chọn dự án này có chủ đích. Phần lớn hướng dẫn "ứng dụng API đầu tiên" bắt bạn dựng một chatbot mà bạn sẽ không bao giờ mở lại. Một công cụ giải thích log thì có ích trên máy chủ ngay từ ngày đầu, và nó ép bạn đi qua đúng hai chỗ người mới hay làm sai: đọc đúng object response, và kiểm soát chi tiêu. API tính tiền theo token và không có trần nào ngoài trần bạn tự đặt. Vì vậy ở đây kiểm soát chi phí là một phần của thiết kế, không phải chuyện tính sau. Đó cũng là kỷ luật cần có khi bạn tiến lên chạy Claude Code trong tmux trên chính chiếc VPS này.
Lấy API key trong Console
Quyền truy cập API được quản lý trong Anthropic Console tại platform.claude.com. Bạn đăng ký tài khoản, rồi tạo key trong mục Settings → API Keys (tài liệu trỏ thẳng tới platform.claude.com/settings/keys). Key chỉ hiện đúng một lần, bắt đầu bằng sk-ant-, và không lấy lại được. Hãy chép nó ngay, hoặc xóa đi rồi tạo key mới.
Về tiền: tính đến tháng 7 năm 2026, API không có gói miễn phí dùng lâu dài. Tài liệu giá của Anthropic nói người dùng mới nhận được một khoản credit nhỏ để thử; số tiền chính xác là con số Console hiển thị lúc bạn đăng ký. Hết khoản đó, bạn phải nạp tiền vào tài khoản thì request mới chạy được. Chuyện này tách biệt với gói thuê bao claude.ai: gói Pro hay Max không kèm credit API, và một API key cũng không cho bạn dùng app chat. Nếu bạn đang cân nhắc giữa thuê bao và API, đó là một chủ đề riêng: chọn gói Claude nào cho đúng nhu cầu.
Hãy tạo key riêng cho từng dự án hoặc từng máy chủ. Sớm muộn gì cũng sẽ có một key bị lộ. Lúc đó bạn muốn thu hồi đúng key đó mà không làm gãy mọi thứ khác bạn đang chạy.
Đừng để key trong .bashrc
Phản xạ đầu tiên của nhiều người là thêm export ANTHROPIC_API_KEY=sk-ant-... vào ~/.bashrc. Đừng làm vậy. Có ba vấn đề tách biệt nhau:
- Mọi tiến trình đều kế thừa nó. Một biến môi trường được export trong shell đăng nhập sẽ lan sang mọi thứ bạn khởi chạy: ứng dụng web, cái trình báo lỗi tử tế đến mức ném cả environment vào báo cáo bug, hay trang
phpinfo()ai đó quên tắt. Phạm vi phơi nhiễm của key trở thành "mọi thứ mà user này từng chạy". - Gõ tay một lần là key nằm lại trong
~/.bash_history. Chạy lệnh export bằng tay đúng một lần, key của bạn nằm trong một file văn bản thuần, vĩnh viễn, và theo mọi bản sao lưu thư mục home đi khắp nơi. - Nó không có mặt khi systemd cần. Service không đọc
.bashrccủa bạn, nên cách làm này hỏng đúng vào lúc bạn nâng script lên thành một unit. Thường là một lỗi 401 khó hiểu vào sáu giờ sáng.
Cách đúng trên máy chủ là một file environment riêng với quyền 600, chỉ nạp cho đúng tiến trình cần tới nó:
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 với printf thay vì mở editor nếu bạn muốn key không lọt vào file swap của editor. Dù làm cách nào, hãy kiểm tra bằng ls -l /etc/claude-explain.env xem quyền có đúng là -rw------- và chủ sở hữu có phải root không. Shell tương tác lấy key theo từng lần gọi qua một script bọc (ở dưới), còn systemd lấy key qua EnvironmentFile=: root đọc file trước khi hạ quyền, nên user chạy service không bao giờ cần quyền đọc file đó. Key không xuất hiện trong code, trong git, trong output của ps, hay trong lịch sử shell.
Cài SDK trong một venv
Ubuntu 24.04 đi kèm Python 3.12 và bật PEP 668, nên chạy thẳng pip install anthropic vào Python hệ thống sẽ báo lỗi error: externally-managed-environment. Lỗi đó là hệ điều hành đang làm đúng việc của nó. 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 máy chủ không cần nghi thức activate: gọi thẳng /opt/explain/venv/bin/python là luôn dùng đúng các package trong venv.
Lệnh gọi đầu tiên, và cách đọc response cho đúng
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)Hai điểm trong mười hai dòng đó gánh gần hết mô hình tư duy của API. Thứ nhất, anthropic.Anthropic() không tham số sẽ đọc key từ environment. Đừng bao giờ truyền key vào dưới dạng chuỗi viết cứng trong code. Thứ hai, response.content là một danh sách các content block, không phải một chuỗi. In thẳng nó ra là bạn gặp ngay cảnh quen thuộc của người mới:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Đó không phải lỗi, đó là repr của object. Một response có thể chứa nhiều loại block (text, lệnh gọi tool, thinking), nên bạn phải lặp qua từng block và kiểm tra block.type == "text" trước khi đụng vào .text. Viết sẵn vòng lặp đó ngay từ ngày đầu thì cả một loạt thắc mắc kiểu "nó in ra rác" sẽ không bao giờ xảy ra.
Dùng đúng model ID claude-opus-4-8. ID thế hệ hiện tại không có phần ngày tháng. Đừng gõ theo thói quen, cũng đừng nghe theo một bài blog cũ bảo bạn gắn thêm hậu tố ngày vào. Làm vậy sẽ ra lỗi 404, nói ở phần dưới.
Công cụ thật: explain
Đây là toàn bộ chương trình. Đọc stdin, in chẩn đoán theo kiểu streaming, và bắt lỗi đầy đủ:
#!/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 file thành /opt/explain/explain.py, rồi thêm một script bọc để nạp key cho lúc 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(Script bọc này phải chạy qua sudo, hoặc file env phải thuộc một group mà user quản trị của bạn nằm trong đó. Hãy chọn một cách có chủ đích, đừng nới quyền file thành 644.)
Vì sao dùng streaming. client.messages.stream in token ra ngay khi chúng về, thay vì ngồi im suốt cả lượt sinh nội dung, và nó tránh được timeout HTTP với output dài. Chính vì lý do đó mà SDK sẽ từ chối những giá trị max_tokens rất lớn trên lệnh gọi không streaming. Nếu sau đó bạn cần object đã ghép hoàn chỉnh, hãy gọi stream.get_final_message() bên trong khối with.
Vì sao thứ tự bắt lỗi lại như vậy. SDK ném ra các exception có kiểu, và bạn bắt từ loại cụ thể nhất trở đi. RateLimitError là lỗi 429 và mang theo header retry-after cho biết phải chờ bao lâu. APIStatusError gom những response không phải 2xx còn lại (kiểm tra e.status_code >= 500 để biết lỗi nằm ở phía máy chủ). APIConnectionError nghĩa là request không nhận được response nào cả. Và trước khi bạn kịp viết một vòng lặp retry: bản thân SDK đã tự thử lại với lỗi 429 và 5xx, mặc định hai lần, có backoff theo cấp số nhân (max_retries trên client). Đến lúc khối except của bạn chạy thì số lần thử lại đã dùng hết. Vì vậy trong một CLI, việc đúng là báo lỗi rồi thoát, chứ không phải ngủ một lát rồi dội tiếp.
Kiểm soát chi phí
Phần này đáng có mục riêng, vì API không có trần chi tiêu hằng tháng nào ngoài trần bạn tự cấu hình, và mọi sai sót ở đây đều âm thầm cộng dồn.
max_tokens là trần chi tiêu cho mỗi lệnh gọi. Token output mới là hướng tốn tiền, trên Opus 4.8 nó đắt gấp năm lần giá input, và max_tokens là giới hạn cứng cho số token mà model được phép sinh ra. Một prompt chạy loạn cũng không thể tốn nhiều output hơn mức bạn cho phép. Hãy đặt con số vừa với công việc: 1.500 là quá đủ cho một lần chẩn đoán log, còn một tác vụ phân loại chỉ cần 100. Nếu câu trả lời đứt giữa chừng kèm stop_reason: "max_tokens", tức là bạn đặt quá chặt. Hãy nâng lên một cách có ý thức, đừng mặc định để một con số khổng lồ.
Đếm trước khi gửi. Input cũng tốn tiền, mà log thì cồng kềnh. API có một endpoint đếm token dùng miễn phí (endpoint này có rate limit riêng, tách khỏi rate limit của 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 nó để chặn trường hợp bạn lỡ tay đẩy một file log 2 GB qua công cụ. Đừng dùng tiktoken cho việc này: đó là tokenizer của OpenAI, nó đếm thiếu token của Claude khoảng 15–20% với văn bản thường, và lệch nhiều hơn với code.
Chọn model theo từng loại việc, đừng chọn 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 triệu token input và $25 cho mỗi triệu token output. Haiku 4.5 (claude-haiku-4-5) là $1/$5 với context window 200K. Sonnet 5 (claude-sonnet-5) nằm giữa hai mức đó, $3/$15, kèm 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 với câu trả lời 500 token tốn khoảng $0.0225 trên Opus và $0.0045 trên Haiku. Hãy bắt đầu bằng Opus trong lúc bạn còn đang đánh giá chất lượng output, rồi chạy lại đúng những prompt đó trên Haiku. Với các tác vụ biến đổi đơn giản và khối lượng lớn, kết quả thường không phân biệt được, mà giá chỉ bằng một phần năm. Hãy kiểm tra lại con số mới nhất trên trang giá trước khi ghim bất kỳ số nào vào một bản dự toán.
Việc nào chờ được thì đẩy sang Batches. Batches API xử lý request bất đồng bộ với giá bằng 50% giá thường, và phần lớn batch xong trong vòng một giờ. Bản tóm tắt chạy đêm, xử lý bù dữ liệu cũ, phân loại hàng loạt: việc nào không có người ngồi chờ thì thuộc về chỗ đó.
Prompt caching cho phần context lặp đi lặp lại. Nếu mỗi lệnh gọi đều gửi lại cùng một system prompt lớn hoặc cùng một runbook, hãy đánh dấu phần đó là có thể 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 onGhi cache tốn khoảng 1,25 lần giá input, đọc cache chỉ khoảng 0,1 lần, với TTL 5 phút. Nghĩa là lệnh gọi thứ hai trong khoảng thời gian đó đã bù được cho lệnh gọi đầu. Có hai chỗ dễ vấp. Phần prefix được cache phải vượt một ngưỡng tối thiểu tùy theo model, trên Opus là vài nghìn token, nên một system prompt ngắn sẽ lặng lẽ không được cache. Và nếu cache_read_input_tokens vẫn bằng không qua những lệnh gọi giống hệt nhau, tức là có thứ gì đó trong prefix thay đổi ở mỗi request (thủ phạm quen thuộc là một dấu thời gian).
Nhớ rõ những gì bị tính là input. System prompt, phần định nghĩa tool, và trong hội thoại nhiều lượt là toàn bộ lịch sử bạn gửi lại ở mỗi lượt, tất cả đều bị tính thành token input. Một vòng lặp chat không bao giờ cắt bớt lịch sử sẽ tăng chi phí theo bình phương. Bạn nên hiểu cách tính đầy đủ trước khi dựng bất cứ thứ gì dạng hội thoại: cách token và hóa đơn của Claude thật sự cộng dồn.
Chạy nó dưới systemd
Đây là phần thưởng cho kỷ luật dùng file environment: 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 nowHãy để ý EnvironmentFile= đem lại điều gì: systemd đọc file thuộc root với quyền 600 trước khi hạ xuống user explain không đặc quyền, nên tiến trình nhận được biến môi trường trong khi user đó vẫn không đọc được file key. Group systemd-journal cấp quyền đọc log. Hãy thử bằng một lệnh systemctl start thủ công rồi đọc journalctl -u log-digest.service. Đừng chờ tới 06:15 mới phát hiện một chỗ gõ sai. Khi cách làm này vượt quá khả năng của một shell pipeline, thì chính cách cất key trong file env đó chuyển thẳng sang các workflow n8n chạy bằng Claude trên cùng chiếc máy.
Các kiểu hỏng, kèm chuỗi lỗi bạn sẽ thấy
Lỗi 401 dù key vẫn dùng được. Exception hiện ra như sau:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Nếu key chạy được trong shell của bạn mà service lại trả 401, tức là service chưa hề nhận được key. Nhớ rằng systemd không đọc .bashrc. Hãy kiểm tra xem EnvironmentFile= có trỏ đúng đường dẫn không. Các nguyên nhân khác: dấu nháy bị dán vào file env (ANTHROPIC_API_KEY="sk-ant-...", systemd loại dấu nháy ra, nhưng lệnh . file trong script bọc của bạn lại giữ chúng trong giá trị nếu bạn đặt nháy lệch), khoảng trắng thừa ở cuối dòng, hoặc một key bạn đã thu hồi trong Console từ tuần trước.
Lỗi 404 do gõ sai tên model. Kiểu phổ biến nhất là gắn hậu tố ngày vào một model ID đời 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 thế hệ hiện tại phải viết chính xác như thế này: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Hãy chép chúng từ tài liệu models, đừng chép theo trí nhớ hay theo một hướng dẫn cũ.
Lỗi 429 rate_limit_error. Chuỗi mô tả kiểu lỗi là rate_limit_error, và response mang theo header retry-after ghi số giây cần chờ. SDK đã tự thử lại hai lần kèm backoff trước khi bạn nhìn thấy exception, nên nếu 429 vẫn dai dẳng thì tốc độ gửi thật sự của bạn đã vượt hạn mức của tier. Hãy gom việc vào batch hoặc giãn nhịp gửi ra, đừng siết chặt thêm vòng lặp retry.
Nó in ra object chứ không in ra chữ. Output trông như [TextBlock(citations=None, text='...', type='text')]. Bạn đã in response.content thay vì lặp qua từng block và đọc .text ở những block có block.type == "text". Mọi ví dụ SDK ở trên đều làm đúng, cứ chép lại vòng lặp đó.
error: externally-managed-environment. Bạn đã chạy pip install vào Python hệ thống của Ubuntu 24.04. Hãy dùng venv, và đừng bao giờ dùng --break-system-packages trên một máy chủ mà bạn còn quan tâm.
Câu trả lời bị cắt cụt. response.stop_reason == "max_tokens" nghĩa là model chạm trần output của bạn khi đang nói dở. Đó là đúng thiết kế. Hãy nâng trần lên một cách có chủ đích.
Khi ứng dụng đầu tiên của bạn đã chạy được, dựng một AI agent với Claude sẽ biến chính những lệnh gọi API đó thành một agent biết dùng tool.
Câu hỏi thường gặp (FAQ)
Dùng thử Claude API tốn bao nhiêu tiền?
Với một công cụ như thế này thì thật sự rất ít. Tính đến tháng 7 năm 2026, Opus 4.8 có giá $5 cho mỗi triệu token input và $25 cho mỗi triệu token output. Một lần chẩn đoán log điển hình, khoảng vài nghìn token vào và vài trăm token ra, tốn chừng hai xu Mỹ; trên Haiku 4.5 ($1/$5) là chưa tới nửa xu. Chạy bản tóm tắt mỗi ngày suốt một tháng vẫn rẻ hơn một ly cà phê. Rủi ro không nằm ở giá của mỗi lệnh gọi. Nó nằm ở những vòng lặp không có điểm dừng và max_tokens không có giới hạn, và đó là lý do hướng dẫn này đặt rõ cả hai.
Claude API có gói miễn phí không?
Tính đến tháng 7 năm 2026 thì không có gói miễn phí dùng lâu dài. Tài liệu giá của Anthropic nói người dùng mới nhận được một khoản credit nhỏ để thử API. Đó là khoản dùng thử một lần, con số chính xác hiện trong Console lúc bạn đăng ký, và hết khoản đó thì 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à chi phí biên bằng không cho mỗi request chứ không phải chất lượng cao nhất, lựa chọn còn lại là tự chạy một model open-weight bằng Ollama và trả giá bằng RAM thay vì bằng token.
Làm sao giữ an toàn cho API key trên máy chủ?
Không để trong code, không để trong git, không export từ .bashrc, không gõ vào một shell có lưu lịch sử. Hãy đặt key trong một file thuộc root với quyền 600, rồi nạp nó theo từng tiến trình: một script bọc cho lúc dùng tương tác, EnvironmentFile= cho systemd. Mỗi máy chủ hoặc mỗi dự án dùng một key riêng, để khi phải thu hồi một key bị lộ thì bạn chỉ mất đúng một chỗ chứ không mất hết. Nếu key từng lọt lên một trang paste hay vào một commit git, hãy thu hồi nó trong Console ngay lập tức. Xóa commit không làm cho key hết lộ.
Nên bắt đầu với model Claude nào?
Hãy bắt đầu với claude-opus-4-8 trong lúc bạn còn đang cân nhắc xem output có đủ tốt để xây tiếp hay không. Bạn cần đánh giá ý tưởng ở mức chất lượng cao nhất, và với lượng dùng nhỏ thì chênh lệch chi phí chỉ là vài xu. Khi prompt đã ổn định, hãy chạy lại chính dữ liệu thật của bạn trên claude-haiku-4-5. Với việc tóm tắt, phân loại và sàng lọc log, nó thường tốt ngang mà giá chỉ bằng một phần năm. Hãy chuyển sang Haiku hay Sonnet dựa trên đo đạc, đừng chuyển theo mặc định.