Metadata-Version: 2.4
Name: claude-webapi
Version: 1.0.0
Summary: Async Python wrapper for the Claude.ai web app
License: MIT
Project-URL: Homepage, https://github.com/yourusername/claude_webapi
Project-URL: Issues, https://github.com/yourusername/claude_webapi/issues
Keywords: claude,anthropic,ai,wrapper,async
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9
Requires-Dist: browser-cookie3>=0.19; extra == "browser"
Provides-Extra: browser
Requires-Dist: browser-cookie3>=0.19; extra == "browser"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# Claude-API

A reverse-engineered **asynchronous Python wrapper** for the [Claude.ai](https://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

- [claude\_webapi](#claude_webapi)
  - [Features](#features)
  - [Table of Contents](#table-of-contents)
  - [Installation](#installation)
  - [Authentication](#authentication)
  - [Usage](#usage)
    - [Initialization](#initialization)
    - [Generate content](#generate-content)
    - [Generate content with files](#generate-content-with-files)
    - [Upload file as bytes](#upload-file-as-bytes)
    - [Inline attachments](#inline-attachments)
    - [Multi-turn conversations](#multi-turn-conversations)
    - [Resume a previous conversation](#resume-a-previous-conversation)
    - [Delete a conversation](#delete-a-conversation)
    - [Streaming mode](#streaming-mode)
    - [Select a language model](#select-a-language-model)
    - [Download files from Claude's sandbox](#download-files-from-claudes-sandbox)
    - [List \& manage conversations](#list--manage-conversations)
    - [Check for multiple reply candidates](#check-for-multiple-reply-candidates)
  - [Logging](#logging)
  - [Error handling](#error-handling)
  - [References](#references)
  - [Stargazers](#stargazers)

---

## Installation

Requires Python **3.10** or higher.

```sh
pip install -U claude-webapi
```

---

## Authentication

1. Go to [claude.ai](https://claude.ai) and log in with your Google / email account.
2. Press **F12** → **Application** tab → **Cookies** → `https://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

```python
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`):

```python
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

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
    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:

```python
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

```python
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.

```python
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:

```python
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:

```python
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:

```python
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

```python
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:

```python
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

```python
from claude_webapi import set_log_level

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

---

## Error handling

```python
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

- [Anthropic — Claude.ai](https://claude.ai)
- [Anthropic — Official API](https://docs.anthropic.com)
- [gemini_webapi](https://github.com/HanaokaYuzu/Gemini-API) — Inspiration for this project's interface design

---

## Stargazers

If this project helped you, please consider starring it ⭐

