A chat model answers from what it learned. Ask it for something that changed last week and you get a confident guess. The usual fix is retrieval: search the web, fetch the page, feed the text back into the model. That works, and it also means a second vendor, a second key, a second bill, and tool-calling glue you now own.
If your inference already speaks the OpenAI API, there is a shorter path: the search and read tools can run on the completion call itself.
What ships
WebWright exposes two tools on POST /v1/chat/completions:
web_searchsearches the live web. Send one query and get ranked results, each with a title, URL, and snippet for grounding.web_readfetches a URL and returns clean, readable text suitable for grounding an answer. Setrender: trueto run the page in a sandboxed browser first, which reaches content built by JavaScript. Reach for it after a search when you need the whole page, not just the snippet.
Both run server-side, inside the same request path as your model call. There is no separate search endpoint for your client to configure.
The gate, stated plainly
These tools are Premium. A Premium pay-as-you-go caller sets payg_tier to premium. A Standard caller that
requests tools gets HTTP 403. If you see a 403 while testing tools,
check the tier before you check anything else.
The working call
Non-streaming, with the tools enabled:
curl https://webwright.ai/v1/chat/completions \
-H "Authorization: Bearer $WREN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wren-r1",
"payg_tier": "premium",
"stream": false,
"messages": [
{"role": "user", "content": "What changed in the OpenAI-compatible API space this week?"}
],
"tools": [{"type": "web_search"}, {"type": "web_read"}]
}' The same shape from Python with the OpenAI SDK:
from openai import OpenAI
client = OpenAI(base_url="https://webwright.ai/v1", api_key=WREN_API_KEY)
resp = client.chat.completions.create(
model="wren-r1",
stream=False,
messages=[{"role": "user", "content": "What changed in the OpenAI-compatible API space this week?"}],
extra_body={"payg_tier": "premium", "tools": [{"type": "web_search"}, {"type": "web_read"}]},
)
print(resp.choices[0].message.content) WebWright runs the model-to-tool loop server-side, up to six rounds, and sums the usage across rounds on the same meter as any other completion. You do not write the tool-calling loop yourself.
The one shape rule that bites
Streaming plus tools returns HTTP 400. If your client streams by default, this is the first error you will hit. Two fixes:
- Send
"stream": falsefor tool-enabled requests, or - Set
tool_choice: "none"when you want to keep the streaming client and opt out of tools for that call.
Search, read, or just prompt
- Prompt when the answer is stable knowledge the model already holds. Do not pay for retrieval you do not need.
- Search when the question depends on facts that change: recent releases, current pricing pages, news, docs.
- Read when a search snippet is not enough and you need the page body, for example to quote a specific section or follow a table.
A small rule of thumb: reach for a tool only when you would otherwise be guessing.
When to book a consult
If you would rather not choose between self-serve retrieval, your own vendor, and a hybrid, that is what the consult is for. One working session maps where you are, what you have tried, and what good would look like, and you leave with a prioritized first use case, an architecture, a rough cost, and the honest risks.
The WebWright Team