Async Python wrapper for the Claude.ai web app
Project description
Claude-API
An unofficial async Python client for the Claude.ai web interface.
Disclaimer: This library reverse-engineers Claude.ai's internal web API. It is not affiliated with or endorsed by Anthropic. Use responsibly.
Features
- Multi-turn conversations — stateful
ChatSessionkeeps threading across turns automatically - Streaming — yield tokens in real time as Claude writes them
- Response styles — built-in presets (
concise,explanatory,formal,learning) or a fully custom prompt; settable per-session or per-request - File attachments — attach local files or in-memory bytes to any message
- File downloads — pull files generated by Claude's code interpreter
- Model selection — any Claude model by enum or raw string
- Session resumption — serialize and restore conversation state across processes
- Async-first — built on
aiohttp
Related projects
ChatAI Console
A full-featured self-hosted web UI for Claude, ChatWithAI.app and 1MinAI. Claude-API stems off of this
Features:
- Multi-account management — Claude, ChatWithAI.app, and 1MinAI accounts in one place side-by-side
- Real-time streaming with artifacts
- File upload, download, and canvas preview panel
- Google OAuth sign-in flow for Claude accounts
- Quota tracking and usage history
- Local conversation storage with pinning
Installation
Requires Python 3.10+.
pip install claude-webapi
Authentication
You can authenticate in either of two ways:
- Reuse an existing
sessionKeycookie. - Exchange a Google auth code from Claude's browser login flow with
ClaudeClient.from_google_code(...).
If you already have a session cookie:
- Open claude.ai and log in.
- Press F12 → Application → Cookies →
https://claude.ai. - Copy the
sessionKeycookie value. - Your organization ID is in the
lastActiveOrgcookie, or in the URL of any conversation.
Keep your
sessionKeysecret — it grants full access to your account.
If you want browser-based Google login instead of cookie reuse, pass the code returned by Claude's login flow:
client = await ClaudeClient.from_google_code(
code="YOUR_GOOGLE_AUTH_CODE",
arkose_session_token="YOUR_ARKOSE_SESSION_TOKEN",
)
The browser handles the OAuth sign-in; the library only exchanges the resulting code and boots a normal Claude session.
Quick start
import asyncio
from claude_webapi import ClaudeClient
async def main():
async with ClaudeClient("sk-ant-…", "org-uuid-…") as client:
r = await client.generate_content("What is the capital of France?")
print(r.text)
asyncio.run(main())
organization_id is optional — the client will discover it automatically if omitted.
Usage
Single-turn generation
r = await client.generate_content("Explain quantum entanglement simply.")
print(r.text)
str(r) is equivalent to r.text.
Streaming
async for chunk in client.generate_content_stream("Write a haiku about Python."):
print(chunk.text_delta, end="", flush=True)
print()
Multi-turn chat
chat = client.start_chat()
r1 = await chat.send_message("My name is Alice.")
r2 = await chat.send_message("What is my name?") # → "Your name is Alice."
Streaming within a session:
async for chunk in chat.send_message_stream("Explain async/await in Python."):
print(chunk.text_delta, end="", flush=True)
print()
r = await chat.send_message("Give me a one-line example.") # context preserved
Response styles
Five built-in presets mirror Claude.ai's style selector:
| Key | Effect |
|---|---|
"normal" |
Default Claude behaviour (default) |
"concise" |
Shorter answers, less preamble |
"explanatory" |
Teacher-style breakdowns with examples |
"formal" |
Professional language, no contractions |
"learning" |
Socratic / guided-discovery mode |
You can also pass any arbitrary string as a custom style prompt, or a raw dict for full control over the personalized_styles payload.
Session-level style
All turns in the session use this style unless overridden per-request:
chat = client.start_chat(style="concise")
r1 = await chat.send_message("Explain Docker.") # concise
r2 = await chat.send_message("Now explain Kubernetes.") # concise
Per-request style override
Pass style= on any individual call — it takes precedence over the session default for that turn only:
chat = client.start_chat(style="concise")
r1 = await chat.send_message("Explain monads.") # concise
r2 = await chat.send_message("Now really dig into it.", style="explanatory") # override
r3 = await chat.send_message("Summarise what we covered.") # back to concise
Works on streaming turns too:
async for chunk in chat.send_message_stream("Write a poem.", style="formal"):
print(chunk.text_delta, end="", flush=True)
Per-request style also works on the stateless methods:
r = await client.generate_content("Explain recursion.", style="learning")
async for chunk in client.generate_content_stream("Tell me a story.", style="formal"):
print(chunk.text_delta, end="", flush=True)
Custom style prompt
chat = client.start_chat(style="Always reply in bullet points. Never use prose.")
Raw dict (full control)
chat = client.start_chat(style={
"type": "custom",
"key": "Pirate",
"name": "Pirate",
"nameKey": "pirate_style_name",
"prompt": "Respond exclusively in pirate dialect. Arr.",
"summary": "Pirate mode",
"summaryKey": "pirate_style_summary",
"isDefault": False,
})
File attachments
Pass local file paths to any message:
r = await client.generate_content(
"Summarise this report.",
files=["report.pdf", "chart.png"],
)
Upload raw bytes (file in memory):
chat = client.start_chat()
fid = await client.upload_file(
chat.cid,
data=b"col1,col2\n1,2\n3,4",
filename="data.csv",
mime_type="text/csv",
)
r = await chat.send_message("Analyse the CSV.", files=[fid])
Inline text attachments (no upload round-trip):
r = await client.generate_content(
"Summarise the document below.",
attachments=[{
"extracted_content": "The quick brown fox…",
"file_name": "notes.txt",
"file_size": 1234,
"file_type": "txt",
}],
)
Download files from Claude's sandbox
chat = client.start_chat()
await chat.send_message("Generate the first 20 Fibonacci numbers and save as fib.csv.")
local = await client.download_file(chat.cid, "fib.csv", dest="./downloads")
print(f"Saved to: {local}")
Model selection
from claude_webapi.constants import Model
r = await client.generate_content("Hello!", model=Model.OPUS)
chat = client.start_chat(model=Model.HAIKU) # session-level
r2 = await chat.send_message("Fast reply.", model=Model.SONNET) # per-turn override
Available models:
| Enum | String |
|---|---|
Model.SONNET |
claude-sonnet-4-6 |
Model.OPUS |
claude-opus-4-6 |
Model.HAIKU |
claude-haiku-4-5-20251001 |
Model.SONNET_3_7 |
claude-3-7-sonnet-20250219 |
Model.SONNET_3_5 |
claude-3-5-sonnet-20241022 |
Model.HAIKU_3_5 |
claude-3-5-haiku-20241022 |
Model.OPUS_3 |
claude-3-opus-20240229 |
Any model string not in the enum can be passed as a plain string.
Resume a previous conversation
import json
# First session
chat = client.start_chat()
await chat.send_message("The secret word is BANANA.")
with open("session.json", "w") as f:
json.dump(chat.metadata, f)
# Later session
with open("session.json") as f:
meta = json.load(f)
chat = client.start_chat(metadata=meta)
r = await chat.send_message("What was the secret word?") # → BANANA
Delete a conversation
await chat.delete()
# or
await client.delete_conversation(chat.cid)
List conversations
convs = await client.list_conversations()
for c in convs[:5]:
print(c["uuid"], c.get("name", "(unnamed)"))
Multiple reply candidates
r = await chat.send_message("Recommend a sci-fi novel.")
for i, c in enumerate(r.candidates):
print(f"[{i}]", c.text[:120])
chat.choose_candidate(1) # branch the conversation from candidate 1
Initialisation options
client = ClaudeClient(
session_key="sk-ant-…",
organization_id="…", # optional, auto-discovered if omitted
proxy="http://user:pass@host:port", # optional HTTP proxy
)
await client.init(
timeout=30, # request timeout in seconds
auto_close=True, # close HTTP session after inactivity
close_delay=300, # inactivity threshold in seconds
)
Logging
from claude_webapi import set_log_level
set_log_level("DEBUG") # DEBUG | INFO | WARNING | ERROR | CRITICAL
Error handling
from claude_webapi.exceptions import (
AuthenticationError,
APIError,
QuotaExceededError,
TimeoutError,
ConversationNotFoundError,
)
try:
r = await client.generate_content("Hello!")
except AuthenticationError:
print("Invalid or expired sessionKey.")
except QuotaExceededError as e:
print(f"Rate limited. Resets in {e.retry_after_s}s.")
except TimeoutError:
print("Request timed out.")
except ConversationNotFoundError:
print("Conversation not found.")
except APIError as e:
print(f"HTTP {e.status_code}: {e}")
References
- Claude.ai
- gemini_webapi — inspiration for the interface design
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_webapi-1.0.2.tar.gz.
File metadata
- Download URL: claude_webapi-1.0.2.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
410f778fccacee17703b38eb43a63cb625ee4cba18067f6876fb46d65bceed29
|
|
| MD5 |
b5a998c2937c97e81f7eff12edee4429
|
|
| BLAKE2b-256 |
c892f137840e2d85ee3a977494ec9e06104087827c5cacd1498e95d92e20c3b7
|
Provenance
The following attestation bundles were made for claude_webapi-1.0.2.tar.gz:
Publisher:
python-publish.yml on cyber-wojtek/Claude-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_webapi-1.0.2.tar.gz -
Subject digest:
410f778fccacee17703b38eb43a63cb625ee4cba18067f6876fb46d65bceed29 - Sigstore transparency entry: 1563174744
- Sigstore integration time:
-
Permalink:
cyber-wojtek/Claude-API@5b7600ce2b3e5c9d3ce9099a5f69f84f723cc7f6 -
Branch / Tag:
refs/tags/v0.12 - Owner: https://github.com/cyber-wojtek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@5b7600ce2b3e5c9d3ce9099a5f69f84f723cc7f6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file claude_webapi-1.0.2-py3-none-any.whl.
File metadata
- Download URL: claude_webapi-1.0.2-py3-none-any.whl
- Upload date:
- Size: 23.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adf8511b4e63da20f42134ab474487000d57a5c154ecfd1ca34436de13befc45
|
|
| MD5 |
2815b14788e81673a84aa5f153ea80df
|
|
| BLAKE2b-256 |
4c30beaa7c8670d2f0b5a3ec5770c15951257c2dc1c5711904b80646942d5931
|
Provenance
The following attestation bundles were made for claude_webapi-1.0.2-py3-none-any.whl:
Publisher:
python-publish.yml on cyber-wojtek/Claude-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_webapi-1.0.2-py3-none-any.whl -
Subject digest:
adf8511b4e63da20f42134ab474487000d57a5c154ecfd1ca34436de13befc45 - Sigstore transparency entry: 1563174787
- Sigstore integration time:
-
Permalink:
cyber-wojtek/Claude-API@5b7600ce2b3e5c9d3ce9099a5f69f84f723cc7f6 -
Branch / Tag:
refs/tags/v0.12 - Owner: https://github.com/cyber-wojtek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@5b7600ce2b3e5c9d3ce9099a5f69f84f723cc7f6 -
Trigger Event:
release
-
Statement type: