Anthropic API
There are two routes. In the first, Anthropic's platform calls our MCP server. In the second, you give Claude our tool definitions and your own code makes each call. Choose the second when you need the key and the data to stay in your code, or when zero data retention matters: Anthropic's MCP connector and Agent Skills are not eligible for zero data retention.
Not open yet. Opens with early access.
On this page
Before you start
- An Anthropic API key. The examples use the model
claude-sonnet-5-5; replace it with yours. - An Understanding API key from the console. You need an account first. Keep it on your server.
- No sign-in. Both routes use the key. For claude.ai and Claude Desktop, which sign in, see Claude.
Set it up
Route one: the MCP connector
The connector is a beta. Send the header anthropic-beta: mcp-client-2025-11-20. A later version, mcp-client-2026-09-15, also pins a server's tool list, so a changed schema cannot alter behavior in the middle of a conversation; that suits a regulated application.
curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: mcp-client-2025-11-20" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-5-5", "max_tokens": 1000, "messages": [{ "role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }], "mcp_servers": [{ "type": "url", "url": "https://api.theunderstanding.company/mcp/claude", "name": "understanding", "authorization_token": "'"$UNDERSTANDING_API_KEY"'" }], "tools": [{ "type": "mcp_toolset", "mcp_server_name": "understanding" }] }'
import os import anthropic client = anthropic.Anthropic() response = client.beta.messages.create( model="claude-sonnet-5-5", max_tokens=1000, messages=[{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}], mcp_servers=[{ "type": "url", "url": "https://api.theunderstanding.company/mcp/claude", "name": "understanding", "authorization_token": os.environ["UNDERSTANDING_API_KEY"], }], tools=[{"type": "mcp_toolset", "mcp_server_name": "understanding"}], betas=["mcp-client-2025-11-20"], ) print(response.content)
Things that trip people up:
- Each server needs exactly one
mcp_toolset. To allow only some tools, setdefault_configto{ "enabled": false }and enable the tools you want underconfigs. To block one, set that tool'senabledtofalse. authorization_tokenis documented as an OAuth token. Test one call with your key. The server also accepts a bare key.- Every tool the address offers is enabled by default.
/mcp/claudeoffers eleven tools, including the three health tools (score_signal_baseline,route_member_messageandcheck_document_complete). If your application is not meant to send health data, setdefault_configto{ "enabled": false }and enable only the banking tools. The MCP connector is not zero data retention, which matters most for health content. - The URL must be public and HTTPS. The connector supports tool calls only, not resources or prompts.
- Not zero data retention. Tool definitions and results are retained under Anthropic's standard policy. Use route two if you need otherwise.
Route two: client tools
The definitions come from our server in Anthropic's shape (name, description, input_schema):
curl "https://api.theunderstanding.company/v1/tools?format=anthropic"The reply is { "format": "anthropic", "tools": [ ... ], "execute": { ... } }. When Claude stops with stop_reason of tool_use, send each tool's input to POST https://api.theunderstanding.company/v1/tools/{name} with your key, and return the answer as a tool_result. Set is_error when the call failed. Save this as understanding-anthropic.mjs:
const API = "https://api.theunderstanding.company"; const ours = (path, init = {}) => fetch(API + path, { ...init, headers: { Authorization: `Bearer ${process.env.UNDERSTANDING_API_KEY}`, "Content-Type": "application/json" }, }); const { tools } = await (await ours("/v1/tools?format=anthropic")).json(); async function runTool(name, input) { const res = await ours(`/v1/tools/${name}`, { method: "POST", body: JSON.stringify(input) }); const body = await res.json(); if (!res.ok) return { content: JSON.stringify({ error: body.error }), is_error: true }; const { abstain, ...rest } = body; // abstain first return { content: JSON.stringify({ abstain, ...rest }) }; } async function message(messages) { const res = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-5-5", max_tokens: 1000, tools, messages }), }); const json = await res.json(); if (!res.ok) throw new Error(json.error?.message ?? `HTTP ${res.status}`); return json; } const messages = [{ role: "user", content: "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }]; let reply = await message(messages); for (let round = 0; round < 5 && reply.stop_reason === "tool_use"; round++) { messages.push({ role: "assistant", content: reply.content }); const results = []; for (const block of reply.content.filter((b) => b.type === "tool_use")) { results.push({ type: "tool_result", tool_use_id: block.id, ...(await runTool(block.name, block.input)) }); } messages.push({ role: "user", content: results }); reply = await message(messages); } console.log(reply.content.filter((b) => b.type === "text").map((b) => b.text).join("\n"));
import json import os import anthropic import requests API = "https://api.theunderstanding.company" OURS = {"Authorization": f"Bearer {os.environ['UNDERSTANDING_API_KEY']}", "Content-Type": "application/json"} client = anthropic.Anthropic() tools = requests.get(f"{API}/v1/tools?format=anthropic", timeout=30).json()["tools"] def run_tool(name, tool_input): res = requests.post(f"{API}/v1/tools/{name}", headers=OURS, json=tool_input, timeout=60) body = res.json() if not res.ok: return {"content": json.dumps({"error": body["error"]}), "is_error": True} return {"content": json.dumps({"abstain": body["abstain"], **{k: v for k, v in body.items() if k != "abstain"}})} messages = [{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}] reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages) for _ in range(5): if reply.stop_reason != "tool_use": break messages.append({"role": "assistant", "content": reply.content}) results = [ {"type": "tool_result", "tool_use_id": b.id, **run_tool(b.name, b.input)} for b in reply.content if b.type == "tool_use" ] messages.append({"role": "user", "content": results}) reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages) print("".join(b.text for b in reply.content if b.type == "text"))
Notes on route two:
- The definitions are not strict. Anthropic's format is the plain schema. You can add
"strict": trueto a tool to have Claude's arguments conform to it. - Force a tool when a pipeline needs it.
tool_choiceset to{ "type": "tool", "name": "check_restricted_merchant" }makes extraction deterministic. Useautofor an interactive assistant. - For many items, skip the model. If you already know which question you are asking of a list, call the batch endpoint and use Claude only for the part that needs language.
What we tuned for this service
Route one uses /mcp/claude: short descriptions that say what a tool does and give no instructions, and one compact JSON object per result with the abstain flag first. See Claude. The guidance on when to call each tool and what to do on an abstain belongs in your system prompt or in the skill, not in the tool descriptions. Route two uses our standard descriptions.
Check that it works
The model should call check_restricted_merchant with merchant set to DRAFTKINGS and mcc set to 7995. Over route one the response holds an mcp_tool_use block and an mcp_tool_result block. The result text is one JSON object:
{"abstain":false,"answer":true,"probability":0.97,"reasons":["mcc_7995","rule_gambling_category","kind_sportsbook"],"decision":"restricted_merchant","type":"predicate"}For a case that abstains, ask about LUCKY COINS LLC with code 5816: abstain is true, and Claude should say it is unsure. The numbers show the format. They are not measured results.
If it doesn't connect
- The API rejects the request about
mcp_servers. Check theanthropic-betaheader and that the server istypeurlwith an HTTPS address. Each server needs onemcp_toolsetintools. - The tool result says
unauthorized. The key did not arrive as a Bearer token. Try a different field, or use route two. Check it withcurl https://api.theunderstanding.company/v1/usage -H "Authorization: Bearer $UNDERSTANDING_API_KEY". - A call fails with
sensitive_data. The arguments held what looks like a card, Social Security or account number. Remove it. See Errors. - The model never calls the tool. Say in your system prompt when to use it. Descriptions on our side do not instruct, on purpose.
- Too many requests. The limit is 60 requests a minute per key. See Rate limits.
# Anthropic API
There are two routes. In the first, Anthropic's platform calls our MCP server. In the second, you give Claude our tool definitions and your own code makes each call. Choose the second when you need the key and the data to stay in your code, or when zero data retention matters: Anthropic's MCP connector and Agent Skills are not eligible for zero data retention.
Not open yet. Opens with early access.
## Before you start
- An Anthropic API key. The examples use the model `claude-sonnet-5-5`; replace it with yours.
- An Understanding API key from the console. You need an account first. Keep it on your server.
- No sign-in. Both routes use the key. For claude.ai and Claude Desktop, which sign in, see [Claude](/api/docs/integrations/claude).
## Set it up
### Route one: the MCP connector
The connector is a beta. Send the header `anthropic-beta: mcp-client-2025-11-20`. A later version, `mcp-client-2026-09-15`, also pins a server's tool list, so a changed schema cannot alter behavior in the middle of a conversation; that suits a regulated application.
The curl example:
```bash
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-5-5",
"max_tokens": 1000,
"messages": [{ "role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }],
"mcp_servers": [{
"type": "url",
"url": "https://api.theunderstanding.company/mcp/claude",
"name": "understanding",
"authorization_token": "'"$UNDERSTANDING_API_KEY"'"
}],
"tools": [{ "type": "mcp_toolset", "mcp_server_name": "understanding" }]
}'
```
The Python example:
```
import os
import anthropic
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-5-5",
max_tokens=1000,
messages=[{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}],
mcp_servers=[{
"type": "url",
"url": "https://api.theunderstanding.company/mcp/claude",
"name": "understanding",
"authorization_token": os.environ["UNDERSTANDING_API_KEY"],
}],
tools=[{"type": "mcp_toolset", "mcp_server_name": "understanding"}],
betas=["mcp-client-2025-11-20"],
)
print(response.content)
```
Things that trip people up:
- Each server needs exactly one `mcp_toolset`. To allow only some tools, set `default_config` to `{ "enabled": false }` and enable the tools you want under `configs`. To block one, set that tool's `enabled` to `false`.
- `authorization_token` is documented as an OAuth token. Test one call with your key. The server also accepts a bare key.
- Every tool the address offers is enabled by default. `/mcp/claude` offers eleven tools, including the three health tools (`score_signal_baseline`, `route_member_message` and `check_document_complete`). If your application is not meant to send health data, set `default_config` to `{ "enabled": false }` and enable only the banking tools. The MCP connector is not zero data retention, which matters most for health content.
- The URL must be public and HTTPS. The connector supports tool calls only, not resources or prompts.
- Not zero data retention. Tool definitions and results are retained under Anthropic's standard policy. Use route two if you need otherwise.
### Route two: client tools
The definitions come from our server in Anthropic's shape (`name`, `description`, `input_schema`):
```
curl "https://api.theunderstanding.company/v1/tools?format=anthropic"
```
The reply is `{ "format": "anthropic", "tools": [ ... ], "execute": { ... } }`. When Claude stops with `stop_reason` of `tool_use`, send each tool's input to `POST https://api.theunderstanding.company/v1/tools/{name}` with your key, and return the answer as a `tool_result`. Set `is_error` when the call failed. Save this as `understanding-anthropic.mjs`:
The JavaScript example:
```
const API = "https://api.theunderstanding.company";
const ours = (path, init = {}) =>
fetch(API + path, {
...init,
headers: { Authorization: `Bearer ${process.env.UNDERSTANDING_API_KEY}`, "Content-Type": "application/json" },
});
const { tools } = await (await ours("/v1/tools?format=anthropic")).json();
async function runTool(name, input) {
const res = await ours(`/v1/tools/${name}`, { method: "POST", body: JSON.stringify(input) });
const body = await res.json();
if (!res.ok) return { content: JSON.stringify({ error: body.error }), is_error: true };
const { abstain, ...rest } = body; // abstain first
return { content: JSON.stringify({ abstain, ...rest }) };
}
async function message(messages) {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: { "x-api-key": process.env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json" },
body: JSON.stringify({ model: "claude-sonnet-5-5", max_tokens: 1000, tools, messages }),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error?.message ?? `HTTP ${res.status}`);
return json;
}
const messages = [{ role: "user", content: "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?" }];
let reply = await message(messages);
for (let round = 0; round < 5 && reply.stop_reason === "tool_use"; round++) {
messages.push({ role: "assistant", content: reply.content });
const results = [];
for (const block of reply.content.filter((b) => b.type === "tool_use")) {
results.push({ type: "tool_result", tool_use_id: block.id, ...(await runTool(block.name, block.input)) });
}
messages.push({ role: "user", content: results });
reply = await message(messages);
}
console.log(reply.content.filter((b) => b.type === "text").map((b) => b.text).join("\n"));
```
The Python example:
```
import json
import os
import anthropic
import requests
API = "https://api.theunderstanding.company"
OURS = {"Authorization": f"Bearer {os.environ['UNDERSTANDING_API_KEY']}", "Content-Type": "application/json"}
client = anthropic.Anthropic()
tools = requests.get(f"{API}/v1/tools?format=anthropic", timeout=30).json()["tools"]
def run_tool(name, tool_input):
res = requests.post(f"{API}/v1/tools/{name}", headers=OURS, json=tool_input, timeout=60)
body = res.json()
if not res.ok:
return {"content": json.dumps({"error": body["error"]}), "is_error": True}
return {"content": json.dumps({"abstain": body["abstain"], **{k: v for k, v in body.items() if k != "abstain"}})}
messages = [{"role": "user", "content": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?"}]
reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages)
for _ in range(5):
if reply.stop_reason != "tool_use":
break
messages.append({"role": "assistant", "content": reply.content})
results = [
{"type": "tool_result", "tool_use_id": b.id, **run_tool(b.name, b.input)}
for b in reply.content
if b.type == "tool_use"
]
messages.append({"role": "user", "content": results})
reply = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000, tools=tools, messages=messages)
print("".join(b.text for b in reply.content if b.type == "text"))
```
Notes on route two:
- The definitions are not strict. Anthropic's format is the plain schema. You can add `"strict": true` to a tool to have Claude's arguments conform to it.
- Force a tool when a pipeline needs it. `tool_choice` set to `{ "type": "tool", "name": "check_restricted_merchant" }` makes extraction deterministic. Use `auto` for an interactive assistant.
- For many items, skip the model. If you already know which question you are asking of a list, call [the batch endpoint](/api/docs/integrations/banking-apps#batch-send-many-at-once) and use Claude only for the part that needs language.
## What we tuned for this service
Route one uses `/mcp/claude`: short descriptions that say what a tool does and give no instructions, and one compact JSON object per result with the abstain flag first. See [Claude](/api/docs/integrations/claude). The guidance on when to call each tool and what to do on an abstain belongs in your system prompt or in the skill, not in the tool descriptions. Route two uses our standard descriptions.
## Check that it works
The model should call `check_restricted_merchant` with `merchant` set to `DRAFTKINGS` and `mcc` set to `7995`. Over route one the response holds an `mcp_tool_use` block and an `mcp_tool_result` block. The result text is one JSON object:
```
{"abstain":false,"answer":true,"probability":0.97,"reasons":["mcc_7995","rule_gambling_category","kind_sportsbook"],"decision":"restricted_merchant","type":"predicate"}
```
For a case that abstains, ask about LUCKY COINS LLC with code 5816: `abstain` is `true`, and Claude should say it is unsure. The numbers show the format. They are not measured results.
## If it doesn't connect
- The API rejects the request about `mcp_servers`. Check the `anthropic-beta` header and that the server is `type` `url` with an HTTPS address. Each server needs one `mcp_toolset` in `tools`.
- The tool result says `unauthorized`. The key did not arrive as a Bearer token. Try a different field, or use route two. Check it with `curl https://api.theunderstanding.company/v1/usage -H "Authorization: Bearer $UNDERSTANDING_API_KEY"`.
- A call fails with `sensitive_data`. The arguments held what looks like a card, Social Security or account number. Remove it. See [Errors](/api/docs/errors).
- The model never calls the tool. Say in your system prompt when to use it. Descriptions on our side do not instruct, on purpose.
- Too many requests. The limit is 60 requests a minute per key. See [Rate limits](/api/docs/limits#rate-limits).
Source: /api/docs/integrations/anthropic-api