# Claude Code & the Anthropic SDK

Saylek speaks Anthropic's Messages API as well as the OpenAI one, so Claude Code and
the Anthropic SDKs can point at your Circle with no code change beyond a base URL and
a key. Calls ride the idle capacity of Hosts in your Circle. Your own work comes first.

This is the hosted member endpoint (your API key, dispatched to your Circle), not the
local daemon. For pointing a client at a daemon running on your own machine, see
[Connect your app](/docs/connect-your-app).

## The endpoint

Anthropic clients own the version segment: `ANTHROPIC_BASE_URL` is the origin, and the
client appends `/v1/messages` itself. So the base URL you set must **not** carry a
`/v1` suffix:

```
https://api.saylek.com
```

(The OpenAI-compatible surface, by contrast, uses `https://api.saylek.com/v1`: the extra
`/v1` is deliberate there and absent here.)

## Claude Code

```bash
export ANTHROPIC_BASE_URL="https://api.saylek.com"
export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"
unset ANTHROPIC_API_KEY

# One id, every model slot Claude Code reaches for.
MODEL="MODEL_ID"
export ANTHROPIC_MODEL="$MODEL"
export ANTHROPIC_SMALL_FAST_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$MODEL"
export CLAUDE_CODE_SUBAGENT_MODEL="$MODEL"

export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
export API_TIMEOUT_MS=600000              # self-hosted models answer slower than the stock timeout allows
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=200000   # your model's real context window

claude
```

Find a live `MODEL_ID`:

```bash
curl https://api.saylek.com/v1/models -H "Authorization: Bearer YOUR_API_KEY"
```

Set every slot, not just `ANTHROPIC_MODEL`. Any slot left unset falls back to a
`claude-*` alias your Circle does not serve, and it fails partway into a run rather than
on the first call.

Claude Code does not recognize Pool model ids, so it assumes a 200,000-token context window
and compacts against that. If the model your Circle serves has a different window, set
`CLAUDE_CODE_MAX_CONTEXT_TOKENS` to the real number. Ask the Host, since the model list
does not report it. Do **not** append `[1m]` to the model name unless the model genuinely
has a 1M window: that lets a session grow past what the Host can accept, and the request is
refused outright rather than compacted.

Every line above needs `export`. A bare `NAME=value` sets a shell variable that `claude`
never sees.

Claude Code will also warn that your claude.ai connectors are disabled. That is expected:
an auth token pointed at Saylek takes precedence over a claude.ai login, so connectors
managed there do not load. Nothing else is affected, and your own MCP servers are
untouched. Turn the notice off in `.claude/settings.json`:

```json
{
  "disableClaudeAiConnectors": true
}
```

This stops Claude Code attempting a load that cannot succeed while you are authenticated
here. To get those connectors back, remove the setting **and** unset
`ANTHROPIC_AUTH_TOKEN`, which means using a claude.ai login for that session instead of
your Circle.

## Web search

Claude Code's built-in `WebSearch` and `WebFetch` are executed by the model provider, not
by whoever serves the request, and no search engine sits behind Saylek to honor them. So
Saylek **refuses the request with a 400** naming the tool, rather than serving it without
the search and letting the model answer as though it had one. Replace them in two steps.

1. Add a search server to `.mcp.json` at the root of your project:

```json
{
  "mcpServers": {
    "search": {
      "command": "npx",
      "args": ["-y", "mcp-searxng"],
      "env": { "SEARXNG_URL": "http://YOUR_SEARXNG_HOST:8080" }
    }
  }
}
```

2. In `.claude/settings.json`, deny the built-ins and grant the MCP tools:

```json
{
  "permissions": {
    "deny": ["WebSearch", "WebFetch"],
    "allow": ["mcp__search__searxng_web_search", "mcp__search__web_url_read"]
  }
}
```

Then run `claude` once in that directory and accept the workspace-trust prompt. Until you
do, Claude Code honors the `deny` but ignores the `allow`, which is the worst of both: the
built-ins are off and your search tool is ungranted, so the model calls it every turn and is
refused every turn. A scripted `claude -p` in a fresh checkout hits this, because there is
no prompt to accept.

Both steps are needed. Leave the built-ins enabled and the model picks them every time and
never calls your MCP tool.

Both must also land in the config **that session actually loads**, which is the trap that
looks exactly like the recipe not working. Claude Code reads settings and MCP registrations
once, at startup, from the home directory the session was launched with. A session already
running will not see either change, and a session launched with a different `HOME` reads
a different config entirely and behaves as if you configured nothing. Restart, then
confirm with `/mcp` that `search` reads **connected**; if it does not, you edited a file
that session is not reading.

The `allow` half matters as much as the `deny`. Without it the model calls your search
tool and the request is refused for want of permission. Interactively you get a prompt;
a scripted `claude -p` run just fails. The names are the server's own tools prefixed
with `mcp__<server>__`, so they follow whichever server you picked: `mcp-searxng` exposes
`searxng_web_search` and `web_url_read`, not `web_search`. Check yours with `/mcp` in a
session, or `claude mcp list`.

Confirm the server is live before blaming the search:

```bash
claude mcp list
```

`✔ Connected` means the MCP server started, not that the search backend answered. For that,
query the backend directly. A 403 rather than a 200 is why results come back empty.

Any MCP search server works. SearXNG can be self-hosted, which keeps your queries off a
third-party search API; if you run one, its JSON API and bot limiter both need attention,
since the defaults refuse programmatic callers.

## The Anthropic SDK

The Python and TypeScript SDKs take a base URL and an auth token directly:

```python
from anthropic import Anthropic

client = Anthropic(base_url="https://api.saylek.com", auth_token="YOUR_API_KEY")
resp = client.messages.create(
    model="MODEL_ID",
    max_tokens=1024,
    messages=[{"role": "user", "content": "How are you doing?"}],
)
print(resp.content[0].text)
```

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

const client = new Anthropic({
  baseURL: "https://api.saylek.com",
  authToken: "YOUR_API_KEY",
});
const resp = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 1024,
  messages: [{ role: "user", content: "How are you doing?" }],
});
const [block] = resp.content;
if (block.type === "text") console.log(block.text);
```

## curl

The raw Messages API endpoint accepts either `x-api-key` or `Authorization: Bearer`, plus the
required `anthropic-version` header:

```bash
curl https://api.saylek.com/v1/messages \
  -H "x-api-key: YOUR_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "How are you doing?"}]
  }'
```

## When it can answer

Calls work while a Host in your Circle is online; when none is, requests fail until one
comes back.

A key's secret is shown once, at creation, so paste it in place of `YOUR_API_KEY` above and
keep your copy safe. To be exact about where it travels: your key **is** sent to Saylek on
every call, because that is what authenticates you. It is **not** passed on to the Host that
serves the request. If you lose a key or a key leaks, revoke it at `/account/keys` and
create another.

## Next steps

- [Use your Circle from anywhere](/docs/connect-openai): the same endpoint, OpenAI dialect.
- [Privacy and egress](/docs/privacy-and-egress): what a Host sees when it serves your agent.
- [Troubleshooting](/docs/troubleshooting): 401s, 404s, and rejected model ids.
