Skip to main content

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 ChatSession keeps 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:

  1. Reuse an existing sessionKey cookie.
  2. Exchange a Google auth code from Claude's browser login flow with ClaudeClient.from_google_code(...).

If you already have a session cookie:

  1. Open claude.ai and log in.
  2. Press F12ApplicationCookieshttps://claude.ai.
  3. Copy the sessionKey cookie value.
  4. Your organization ID is in the lastActiveOrg cookie, or in the URL of any conversation.

Keep your sessionKey secret — 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

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

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

claude_webapi-1.0.31.tar.gz (24.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claude_webapi-1.0.31-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file claude_webapi-1.0.31.tar.gz.

File metadata

  • Download URL: claude_webapi-1.0.31.tar.gz
  • Upload date:
  • Size: 24.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for claude_webapi-1.0.31.tar.gz
Algorithm Hash digest
SHA256 ffbe4689faa402194479b9971bef27c40f3712a9f7c19b4ddb10fe37d379a3f3
MD5 067725f57205620def6eab49a2fd0d66
BLAKE2b-256 3fb1474424c032972126e63941f310ffea6d01f96790a4fc4dac2ac8a9265049

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_webapi-1.0.31.tar.gz:

Publisher: python-publish.yml on cyber-wojtek/Claude-API

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file claude_webapi-1.0.31-py3-none-any.whl.

File metadata

  • Download URL: claude_webapi-1.0.31-py3-none-any.whl
  • Upload date:
  • Size: 23.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for claude_webapi-1.0.31-py3-none-any.whl
Algorithm Hash digest
SHA256 b9fa206c66ea7cd734cdc8cfd0e2cf75d2195d90865ab7039aef3763b0db5ede
MD5 e1dfdd883c4726ec65d153ec52136e4c
BLAKE2b-256 5f58a85ab031888d1564ea91d0d3530061f6a7b2a31c9ba334843c36e1832107

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_webapi-1.0.31-py3-none-any.whl:

Publisher: python-publish.yml on cyber-wojtek/Claude-API

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page