xAI API
There are two routes. In the first, xAI's platform connects to our MCP server. In the second, you give Grok our function definitions and your own code makes each call. For Grok Bot, Grok Build and the Grok chat app, see Grok Bot.
Not open yet. Opens with early access.
On this page
Before you start
- An xAI API key. The examples use
grok-4.7, the model in xAI's own examples. Which models support remote MCP is not documented; replace it with yours. - An Understanding API key from the console. You need an account first. Keep it on your server.
- No sign-in. xAI's remote MCP tool has no sign-in flow. It sends a token or headers that you give it.
Set it up
Route one: the remote MCP tool
curl https://api.x.ai/v1/responses \ -H "Authorization: Bearer $XAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "grok-4.7", "input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", "tools": [{ "type": "mcp", "server_url": "https://api.theunderstanding.company/mcp/grok", "server_label": "understanding", "server_description": "Closed questions about bank transactions and member support messages. Read-only. Each answer has a probability and an abstain flag.", "authorization": "'"$UNDERSTANDING_API_KEY"'", "allowed_tools": ["check_restricted_merchant", "classify_spend_type", "match_receipt", "pair_refund", "check_return_window", "route_support_message", "ask_closed_question"] }] }'
import os import requests res = requests.post( "https://api.x.ai/v1/responses", headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"}, json={ "model": "grok-4.7", "input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", "tools": [{ "type": "mcp", "server_url": "https://api.theunderstanding.company/mcp/grok", "server_label": "understanding", "server_description": "Closed questions about bank transactions and member support messages. Read-only. Each answer has a probability and an abstain flag.", "authorization": os.environ["UNDERSTANDING_API_KEY"], "allowed_tools": ["check_restricted_merchant", "classify_spend_type", "match_receipt", "pair_refund", "check_return_window", "route_support_message", "ask_closed_question"], }], }, timeout=120, ) res.raise_for_status() print(res.json())
Three things matter here.
authorizationmay not add the word Bearer. xAI says only that it is "a token set in the Authorization header". So the example sends the bare key, and the/mcp/grokaddress accepts a bare key as well asBearer <key>. If it is refused, send"headers": { "Authorization": "Bearer <key>" }instead and dropauthorization.- Always set
allowed_tools. Without it xAI injects every tool definition the server lists into the model's context. The list above is the seven banking question tools./mcp/grokalready offers only those seven, with the shortest descriptions, and none of the three health tools. require_approvalandconnector_idare not supported on the xAI API. Do not send them.
Only Streamable HTTP and SSE are supported, and ours is Streamable HTTP. The server must be reachable from the internet.
Route two: function calling
The definitions come from our server in the shape xAI shares with OpenAI's strict form:
curl "https://api.theunderstanding.company/v1/tools?format=xai"The reply is { "format": "xai", "tools": [ ... ], "execute": { ... } }. xAI requires a parameters object at the root, which these have, and allows up to 350 tools in a request. parallel_tool_calls defaults to true; the tools are safe to run in parallel. Send each call's arguments to POST https://api.theunderstanding.company/v1/tools/{name} with your key and return the answer as function_call_output. Save this as understanding-xai.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=xai")).json(); async function runTool(name, args) { const res = await ours(`/v1/tools/${name}`, { method: "POST", body: JSON.stringify(args) }); const body = await res.json(); if (!res.ok) return JSON.stringify({ error: body.error }); const { abstain, ...rest } = body; // abstain first return JSON.stringify({ abstain, ...rest }); } async function respond(request) { const res = await fetch("https://api.x.ai/v1/responses", { method: "POST", headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify(request), }); const json = await res.json(); if (!res.ok) throw new Error(json.error?.message ?? `HTTP ${res.status}`); return json; } const model = "grok-4.7"; let response = await respond({ model, input: "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", tools }); for (let round = 0; round < 5; round++) { const calls = response.output.filter((item) => item.type === "function_call"); if (calls.length === 0) break; const outputs = []; for (const call of calls) { outputs.push({ type: "function_call_output", call_id: call.call_id, output: await runTool(call.name, JSON.parse(call.arguments || "{}")) }); } response = await respond({ model, previous_response_id: response.id, input: outputs, tools }); } const message = response.output.find((item) => item.type === "message"); console.log(message?.content?.map((part) => part.text).join("\n"));
import json import os import requests API = "https://api.theunderstanding.company" OURS = {"Authorization": f"Bearer {os.environ['UNDERSTANDING_API_KEY']}", "Content-Type": "application/json"} THEIRS = {"Authorization": f"Bearer {os.environ['XAI_API_KEY']}", "Content-Type": "application/json"} MODEL = "grok-4.7" tools = requests.get(f"{API}/v1/tools?format=xai", timeout=30).json()["tools"] def run_tool(name, args): res = requests.post(f"{API}/v1/tools/{name}", headers=OURS, json=args, timeout=60) body = res.json() if not res.ok: return json.dumps({"error": body["error"]}) return json.dumps({"abstain": body["abstain"], **{k: v for k, v in body.items() if k != "abstain"}}) def respond(request): res = requests.post("https://api.x.ai/v1/responses", headers=THEIRS, json=request, timeout=120) res.raise_for_status() return res.json() response = respond({"model": MODEL, "input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", "tools": tools}) for _ in range(5): calls = [item for item in response["output"] if item["type"] == "function_call"] if not calls: break outputs = [ {"type": "function_call_output", "call_id": c["call_id"], "output": run_tool(c["name"], json.loads(c["arguments"] or "{}"))} for c in calls ] response = respond({"model": MODEL, "previous_response_id": response["id"], "input": outputs, "tools": tools}) for item in response["output"]: if item["type"] == "message": print("\n".join(part["text"] for part in item["content"]))
With function calling, xAI never sees your key, and nothing goes through xAI's MCP connector.
What we tuned for this service
Route one uses /mcp/grok. It has the shortest descriptions, offers only the seven banking question tools (no get_usage, which would only add to the model's context, and no health tools), returns compact JSON with the abstain flag first, contains no text that reads like an instruction, and accepts a bare key in the Authorization header. Route two uses our standard descriptions in the shared strict form.
Check that it works
The model should call check_restricted_merchant with merchant set to DRAFTKINGS and mcc set to 7995. The result 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 Grok should say it is unsure. The numbers show the format. They are not measured results. xAI may show the tool name prefixed with the server label; that is on its side.
If it doesn't connect
- xAI says the server refused the credential. Switch between
authorizationandheaderswith an explicitBearer, as above. Check the key withcurl https://api.theunderstanding.company/v1/usage -H "Authorization: Bearer $UNDERSTANDING_API_KEY". - The request fails because of
require_approval. xAI does not support it. Remove it. - The context is large and the answers are poor. You left out
allowed_tools, so every tool definition is in the context. - A call fails with
sensitive_data. The arguments held what looks like a card, Social Security or account number. Remove it. See Errors. - Too many requests. The limit is 60 requests a minute per key. See Rate limits.
# xAI API
There are two routes. In the first, xAI's platform connects to our MCP server. In the second, you give Grok our function definitions and your own code makes each call. For Grok Bot, Grok Build and the Grok chat app, see [Grok Bot](/api/docs/integrations/grok-bot).
Not open yet. Opens with early access.
## Before you start
- An xAI API key. The examples use `grok-4.7`, the model in xAI's own examples. Which models support remote MCP is not documented; replace it with yours.
- An Understanding API key from the console. You need an account first. Keep it on your server.
- No sign-in. xAI's remote MCP tool has no sign-in flow. It sends a token or headers that you give it.
## Set it up
### Route one: the remote MCP tool
The curl example:
```bash
curl https://api.x.ai/v1/responses \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-4.7",
"input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?",
"tools": [{
"type": "mcp",
"server_url": "https://api.theunderstanding.company/mcp/grok",
"server_label": "understanding",
"server_description": "Closed questions about bank transactions and member support messages. Read-only. Each answer has a probability and an abstain flag.",
"authorization": "'"$UNDERSTANDING_API_KEY"'",
"allowed_tools": ["check_restricted_merchant", "classify_spend_type", "match_receipt", "pair_refund", "check_return_window", "route_support_message", "ask_closed_question"]
}]
}'
```
The Python example:
```
import os
import requests
res = requests.post(
"https://api.x.ai/v1/responses",
headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
json={
"model": "grok-4.7",
"input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?",
"tools": [{
"type": "mcp",
"server_url": "https://api.theunderstanding.company/mcp/grok",
"server_label": "understanding",
"server_description": "Closed questions about bank transactions and member support messages. Read-only. Each answer has a probability and an abstain flag.",
"authorization": os.environ["UNDERSTANDING_API_KEY"],
"allowed_tools": ["check_restricted_merchant", "classify_spend_type", "match_receipt", "pair_refund", "check_return_window", "route_support_message", "ask_closed_question"],
}],
},
timeout=120,
)
res.raise_for_status()
print(res.json())
```
Three things matter here.
- `authorization` may not add the word Bearer. xAI says only that it is "a token set in the Authorization header". So the example sends the bare key, and the `/mcp/grok` address accepts a bare key as well as `Bearer <key>`. If it is refused, send `"headers": { "Authorization": "Bearer <key>" }` instead and drop `authorization`.
- Always set `allowed_tools`. Without it xAI injects every tool definition the server lists into the model's context. The list above is the seven banking question tools. `/mcp/grok` already offers only those seven, with the shortest descriptions, and none of the three health tools.
- `require_approval` and `connector_id` are not supported on the xAI API. Do not send them.
Only Streamable HTTP and SSE are supported, and ours is Streamable HTTP. The server must be reachable from the internet.
### Route two: function calling
The definitions come from our server in the shape xAI shares with OpenAI's strict form:
```
curl "https://api.theunderstanding.company/v1/tools?format=xai"
```
The reply is `{ "format": "xai", "tools": [ ... ], "execute": { ... } }`. xAI requires a parameters object at the root, which these have, and allows up to 350 tools in a request. `parallel_tool_calls` defaults to true; the tools are safe to run in parallel. Send each call's arguments to `POST https://api.theunderstanding.company/v1/tools/{name}` with your key and return the answer as `function_call_output`. Save this as `understanding-xai.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=xai")).json();
async function runTool(name, args) {
const res = await ours(`/v1/tools/${name}`, { method: "POST", body: JSON.stringify(args) });
const body = await res.json();
if (!res.ok) return JSON.stringify({ error: body.error });
const { abstain, ...rest } = body; // abstain first
return JSON.stringify({ abstain, ...rest });
}
async function respond(request) {
const res = await fetch("https://api.x.ai/v1/responses", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.XAI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(request),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error?.message ?? `HTTP ${res.status}`);
return json;
}
const model = "grok-4.7";
let response = await respond({ model, input: "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", tools });
for (let round = 0; round < 5; round++) {
const calls = response.output.filter((item) => item.type === "function_call");
if (calls.length === 0) break;
const outputs = [];
for (const call of calls) {
outputs.push({ type: "function_call_output", call_id: call.call_id, output: await runTool(call.name, JSON.parse(call.arguments || "{}")) });
}
response = await respond({ model, previous_response_id: response.id, input: outputs, tools });
}
const message = response.output.find((item) => item.type === "message");
console.log(message?.content?.map((part) => part.text).join("\n"));
```
The Python example:
```
import json
import os
import requests
API = "https://api.theunderstanding.company"
OURS = {"Authorization": f"Bearer {os.environ['UNDERSTANDING_API_KEY']}", "Content-Type": "application/json"}
THEIRS = {"Authorization": f"Bearer {os.environ['XAI_API_KEY']}", "Content-Type": "application/json"}
MODEL = "grok-4.7"
tools = requests.get(f"{API}/v1/tools?format=xai", timeout=30).json()["tools"]
def run_tool(name, args):
res = requests.post(f"{API}/v1/tools/{name}", headers=OURS, json=args, timeout=60)
body = res.json()
if not res.ok:
return json.dumps({"error": body["error"]})
return json.dumps({"abstain": body["abstain"], **{k: v for k, v in body.items() if k != "abstain"}})
def respond(request):
res = requests.post("https://api.x.ai/v1/responses", headers=THEIRS, json=request, timeout=120)
res.raise_for_status()
return res.json()
response = respond({"model": MODEL, "input": "Is the merchant DRAFTKINGS, MCC 7995, a restricted merchant?", "tools": tools})
for _ in range(5):
calls = [item for item in response["output"] if item["type"] == "function_call"]
if not calls:
break
outputs = [
{"type": "function_call_output", "call_id": c["call_id"], "output": run_tool(c["name"], json.loads(c["arguments"] or "{}"))}
for c in calls
]
response = respond({"model": MODEL, "previous_response_id": response["id"], "input": outputs, "tools": tools})
for item in response["output"]:
if item["type"] == "message":
print("\n".join(part["text"] for part in item["content"]))
```
With function calling, xAI never sees your key, and nothing goes through xAI's MCP connector.
## What we tuned for this service
Route one uses `/mcp/grok`. It has the shortest descriptions, offers only the seven banking question tools (no `get_usage`, which would only add to the model's context, and no health tools), returns compact JSON with the abstain flag first, contains no text that reads like an instruction, and accepts a bare key in the `Authorization` header. Route two uses our standard descriptions in the shared strict form.
## Check that it works
The model should call `check_restricted_merchant` with `merchant` set to `DRAFTKINGS` and `mcc` set to `7995`. The result 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 Grok should say it is unsure. The numbers show the format. They are not measured results. xAI may show the tool name prefixed with the server label; that is on its side.
## If it doesn't connect
- xAI says the server refused the credential. Switch between `authorization` and `headers` with an explicit `Bearer`, as above. Check the key with `curl https://api.theunderstanding.company/v1/usage -H "Authorization: Bearer $UNDERSTANDING_API_KEY"`.
- The request fails because of `require_approval`. xAI does not support it. Remove it.
- The context is large and the answers are poor. You left out `allowed_tools`, so every tool definition is in the context.
- 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).
- Too many requests. The limit is 60 requests a minute per key. See [Rate limits](/api/docs/limits#rate-limits).
Source: /api/docs/integrations/xai-api