# Anthropic SDK

> Point the official Anthropic SDK for Python or TypeScript at MirAPI by changing only the base URL and API key.

The official [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-python) (Python or TypeScript) speaks the Anthropic Messages protocol, which MirAPI implements on the bare API origin. To switch from Anthropic to MirAPI you only change the base URL and API key; request and response shapes stay the same.

## What you need

| Element | Value |
|---|---|
| Base URL | `https://api.mirapi.ai` — bare origin, **no `/v1`** |
| API key | `MIRAPI_API_KEY` |
| Model | `deepseek-chat` |

The SDK authenticates with the `x-api-key` header by default. MirAPI accepts both `x-api-key` and `Authorization: Bearer` on `/v1/messages`.

:::note
MirAPI exposes three protocol surfaces. The Anthropic SDK targets the Anthropic one, so it needs the **bare origin**:

- OpenAI-compatible: `https://api.mirapi.ai/v1`
- Anthropic: `https://api.mirapi.ai` (bare origin, this page)
- Gemini: `https://api.mirapi.ai` or `https://api.mirapi.ai/v1beta`
:::

## Install

```bash
pip install anthropic
```

```bash
npm install @anthropic-ai/sdk
```

## Configure

### Python

```python
import os
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.mirapi.ai",
    api_key=os.environ["MIRAPI_API_KEY"],
)
```

### TypeScript

```ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://api.mirapi.ai",
  apiKey: process.env.MIRAPI_API_KEY,
});
```

The SDK sends the `anthropic-version` header by default; MirAPI accepts it like any other header.

## Verify with a message

```python
message = client.messages.create(
    model="deepseek-chat",
    max_tokens=200,
    messages=[{"role": "user", "content": "Reply with exactly: connected to MirAPI"}],
)
print(message.content[0].text)
```

The output should contain `connected to MirAPI`. The Anthropic protocol requires `max_tokens`, so set it on every call.

## Streaming

```python
with client.messages.stream(
    model="deepseek-chat",
    max_tokens=200,
    messages=[{"role": "user", "content": "Count from 1 to 5."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="")
```

Streaming uses standard SSE. Reasoning content, when the model produces it, arrives before the final answer.

## Prompt caching

Add `cache_control` to a `system` or `messages` block to mark a cache breakpoint:

```python
message = client.messages.create(
    model="deepseek-chat",
    max_tokens=200,
    system=[
        {
            "type": "text",
            "text": "You are a helpful assistant.",
            "cache_control": {"type": "ephemeral"},
        }
    ],
    messages=[{"role": "user", "content": "Hello"}],
)
```

- You can mark up to 4 breakpoints. `"ttl": "1h"` extends the cache to 1 hour and raises the write price.
- Minimum length and TTL are decided by the vendor (Claude 1,024–4,096 tokens; OpenAI 1,024; default about 5 minutes).
- Confirm a hit through the `usage.prompt_tokens_details.cached_tokens` / `cache_write_tokens` fields. Cache reads are cheaper than normal input.

## Errors and troubleshooting

| Status | Meaning | What to do |
|---|---|---|
| 401 | Missing, invalid, or unknown key | Fix your credentials |
| 403 | Valid key, but rejected | Resolve the cause and retry (balance, whitelist, quota, IP allowlist) |
| 413 | Payload too large | Reduce the request size |
| 429 | Rate limited | Retry with jittered exponential backoff (1s → 2s → 4s, cap ~30s) |
| 500 | Gateway or upstream error | Idempotent requests are safe to retry |

The Anthropic error envelope looks like this:

```text
{"type":"error","error":{"type":"permission_error","message":"..."}}
```

Every error message ends with a request ID — include it when you open a support ticket. A 403 with a valid key usually means the model is not on your whitelist or the balance is exhausted.

## Billing

- A non-streaming response's `usage` reports token counts only, never an amount. Check the amount in the console or via `GET /api/log/token`.
- Text and multimodal chat bill per token (input / output / cache read, per 1M USD). Reasoning tokens bill as output.
- Balance is prepaid USD, never expires, and there is no subscription or monthly fee. When it runs out, calls fail with 403; top up and the same key recovers immediately.

## Troubleshooting

- **Bare `base_url`, no `/v1`.** The SDK builds `/v1/messages` itself; adding `/v1` duplicates the segment.
- **`/v1/messages/count_tokens` is not implemented** and returns 404. The SDK exposes it, but counting is not required to call `messages.create`.
- **Unsupported parameters.** Tool use, streaming, and `thinking` map to gateway behavior only when the upstream model supports them; unsupported parameters may be ignored or rejected upstream.
- **429 has no `Retry-After` header.** Implement backoff in your client instead of waiting for a hint.
- **`baseURL` vs `base_url`.** The Python SDK uses `base_url`; the TypeScript SDK uses `baseURL`.

## Related links

- [Claude Code](/docs/integrations/claude-code)
- [Models](/docs/models)
- [Prompt caching](/docs/guides/prompt-caching)
- [Billing](/docs/billing)
- [Authentication](/docs/api-reference/authentication)
- [Errors](/docs/api-reference/errors)