Strut AI Grounding Engine — Developer Integration
Strut is a web grounding engine. It discovers sources, extracts their readable content, ranks the passages that answer your query, and returns them with quotes verified against the page — optionally with a synthesized answer on top.
/v1/ground fetches live pages, isolates the passages that answer the query, and returns each with a quote taken from the page and a bracketed citation [1]. Retrieval-only responses typically land in ~1.3s; adding a synthesized answer takes about 2s.
Quick cURL Test
curl -X POST "$STRUT_BASE_URL/v1/ground" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $STRUT_API_KEY" \
-d '{
"query": "What are topological quantum qubits?",
"max_sources": 3
}'
Telemetry & Pipeline Benchmarks
Every grounding response includes microsecond-precision breakdown telemetry:
Strips ads, scripts, navbars, and converts raw HTML directly to clean semantic markdown.
Preserves header hierarchy and extracts contextual 350-word passages.
Exact phrase matching and semantic alignment ranking across evaluated chunks.
Every factual claim is aligned to exact source URLs and quotes with `[1]`, `[2]` tags.
Authentication & Keys
Every request to /v1/ground, /v1/search and /v1/extract
must carry an API key. Create one in the
developer console.
curl -X POST "https://your-deployment.example.com/v1/search" \
-H "Authorization: Bearer strut_live_..." \
-H "Content-Type: application/json" \
-d '{"query": "who invented the transistor"}'
x-api-key: strut_live_... is accepted as an alternative to the
Authorization header.
Scopes
Each key carries a scope, chosen when the key is created and enforced on every request.
| Scope | Grants |
|---|---|
full | /v1/ground, /v1/search, /v1/extract |
ground-only | /v1/ground |
search-only | /v1/search |
extract-only | /v1/extract |
Rate limits
Each key has a per-minute request limit, set when the key is created. Every response carries the current state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests permitted per minute for this key. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Seconds until the window resets. |
Retry-After | Sent with 429; seconds to wait before retrying. |
Rate limit counters are held in a shared store, so every server instance counts a key together. Windows are fixed one-minute windows: a caller can use up to twice the limit across a window boundary.
What is metered
/v1/ground, /v1/search and /v1/extract each count as
one query against your usage and your monthly spend cap, at the rate shown by
GET /v1/models. (Before October 2026 only /v1/ground was counted.)
Error responses
| Status | Meaning |
|---|---|
401 | No key supplied, or the key is unknown or revoked. |
403 | The key is valid but its scope does not cover this endpoint. |
429 | The key's per-minute rate limit is exhausted. Also sent for requests with no key when the shared no-key allowance is used up, and for the Nova model over its per-caller allowance. |
400 | The request body is not valid JSON, or a field has the wrong type or is out of range. |
402 | The account's monthly spend cap has been reached. Months are calendar months in UTC; the cap resets on the 1st. |
Every error body is JSON with an error field and, where useful, a hint.
Degraded results
When the search provider was rate limited or failed while your request ran, the response is
still returned with whatever was found and telemetry.degraded is true
(telemetry.discovery_sources names the cause). Degraded results are never cached,
so retrying gets a fresh attempt.
POST /v1/ground
Generates an end-to-end factually grounded answer from live web sources with verified quotes and bracketed citations.
Request Parameters
| Field | Type | Default | Description |
|---|---|---|---|
query | string | Required | The question or prompt to ground against the live web. |
max_sources | number | 5 | Maximum number of candidate web pages to evaluate (1–10; larger values are clamped to 10). |
prefer_domains | array | — | Domains to rank higher (at most 20), for any question. Unlike include_domains it does not exclude anything: it only moves passages from those sites up. For questions about company figures (revenue, net income, EPS) passages from the SEC, regulators, newswire services and investor-relations sites are also ranked higher automatically. |
urls | array | — | Pages to ground on instead of searching; at most 10 are used. Only public http(s) pages are fetched: private, loopback and link-local addresses are refused. query may be up to 2,000 characters. |
model | string | strut/web-grounding | Model identifier (strut/web-grounding, strut-ground-v1, groq/openai/gpt-oss-20b). |
include_answer | boolean | false | When false (default) the response returns extracted, reranked passages with verified quotes and no synthesized prose — roughly 1s (advanced) and zero token cost. Recommended when the consumer is an LLM. Set true for a written answer (adds the model call). |
search_depth | string | advanced | basic grounds on excerpts from the search index with no page fetches — the fastest mode, comparable to Tavily basic. advanced fetches and extracts each page for verified quotes. Every result carries published_date (ISO 8601) when it is known. Pages that cannot be fetched in time are filled from their search-index excerpts; each result carries content_source (page or index_excerpt), and only page results can have a verified quote. fast and deep are accepted as aliases. |
topic | string | general | general, news or finance. news (and finance, served the same way) searches news indexes only, so every result carries a publication date. |
days | integer | — | Only results published in the last N days (1–3650). |
time_range | string | — | day, week, month or year. An alternative to days. |
start_date / end_date | string | — | YYYY-MM-DD. An explicit window, inclusive; end_date defaults to today. Takes precedence over days. With any recency window, results with a known date outside it are removed, and undated results are kept only when the search index applied the window itself. The response echoes the applied window in filters. |
stream | boolean | false | Whether to stream response tokens via Server-Sent Events (SSE). |
Response Payload
{
"query": "What are topological qubits?",
"grounded_answer": "Topological qubits store quantum information in non-local topological properties of matter [1]...",
"citations": [
{
"source_index": 1,
"title": "Topological quantum computer - Wikipedia",
"url": "https://en.wikipedia.org/wiki/Topological_quantum_computer",
"verified_quote": "Topological qubits are quantum bits that store information in non-local...",
"relevance_score": 0.8842
}
],
"model": "strut/web-grounding",
"telemetry": {
"fetchMs": 450,
"extractMs": 18,
"chunkMs": 2,
"rerankMs": 6,
"synthesis_ms": 980,
"total_end_to_end_ms": 1456
}
}
POST /v1/search
Discovery and ranking without synthesis. search_depth=fast returns ranked
candidates from the index layer; deep additionally fetches each page, extracts
the readable text and reranks passages. Neither depth calls a language model.
Request Parameters
| Field | Type | Default | Description |
|---|---|---|---|
query | string | — | Required. The search query. |
search_depth | string | fast | fast or deep. |
max_results | integer | 5 | Results to return. |
include_domains | string[] | [] | Restrict discovery to these domains. |
exclude_domains | string[] | [] | Drop results from these domains. |
POST /v1/extract
Fetch a list of URLs and return their readable content as clean Markdown, with scripts, styles and navigation removed. Useful when you already know which pages you want.
| Field | Type | Default | Description |
|---|---|---|---|
urls | string[] | — | Required. URLs to fetch and extract. |
GET /v1/models
Engine capabilities, the models available for synthesis, and the metered rate.
Unauthenticated callers get the capabilities, the rate and one coarse flag
(providers.web_search_index.independent_index_configured); send an API key
to also see which providers are configured and the account store. The developer console
reads the rate from here so the price it displays cannot drift from the rate the spend
cap is enforced against.
{
"engine": "Strut Grounding Engine",
"pricing": { "cost_per_query": 0.007, "currency": "USD" },
"providers": { "web_search_index": { "mojeek_configured": false } }
}
SSE Token Streaming
Set stream: true on /v1/ground to receive the answer as
Server-Sent Events. Citations are resolved before the first token, so the source list is
available immediately and the prose arrives incrementally. Streaming implies
include_answer; a retrieval-only request has nothing to stream.
curl -N -X POST "$STRUT_BASE_URL/v1/ground" \
-H "Authorization: Bearer $STRUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "...", "stream": true, "include_answer": true}'
LangChain & LangGraph Integration
Use Strut as a verified live grounding tool in your autonomous AI agents:
from langchain.tools import tool
import requests
@tool
def strut_grounding_tool(query: str) -> str:
"""Search the live web and obtain factually grounded synthesis with verified citations."""
res = requests.post(
"$STRUT_BASE_URL/v1/ground",
json={"query": query, "max_sources": 4},
headers={"Content-Type": "application/json"}
)
if not res.ok:
return f"Lookup error: {res.text}"
data = res.json()
return data.get("grounded_answer", "")
# Attach to LangChain Agent
from langchain_openai import ChatOpenAI
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
tools = [strut_grounding_tool]
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "You are an autonomous research agent using Strut AI web grounding."),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Run Query
response = executor.invoke({"input": "What are the latest developments in quantum qubits in 2026?"})
print(response["output"])
Vercel AI SDK Integration
Wire Strut grounding seamlessly with the ai package in Next.js App Router:
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
messages,
tools: {
strutGround: tool({
description: 'Ground live web queries with sub-second verified factual citations',
parameters: z.object({
query: z.string().describe('The web search query to ground factually')
}),
execute: async ({ query }) => {
const res = await fetch('$STRUT_BASE_URL/v1/ground', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, max_sources: 4 })
});
return await res.json();
}
})
}
});
return result.toDataStreamResponse();
}
Strut Python SDK Client
Lightweight drop-in client for Python backends:
from sdk.python.strut import StrutClient
client = StrutClient(base_url="https://your-deployment.example.com", api_key=os.environ["STRUT_API_KEY"])
# 1. Factual Grounding
grounded = client.ground("What is topological quantum computing?")
print("Answer:", grounded["grounded_answer"])
# 2. Fast AI Search
search_res = client.search("Latest AI breakthroughs 2026", search_depth="fast")
for item in search_res["results"]:
print(f"• {item['title']} ({item['domain']}): {item['snippet'][:100]}...")
# 3. Batch Markdown Extraction
extracted = client.extract(["https://en.wikipedia.org/wiki/Quantum_computing"])
print("Markdown preview:", extracted["results"][0]["markdown"][:300])