Skip to main content

Async Python wrapper for the Claude.ai web app

Project description

Claude-API

A reverse-engineered asynchronous Python wrapper for the Claude.ai web app.

Disclaimer: This is an unofficial library that interacts with Claude.ai's internal web API.
It is not affiliated with or endorsed by Anthropic. Use responsibly.


Features

  • Multi-turn Conversations — Stateful ChatSession objects keep history automatically across turns.
  • Streaming Mode — Yield partial outputs in real time as Claude writes them.
  • File Attachments — Attach local images, PDFs, and documents to any message.
  • File Downloads — Download files generated by Claude's REPL/sandbox.
  • Model Selection — Switch between Claude 4, 3.7, 3.5, and any future model by name.
  • System Prompts — Apply custom system prompts at the conversation or single-call level.
  • Session Resumption — Serialize and reload conversation metadata to continue across Python processes.
  • Auto-Close — Optional inactivity timer for always-on services.
  • Async-First — Built on aiohttp for non-blocking I/O throughout.

Table of Contents


Installation

Requires Python 3.10 or higher.

pip install -U claude-webapi

Authentication

  1. Go to claude.ai and log in with your Google / email account.
  2. Press F12Application tab → Cookieshttps://claude.ai.
  3. Copy the value of the sessionKey cookie.
  4. Your organization_id is visible in the lastActiveOrg cookie, or in the URL when you open a conversation: https://claude.ai/chat/<org_uuid>/…

Note: Keep your sessionKey secret — it grants full access to your Claude account.


Usage

Initialization

import asyncio
from claude_webapi import ClaudeClient

SESSION_KEY     = "sk-ant-…"       # your sessionKey cookie
ORGANIZATION_ID = "xxxxxxxx-…"     # your org UUID

async def main():
    client = ClaudeClient(SESSION_KEY, ORGANIZATION_ID)
    await client.init(timeout=30, auto_close=False, close_delay=300)
    # … use client …
    await client.close()

asyncio.run(main())

Or use it as an async context manager (automatically calls init and close):

async def main():
    async with ClaudeClient(SESSION_KEY, ORGANIZATION_ID) as client:
        response = await client.generate_content("Hello!")
        print(response.text)

Tip: In long-running services (bots, APIs) set auto_close=True and a reasonable close_delay so the HTTP session is cleaned up during idle periods.


Generate content

async def main():
    response = await client.generate_content("Explain quantum entanglement simply.")
    print(response.text)

print(response) produces the same output — ModelOutput.__str__ returns text.


Generate content with files

Pass a list of local file paths alongside your prompt:

from pathlib import Path

async def main():
    response = await client.generate_content(
        "Summarise this PDF and describe the chart image.",
        files=["report.pdf", Path("chart.png")],
    )
    print(response.text)

Supported file types mirror what Claude.ai accepts: PDFs, plain text, images (PNG/JPEG/GIF/WebP), CSV, and most common document formats.


Upload file as bytes

When the file lives in memory rather than on disk, pass raw bytes directly:

async def main():
    data = b"col1,col2\n1,2\n3,4"
    chat = client.start_chat()
    fid = await client.upload_file(
        chat.cid,
        data=data,
        filename="data.csv",
        mime_type="text/csv",
    )
    r = await chat.send_message("Analyse the uploaded CSV.", files=[fid])
    print(r.text)

Inline attachments

Pass pre-extracted text directly as an attachment — no upload round-trip needed:

async def main():
    response = 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",
        }],
    )
    print(response.text)

attachments is supported on generate_content, generate_content_stream, and ChatSession.send_message / send_message_stream.


Multi-turn conversations

Use start_chat() to create a ChatSession that automatically threads context across messages:

async def main():
    chat = client.start_chat()

    r1 = await chat.send_message("My name is Alice.")
    print(r1.text)

    r2 = await chat.send_message("What is my name?")
    print(r2.text)   # → "Your name is Alice."

Files can be attached to any turn:

    r3 = await chat.send_message(
        "Analyse this spreadsheet and create a bar chart.",
        files=["sales_q1.csv"],
    )

Resume a previous conversation

Save chat.metadata and pass it back to start_chat to resume later — even after the Python process has exited:

import json

async def main():
    chat = client.start_chat()
    await chat.send_message("Remember: the secret word is BANANA.")

    # Persist metadata
    saved = chat.metadata
    with open("session.json", "w") as f:
        json.dump(saved, f)

    # --- later, in a new process ---
    with open("session.json") as f:
        saved = json.load(f)

    previous_chat = client.start_chat(metadata=saved)
    r = await previous_chat.send_message("What was the secret word?")
    print(r.text)   # → "The secret word is BANANA."

