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.
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
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:
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:
{
"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.
- Add a search server to
.mcp.jsonat the root of your project:
{
"mcpServers": {
"search": {
"command": "npx",
"args": ["-y", "mcp-searxng"],
"env": { "SEARXNG_URL": "http://YOUR_SEARXNG_HOST:8080" }
}
}
}
- In
.claude/settings.json, deny the built-ins and grant the MCP tools:
{
"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:
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:
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)
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:
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: the same endpoint, OpenAI dialect.
- Privacy and egress: what a Host sees when it serves your agent.
- Troubleshooting: 401s, 404s, and rejected model ids.
Last checked 2026-08-07 · read as markdown at /docs/connect-anthropic.md