Metadata-Version: 2.4
Name: claude_webapi
Version: 1.0.1
Summary: Async Python wrapper for the Claude.ai web app
License: MIT
Project-URL: Homepage, https://github.com/cyber-wojtek/Claude-API
Project-URL: Issues, https://github.com/cyber-wojtek/Claude-API/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
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

An unofficial **async Python client** for the [Claude.ai](https://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`

---

## Installation

Requires Python **3.10+**.

```sh
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](https://claude.ai) and log in.
2. Press **F12** → **Application** → **Cookies** → `https://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:

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

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

```python
r = await client.generate_content("Explain quantum entanglement simply.")
print(r.text)
```

`str(r)` is equivalent to `r.text`.

---

### Streaming

```python
async for chunk in client.generate_content_stream("Write a haiku about Python."):
    print(chunk.text_delta, end="", flush=True)
print()
```

---

### Multi-turn chat

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

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

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

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

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

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

```python
chat = client.start_chat(style="Always reply in bullet points. Never use prose.")
```

#### Raw dict (full control)

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

```python
r = await client.generate_content(
    "Summarise this report.",
    files=["report.pdf", "chart.png"],
)
```

Upload raw bytes (file in memory):

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

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

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

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

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

```python
await chat.delete()
# or
await client.delete_conversation(chat.cid)
```

---

### List conversations

```python
convs = await client.list_conversations()
for c in convs[:5]:
    print(c["uuid"], c.get("name", "(unnamed)"))
```

---

### Multiple reply candidates

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

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

```python
from claude_webapi import set_log_level
set_log_level("DEBUG")   # DEBUG | INFO | WARNING | ERROR | CRITICAL
```

---

## Error handling

```python
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](https://claude.ai)
- [Anthropic official API](https://docs.anthropic.com)
- [gemini_webapi](https://github.com/HanaokaYuzu/Gemini-API) — inspiration for the interface design