Delete a conversation

async def main():
    chat = client.start_chat()
    await chat.send_message("This is temporary.")

    await client.delete_conversation(chat.cid)
    print(f"Deleted: {chat.cid}")
    # Or equivalently:
    # await chat.delete()

Streaming mode

Get incremental output using generate_content_stream or ChatSession.send_message_stream. The text_delta attribute on each chunk holds only the new characters since the last yield.

async def main():
    async for chunk in client.generate_content_stream(
        "Write a 500-word short story about a time-travelling librarian."
    ):
        print(chunk.text_delta, end="", flush=True)
    print()

Inside a chat session:

async def main():
    chat = client.start_chat()
    async for chunk in chat.send_message_stream("Explain async/await in Python."):
        print(chunk.text_delta, end="", flush=True)
    print()
    # Follow-up works normally — context is preserved
    r = await chat.send_message("Give me a code example.")
    print(r.text)

Select a language model

Pass a Model enum member or a raw model string:

from claude_webapi.constants import Model

async def main():
    # Using the enum
    r1 = await client.generate_content(
        "What model are you?",
        model=Model.OPUS,
    )
    print(r1.text)

    # Using a raw string (useful for models not in the enum)
    r2 = await client.generate_content(
        "What model are you?",
        model="claude-sonnet-4-6",
    )
    print(r2.text)

    # Per-session model
    chat = client.start_chat(model=Model.HAIKU)
    r3 = await chat.send_message("Fast reply please.")
    print(r3.text)

Available models (as of February 2026):

Enum constant Model 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

You can always pass a custom string to access models not listed above.


Download files from Claude's sandbox

When Claude generates a file through its code interpreter (REPL), you can download it:

async def main():
    chat = client.start_chat()
    await chat.send_message(
        "Generate a CSV with the first 20 Fibonacci numbers and save it as fib.csv"
    )

    local_path = await client.download_file(
        chat.cid, "fib.csv", dest="./downloads"
    )
    print(f"Saved to: {local_path}")

List & manage conversations

async def main():
    conversations = await client.list_conversations()
    for conv in conversations[:5]:
        print(conv["uuid"], conv.get("name", "(unnamed)"))

    # Rename a conversation
    await client.rename_conversation(conversations[0]["uuid"], "My renamed chat")

Check for multiple reply candidates

Claude sometimes returns multiple reply candidates. You can inspect them and select which one to continue the conversation from:

async def main():
    chat = client.start_chat()
    response = await chat.send_message("Recommend a sci-fi novel.")

    for i, candidate in enumerate(response.candidates):
        print(f"--- Candidate {i} ---")
        print(candidate.text[:200])

    if len(response.candidates) > 1:
        chat.choose_candidate(index=1)   # use the second candidate going forward
        follow_up = await chat.send_message("Tell me more about that book.")
        print(follow_up.text)

Logging

from claude_webapi import set_log_level

set_log_level("DEBUG")   # DEBUG | INFO | WARNING | ERROR | CRITICAL

Error handling

from claude_webapi import (
    ClaudeClient,
    AuthenticationError,
    APIError,
    QuotaExceededError,
    TimeoutError,
    ConversationNotFoundError,
)

async def main():
    try:
        response = await client.generate_content("Hello!")
    except AuthenticationError:
        print("Invalid or expired sessionKey — please re-authenticate.")
    except QuotaExceededError:
        print("Daily message limit reached. Try again later.")
    except TimeoutError:
        print("Request timed out.")
    except ConversationNotFoundError:
        print("Conversation UUID not found.")
    except APIError as e:
        print(f"API error {e.status_code}: {e}")

References


Stargazers

If this project helped you, please consider starring it ⭐

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.0.tar.gz (22.2 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.0-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: claude_webapi-1.0.0.tar.gz
  • Upload date:
  • Size: 22.2 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.0.tar.gz
Algorithm Hash digest
SHA256 f3c5755cfb8f1770d3578c7346e25d74c7581ab0374e2cdc847c472709e984db
MD5 5074bf6a0feb1424d54e41baca12bf9d
BLAKE2b-256 c3ac32233e3a3022499b22dcc7df145a9275a107e98f8d7bb1ebf74463687930

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_webapi-1.0.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: claude_webapi-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 20.9 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d90190e8896c57d1af9e367f5c095569b79f612cc4006382aff544fd574d62ae
MD5 73efe2dd89445a9694fa2a6dabf80606
BLAKE2b-256 34de39199991c208752733931bc73e8d82b1f5a3af804ae097d852c907229924

See more details on using hashes here.

Provenance

The following attestation bundles were made for claude_webapi-1.0.0-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