OpenAI and Anthropic APIs
Use XField through Chat Completions, Responses, or Anthropic Messages, with the SDKs you already have.
XField speaks three request formats natively: OpenAI Chat Completions, OpenAI Responses, and Anthropic Messages. Every model works on every one of them, so pick the format your code or tool already uses. There is nothing to translate on your side.
Routes
| Route | Format | Use it with |
|---|---|---|
POST /api/v1/chat/completions | OpenAI Chat Completions | The OpenAI SDKs, LangChain, the Vercel AI SDK, most tools |
POST /api/v1/responses | OpenAI Responses | Codex and newer OpenAI SDK code |
POST /api/v1/messages | Anthropic Messages | The Anthropic SDKs, Claude Code |
POST /api/v1/completions | Legacy completions | Older code that sends a raw prompt |
The full URL is the base URL plus the route, for example https://xfield.cloud.brutal.ai/api/v1/chat/completions. GET /api/v1/models, GET /api/v1/models/<id>, and GET /api/v1/key sit next to them.
Base URLs
The two SDK families build URLs differently, so they take different base URLs.
| Client | Base URL |
|---|---|
| OpenAI SDKs and OpenAI-compatible tools | https://xfield.cloud.brutal.ai/api/v1 |
| Anthropic SDKs and Claude Code | https://xfield.cloud.brutal.ai/api |
The OpenAI base already ends in /v1. The Anthropic SDK appends /v1/messages on its own, so you give it the origin without /v1. If you get a 404, this is the first thing to check.
Chat Completions
import os
from openai import OpenAI
client = OpenAI(base_url="https://xfield.cloud.brutal.ai/api/v1", api_key=os.environ["XFIELD_API_KEY"])
response = client.chat.completions.create(
model="glm-5.3-uncensored",
messages=[
{"role": "system", "content": "You are a senior penetration tester."},
{"role": "user", "content": "Outline a test plan for an internal web app with SSO."},
],
max_tokens=4000,
)
print(response.choices[0].message.content)Responses
response = client.responses.create(
model="deepseek-v4.1-flash-1017-uncensored",
instructions="Answer briefly.",
input="What does the SameSite cookie attribute protect against?",
reasoning={"effort": "low"},
)
print(response.output_text)Reasoning comes back as native reasoning items in output, next to the message.
Messages
import os
import anthropic
client = anthropic.Anthropic(
base_url="https://xfield.cloud.brutal.ai/api",
api_key=os.environ["XFIELD_API_KEY"],
)
message = client.messages.create(
model="deepseek-v4.1-flash-1017-uncensored",
max_tokens=4000,
system="You write detection rules.",
messages=[
{"role": "user", "content": "Write a Sigma rule for suspicious use of certutil to download files."}
],
)
print(message.content[-1].text)The response is a normal Anthropic message. When the model thinks, you get thinking blocks before the text block, which is why the example reads the last block.
The TypeScript SDKs work the same way:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://xfield.cloud.brutal.ai/api",
apiKey: process.env.XFIELD_API_KEY,
});What is passed through
The usual sampling and output controls work on every route, under the names that route uses:
max_tokens(ormax_completion_tokens, ormax_output_tokenson Responses)temperature,top_p,top_k,min_pfrequency_penalty,presence_penalty,repetition_penaltystop(orstop_sequenceson Messages),seedresponse_formatfor JSON outputtools,tool_choice,parallel_tool_callsstreamandstream_options
Reasoning controls differ per format and are covered in Reasoning. Each model also has sensible defaults, so you can leave sampling settings out entirely.
Model ids
- Use the exact ids from the Models page, such as
deepseek-v4.1-flash-1017-uncensored. They are the same on every route. - An unknown id returns a 400 whose message names the id, and the error metadata lists the valid ones.
- Always send
model. There is no default model to fall back on. - Tools that insist on a model from their own vendor, like Claude Code, let you override the model name with an environment variable. See Claude Code setup.