RESEARCH A CONCEPT
✗ "biomimicry architecture"
✓ "buildings whose cooling systems copy termite mounds"
Describe the thing rather than naming the category. Pair with mode: "pro" and limit: 25.
ORBITA API / v1
Orbita is a source and context API built for agents rather than a general chatbot. Five fixed depth modes return ranked sources; an optional Auto router resolves a question to one of those modes. An optional Context layer then returns either a synthesized brief or an evidence package assembled from original source blocks.
There is one endpoint. You send a question in natural language; you get back the pages that answer it, ranked, with their text. The fixed modes are Fast, Standard, Pro, Deep and Ultra; mode: "auto" selects one of them without exposing a sixth candidate budget.
What makes that worth a separate product is what is not in the normal response path: no second call to fetch page content, and no separate date-extraction call. When a trustworthy publication date cannot be found, published is null rather than an invented crawl date. If you enable Context, Evidence mode still returns original blocks; Summary mode is optional and review-gated.
Orbita is not a general chatbot, not a crawler you point at a URL, and not a Google proxy. It can optionally build a compact context or an evidence package, but it does not pretend that a generated brief is the source: Evidence mode keeps source blocks, IDs, dates and citations available for verification. If you want an essay with footnotes, ask your own model; Orbita gives it grounded material and stays out of the way.
Gravity is Orbita’s evidence-quality technology. It reduces irrelevant page material while keeping the useful source material verifiable. Customers receive cleaner context, source links and citation-ready evidence; the internal extraction and ranking implementation is intentionally not public.
Frozen 2026-09-12 run: five public Ukrainian admissions sites and ten pages. Exact-match scoring was used. This is an Orbita before/after measurement, not a head-to-head extraction benchmark.
| Product | Published extraction facts |
|---|---|
| Exa | Contents costs $1 per 1,000 pages for each requested content type. Its own 250-URL evaluation reports 89.3 accuracy, 96.7 code recall and 91.9 table recall. |
| Tavily | Up to 20 URLs per call; query-focused output supports 1–5 chunks of at most 500 characters. At PAYG rates, Basic and Advanced equal about $1.60 and $3.20 per 1,000 successful URLs. |
| Linkup | Fetch accepts one URL per call and costs $1 per 1,000 pages without JavaScript or $5 per 1,000 with JavaScript. Errors are not charged. |
| Firecrawl | Basic scrape costs one credit per page. Annual self-serve tiers imply roughly $0.60–$3.20 per 1,000 pages; its own 1,000-URL evaluation reports 96% success and 3.387 s p95. |
Official references checked 2026-09-13: Exa pricing and vendor evaluation; Tavily Extract and credits; Linkup pricing; Firecrawl pricing and vendor evaluation. Vendor-run quality figures are not mixed with Orbita’s measurement.
Three things: a key, a request, a response. If you have curl you are ninety seconds away.
Create an account and press New key; it starts with orb_live_. It is shown in full exactly once, so copy it then. Keep it server-side — a key in a browser bundle is a key someone else is now using.
export ORBITA_API_KEY="orb_live_your_key_here"
curl https://api.orbita.dev/v1/search \
-H "Authorization: Bearer $ORBITA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "biomimicry in contemporary architecture",
"limit": 10,
"content": "text"
}'
import os, httpx
r = httpx.post(
"https://api.orbita.dev/v1/search",
headers={"Authorization": f"Bearer {os.environ['ORBITA_API_KEY']}"},
json={
"query": "biomimicry in contemporary architecture",
"limit": 10,
"content": "text",
},
timeout=30,
)
r.raise_for_status()
for hit in r.json()["results"]:
print(hit["published"], hit["title"], hit["url"])
const res = await fetch("https://api.orbita.dev/v1/search", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ORBITA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: "biomimicry in contemporary architecture",
limit: 10,
content: "text",
}),
});
if (!res.ok) throw new Error(`orbita ${res.status}: ${await res.text()}`);
const { results } = await res.json();
for (const hit of results) console.log(hit.published, hit.title, hit.url);
body, _ := json.Marshal(map[string]any{
"query": "biomimicry in contemporary architecture",
"limit": 10,
"content": "text",
})
req, _ := http.NewRequest("POST", "https://api.orbita.dev/v1/search", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("ORBITA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
You will get an object with results, usage and took_ms. The whole shape is in Response.
Runnable variants for cURL, Python, TypeScript and Go are kept in prototype/examples/phase2-search.*; they use the local session-token contract and do not contain production secrets.
The public Search API uses a bearer key on every request; it does not accept signed URLs or dashboard cookies.
Authorization: Bearer orb_live_...
Keys are scoped to a project and can be rotated without downtime: create the new one, deploy it, delete the old one. A deleted key stops working immediately — there is no grace window, so do it in that order.
The dashboard itself is different: signup/signin create an opaque server session in an HttpOnly; SameSite=Strict cookie, and every state-changing browser request carries a CSRF token. Session cookies are never valid as corpus-admin credentials.
POST https://api.orbita.dev/v1/search
The only endpoint you need. It takes a question and returns ranked pages.
Every field except query has a default that is sensible on its own, so this is what a request looks like when you actually use all of it — not what you have to send.
{
"query": "how Ukrainian grain export corridors changed in 2026",
"mode": "auto",
"max_mode": "deep",
"max_cost_usd": 0.01049,
"limit": 25,
"content": "text",
"max_chars": 4000,
"lang": ["uk", "en"],
"published_after": "2026-01-01",
"domains": {
"include": ["reuters.com", "pravda.com.ua"],
"exclude": ["pinterest.com"]
},
"highlights": 3,
"timeout_ms": 4000,
"links": "relevant",
"context": {
"enabled": true,
"strategy": "evidence",
"max_tokens": 2000,
"citations": true,
"dates": true
}
}
Use mode: "auto" when the client should select depth. Evidence Context returns original blocks with citation IDs; Summary Context is optional and falls back to Evidence when its provider is unavailable.
curl https://api.orbita.dev/v1/search \
-H "Authorization: Bearer $ORBITA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"compare current vector database options","mode":"auto","max_mode":"deep","links":"relevant","context":{"enabled":true,"strategy":"evidence","max_tokens":1000,"citations":true,"dates":true}}'
| Field | Type | Default | What it does |
|---|---|---|---|
query required | string | — | A question or a description in natural language. Write it the way you would ask a colleague; keyword soup ranks worse here, not better. Hard limit 2 000 characters. |
limit | integer | mode maximum | How many results to return, up to the selected mode's result budget. Asking for fewer does not change that mode's price. |
content | enum | "text" | "text" — clean page text. "none" — titles and URLs only, with a smaller response; its production latency effect is not yet measured. "markdown" — text with headings and lists preserved. |
max_chars | integer | 2000 | Characters of page text per result, 200–20 000. This is the single biggest lever on your token bill: 25 results at 2 000 characters is roughly 12 k tokens, at 20 000 it is 120 k. |
mode | enum | "auto" | Five fixed modes: Fast 20→5, Standard 100→10, Pro 200→25, Deep 300→50, Ultra 500→100. Auto resolves to one fixed mode and reports why. |
max_mode | enum | "ultra" | Auto ceiling. It never selects a deeper mode than this. |
max_cost_usd | number | — | Per-request USD ceiling in the same unit as usage.cost_usd. Auto downgrades only within max_mode; a request fails if no mode fits. |
max_results | integer | 100 | Client ceiling. The resolved mode and limit are both capped by it. |
lang | string[] | auto | ISO 639-1 codes to prefer, e.g. ["uk","en"]. Left out, the language of the query decides. This is a preference, not a filter: a decisive English source still outranks a weak Ukrainian one. |
published_afterpublished_before | date | — | YYYY-MM-DD. Filters on the publication date, not the crawl date. Pages we could not date are excluded when either bound is set — say so out loud rather than let a filter silently drop a third of the web. |
domains.include | string[] | — | Up to 50 hosts. Subdomains are included: bbc.co.uk also matches news.bbc.co.uk. |
domains.exclude | string[] | — | Up to 50 hosts to drop. Applied after ranking, so excluding a domain promotes what was behind it rather than shortening the list. |
highlights | integer | 0 | 0–5 passages per result that most directly answer the query, with character offsets into text. Use these for citations instead of asking the model to find the quote again. |
timeout_ms | integer | 15000 | 500–30 000. On timeout you get 504 and are not billed. The staging default was raised after a measured cold-start exceeded 8 seconds; set it below your agent's own deadline. |
links | enum | "relevant" | "none", relevant discovered links, or "all". Relevant links are classified as official, docs, support, careers, social, phone or email where the source exposes them. |
context.enabled | boolean | false | Optional Context/Compact layer. False keeps the normal retrieval behavior. |
context.strategy | enum | "evidence" | evidence has Ling select source-scoped IDs and the server reconstruct original blocks; summary asks inclusionai/ling-3.0-flash for a shorter synthesis and falls back to deterministic Evidence. |
context.max_tokens | integer | 1000 | One of 500 / 1 000 / 2 000 / 4 000. This output ceiling is independent from the mode input maximum of 3 000 / 5 000 / 10 000 / 20 000 / 50 000 tokens. Input is not padded. |
context.citations / dates | boolean | true | Controls citation IDs and publication/modified dates in the context package. Evidence text itself is never rewritten. |
{
"id": "req_01JZQ8F3M2K9",
"query": {
"text": "how Ukrainian grain export corridors changed in 2026",
"language": "en"
},
"requested_mode": "auto",
"resolved_mode": "pro",
"selection_reason": "multi_source_current_events",
"results": [
{
"url": "https://www.reuters.com/markets/commodities/…",
"title": "Black Sea corridor volumes recover to pre-war levels",
"site": "reuters.com",
"published": "2026-06-18",
"retrieved": "2026-08-06T04:12:51Z",
"language": "en",
"score": 0.914,
"text": "Grain shipments through the Black Sea corridor …",
"highlights": [
{ "text": "volumes reached 5.2 million tonnes in May", "start": 412, "end": 454 }
]
}
],
"context": { "enabled": true, "strategy": "evidence", "input_tokens": 1470, "output_tokens": 1470, "max_tokens": 2000 },
"usage": { "searches": 1, "results": 25, "context_input_tokens": 1470, "context_output_tokens": 1470, "cost_usd": 0.00799 },
"took_ms": 138
}
| Field | Type | Notes |
|---|---|---|
id | string | Request id. Quote it when you report a problem; it is how we find your call in the logs. |
query.language | string | What language we decided the question was in. Worth logging — a wrong guess here explains most surprising result sets. |
results[].published | date | null | null when the page carries no trustworthy date. We would rather admit that than invent one from the crawl time, which is what a date on every single result usually means. |
results[].retrieved | datetime | When we last fetched the page. The gap between this and published tells you how stale the text may be. |
results[].score | float | 0–1, comparable within this response only. Do not persist it, do not threshold on it, do not compare it across queries. |
results[].text | string | null | Page text, boilerplate removed, truncated to max_chars on a word boundary. null when content: "none". |
results[].highlights | array | Offsets are into text, so text.slice(start, end) gives you the exact quote to cite. |
usage.cost_usd | float | What this call actually cost, on the response. No end-of-month arithmetic to work out where the money went. |
requested_mode / resolved_mode | string | Auto preserves what the client asked for and reports the fixed mode actually used. |
selection_reason | string | Stable router reason such as multi_source_current_events; it is a routing explanation, not a relevance score. |
context | object | null | Optional Summary or Evidence package. Evidence blocks are original source blocks and carry section_id, block_id and passage_id when citations are enabled. |
usage.results, context_input_tokens, context_output_tokens | integer | Customer-visible result and optional Context usage counters. |
took_ms | integer | Server-side time. Your wall clock will be this plus the round trip — see the reach map for what that is from where you are. |
The failure mode we see most often is a query written for a keyword engine. Choose a fixed depth when you know the budget; use Auto when the client should choose.
RESEARCH A CONCEPT
✗ "biomimicry architecture"
✓ "buildings whose cooling systems copy termite mounds"
Describe the thing rather than naming the category. Pair with mode: "pro" and limit: 25.
COMPANY / CONTACT LOOKUP
✓ mode: "fast", links: "relevant"
Ask for the official site, docs, support, careers, GitHub, social handles, email or phone. Orbita returns only links extracted from indexed source content.
TECHNICAL QUESTION
✓ mode: "pro", context.strategy: "evidence"
Evidence mode returns raw blocks and stable IDs so the answering model can preserve code, specifications, numbers and negations.
NEWS BRIEFING
✓ mode: "pro" or "deep", published_after
Use Pro for a focused briefing, Deep for many independent sources. Auto resolves current-events questions to a safe fixed mode.
BROAD / MAXIMUM RECALL
✓ mode: "deep" or "ultra"
Deep covers broad research; Ultra is for due diligence and critical investigation when the client accepts the largest candidate and result budget.
COMPACT SUMMARY
✓ context: { enabled: true, strategy: "summary", max_tokens: 1000 }
Summary is optional and review-gated. If the free model is unavailable or rate-limited, the response remains source-grounded through Evidence fallback.
FIND SOMETHING RECENT
✓ published_after plus a question with no date in it
Do not write “in 2026” into the query — that ranks pages that mention 2026. The filter is what restricts time; the text is what describes the subject.
LOOK UP A FACT
✓ mode: "fast", limit: 5, max_chars: 800
For error strings, version numbers and names, exact matching beats semantic search and costs you less context.
FEED AN AGENT LOOP
✓ content: "none" first, then re-query the winners
When the model is going to discard most of what it reads, get titles first to reduce response size and model tokens, then fetch text for the two or three that survived.
Every failure returns the same envelope, with the HTTP status and a stable machine-readable type. Branch on type, never on the message — messages get reworded, types do not.
{
"error": {
"type": "rate_limited",
"message": "20 requests per second exceeded on key orb_live_…f31c.",
"retry_after": 0.4,
"retryable": true,
"request_id": "req_01JZQ8F3M2K9",
"docs": "https://orbita.dev/docs#errors"
}
}
| Status | type | Retry? | What to do |
|---|---|---|---|
| 400 | invalid_request | no | A field is missing or the wrong shape. The message names the field. Fix the caller. |
| 401 | invalid_key | no | Missing, malformed or deleted key. Retrying will not create one. |
| 402 | quota_exhausted | no | Prepaid balance is gone. Top up; queued retries will only pile up. |
| 413 | query_too_long | no | Over 2 000 characters. Summarise the question before sending it — a 3 000-character query ranks worse anyway. |
| 422 | unsupported_filter | no | A filter combination that cannot return anything, e.g. published_after later than published_before. |
| 429 | rate_limited | yes | Wait retry_after seconds, then retry with jitter. Do not retry immediately in a loop; that is how you turn a spike into an outage. |
| 500 | internal | yes | Ours. Retry twice with backoff, then surface it. Send us the request_id. |
| 503 | capacity | yes | We are shedding load. Back off harder than for a 500 — a second or more. |
| 504 | timeout | yes | Your timeout_ms elapsed. You are not billed. Either retry or degrade — for most agents, answering without search beats waiting twice. |
import random, time, httpx
RETRYABLE = {"rate_limited", "internal", "capacity", "timeout"}
def search(payload, attempts=3):
for attempt in range(attempts):
r = httpx.post(URL, headers=HEADERS, json=payload, timeout=30)
if r.status_code < 400:
return r.json()
err = r.json().get("error", {})
if err.get("type") not in RETRYABLE or attempt == attempts - 1:
raise RuntimeError(f"{err.get('type')}: {err.get('message')}")
# Honour retry_after when the server sent one; jitter either way, or a
# fleet of workers will retry in lockstep and rebuild the same spike.
wait = err.get("retry_after") or 2 ** attempt
time.sleep(wait * (0.5 + random.random()))
const RETRYABLE = new Set(["rate_limited", "internal", "capacity", "timeout"]);
async function search(payload: unknown, attempts = 3) {
for (let attempt = 0; attempt < attempts; attempt++) {
const res = await fetch(URL, { method: "POST", headers, body: JSON.stringify(payload) });
if (res.ok) return res.json();
const { error } = await res.json();
if (!RETRYABLE.has(error.type) || attempt === attempts - 1) {
throw new Error(`${error.type}: ${error.message}`);
}
// Jitter, always — otherwise every worker retries on the same tick.
const wait = (error.retry_after ?? 2 ** attempt) * (0.5 + Math.random());
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
Limits are per key, not per project, so you can give a noisy batch job its own key and keep it away from your live traffic.
| Plan | Requests / second | Burst | Concurrent |
|---|---|---|---|
| Beta | 5 | 20 | 10 |
| Standard | 20 | 60 | 40 |
| Scale | 100 | 300 | 200 |
| Higher | Ask. We would rather raise your limit than have you shard across five keys. | ||
Every response carries the current state, so you can pace yourself without waiting to be told off:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 17
X-RateLimit-Reset: 0.31
Five fixed depth modes plus optional Auto. New Search is — per 1 000; Search + Compact is — and includes a controlled context pass. Page content and citation highlights are included in every mode; publication dates are included when available, and a timeout costs nothing.
| Orbita mode | Candidates → results | New Search | Search + Compact | Max context for LFM | Best for |
|---|---|---|---|---|---|
| Fast | 20 → 5 | — | — | — | Autocomplete and simple facts |
| Standard | 100 → 10 | — | — | — | Daily agents, chat, product search |
| Pro | 200 → 25 | — | — | — | Comparisons and evidence-heavy questions |
| Deep | 300 → 50 | — | — | — | Broad single-pass research |
| Ultra | 500 → 100 | — | — | — | Maximum recall |
Launch pricing, updated 2026-09-13. Deep is a wider single-pass retrieval budget, not a multi-step research agent. Page text and citations are included; published is nullable. Context ceilings apply to Compact output for the selected mode.
max_chars and content: "none" are documented as loudly as the price is.
If you are a coding agent reading this page, or you are pasting context into one, this is the block to take. It is self-contained: endpoint, auth, schema, limits, and the mistakes that cost the most.
You are integrating the Orbita search API.
ENDPOINT POST https://api.orbita.dev/v1/search
AUTH Authorization: Bearer $ORBITA_API_KEY (server-side only)
CONTENT application/json
REQUEST
query string, required, <= 2000 chars, natural language
mode enum auto | fast | standard | pro | deep | ultra, default auto
max_mode enum fast | standard | pro | deep | ultra, default ultra
max_cost_usd number Per-request USD ceiling; same unit as usage.cost_usd
max_results int 1 to 100, client ceiling
limit int 1 to the resolved mode maximum
content enum "text" | "markdown" | "none", default "text"
max_chars int 200-20000, default 2000 (per result)
lang string[] ISO 639-1, preference not a filter
published_after date YYYY-MM-DD
published_before date YYYY-MM-DD
domains { include: string[], exclude: string[] } <= 50 each
highlights int 0-5, default 0
timeout_ms int 500-30000, default 15000
links enum none | relevant | all, default relevant
context { enabled, strategy: summary|evidence, max_tokens, citations, dates }
RESPONSE 200
id string
query { text, language }
results[] { url, title, site, published|null, retrieved, language,
score, text|null, highlights[{text,start,end}] }
context optional { strategy, input_tokens, output_tokens, sources[] }
links[] discovered links; relevance-filtered by default
coverage[] extraction coverage for returned source versions
usage { searches, results, context_input_tokens,
context_output_tokens, cost_usd }
took_ms int
ERRORS { error: { type, message, retryable, retry_after?, request_id, docs } }
retryable types: rate_limited (429), internal (500), capacity (503), timeout (504)
terminal types: invalid_request (400), invalid_key (401), quota_exhausted (402),
query_too_long (413), unsupported_filter (422)
Branch on error.type, never on error.message.
Retry with exponential backoff AND jitter. Honour retry_after when present.
RULES THAT MATTER
1. Write query as a question, not keywords. Semantic ranking rewards it.
2. Do not put a year in the query to get recent pages; use published_after.
3. Returning fewer rows does not change the selected mode price; max_chars changes downstream token cost.
4. In a loop: content:"none" first, then re-query the two or three survivors
with content:"text". Saves ~40ms and most of the context.
5. published may be null. Never fabricate a date; say the source is undated.
6. score is comparable within one response only. Do not threshold on it.
7. Cite from highlights[].start/end, not from the model's memory of the text.
8. A 504 is not billed. Degrading beats retrying twice for most agents.
PRICE New Search: Fast $2.99 · Standard $4.99 · Pro $7.99 · Deep $9.99 · Ultra $13.99
Search + Compact: $4.99 · $6.99 · $11.99 · $14.99 · $19.99
per 1000 searches. usage.cost_usd is returned on every response.
/llms.txt — the index of this documentation in the llms.txt format./llms-full.txt — every page as one markdown file, for pasting into a context window whole./openapi.json — OpenAPI 3.1 description, for generating a client or a tool definition.Orbita speaks the Model Context Protocol, so Claude Code, Cursor and anything else that speaks MCP can call it without you writing a wrapper.
{
"mcpServers": {
"orbita": {
"command": "npx",
"args": ["-y", "@orbita/mcp"],
"env": { "ORBITA_API_KEY": "orb_live_..." }
}
}
}
claude mcp add orbita \
--env ORBITA_API_KEY=orb_live_... \
-- npx -y @orbita/mcp
It exposes one tool, orbita_search, with the parameters above, including Auto, Context and links. One tool on purpose: the model picks capabilities through parameters instead of choosing among near-duplicate tools.
Claude Code can use the documented mcp.json entry; Cursor can register the same command under MCP settings; a custom MCP client should expose the same orbita_search input schema and pass the API key server-side. The local prototype includes a stdio reference server at prototype/src/mcp-server.mjs; its only search tool calls the local v1.1 API. The browser UI may also expose a separate read-only status capability, not a second search contract.
Field by field, from whatever you are on now. Where a concept does not exist on the other side we say so instead of inventing an equivalent.
| Orbita | Exa | Tavily | Linkup | Brave | Serper |
|---|---|---|---|---|---|
query | query | query | q | q | q |
limit | numResults | max_results | maxResults | count | num |
content: "text" | contents.text | include_raw_content | includeSources | — snippets only | — snippets only |
max_chars | contents.text.maxCharacters | — | — | — | — |
mode | type | search_depth | depth | — | — |
published_after | startPublishedDate | time_range | fromDate | freshness | tbs |
domains.include | includeDomains | include_domains | includeDomains | — | via site: |
lang | — | country | — | search_lang | hl / gl |
highlights | contents.highlights | — | — | extra_snippets | — |
results[].published | publishedDate | — not returned | — not returned | page_age | date |
usage.cost_usd | costDollars | — | — | — | credits |
mode: "auto" | — | — | — | — | — |
context.strategy: "evidence" | contents.text | include_raw_content | includeSources | — | — |
links | links | — | — | — | — |
The original 100-query responses remain unchanged. The current Fast-to-Standard consistency rule is replayed over them and the original score formula is applied without manual adjustment.
| Orbita mode | Quality | Hit@1 | Hit@5 | Hit@10 | MRR | nDCG@10 |
|---|---|---|---|---|---|---|
| Standard | 80.01 | 65% | 88% | 91% | 0.7363 | 0.7789 |
| Fast | 79.37 | 65% | 88% | 88% | 0.7317 | 0.7686 |
The original values remain available for auditability and are not silently rewritten after the consistency correction.
| Provider / mode | Quality | Hit@1 | Hit@5 | Hit@10 | Hit@10 95% CI | MRR | p50 | p95 |
|---|---|---|---|---|---|---|---|---|
| Orbita Fast | 79.37 | 65% | 88% | 88% | 80.19–93.00% | 0.7317 | 836 ms* | 5 672 ms* |
| Orbita Standard | 76.06 | 61% | 81% | 88% | 80.19–93.00% | 0.6990 | 5 394 ms* | 16 746 ms* |
| Exa Fast | 68.23 | 62% | 67% | 67% | 57.31–75.44% | 0.6400 | 624 ms | 970 ms |
| Linkup Standard | 52.92 | 38% | 52% | 61% | 51.20–69.98% | 0.4498 | 1 327 ms | 2 312 ms |
| Tavily Basic | 35.97 | 18% | 37% | 40% | 30.94–49.80% | 0.2574 | 2 065 ms | 4 402 ms |
Quality = Hit@1 25% + Hit@5 20% + Hit@10 15% + MRR 20% + nDCG@10 10% + delivery 10%. All 500 calls returned successfully. *External and Orbita timings have different measurement boundaries and are not directly comparable. One AP-title Standard outlier took about four minutes and remains in the frozen record.
All known target pages were confirmed before the frozen run. Corpus size and internal index statistics are not public. Equivalent published-rate external spend: $2.00.
This preserved prototype run used 20 pre-registered questions, one known publisher URL per question, top ten from each provider, and no LLM judge. A hit means that exact URL appeared at or above the stated rank after URL normalisation.
| Provider | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR | p50 | p95 | Date field |
|---|---|---|---|---|---|---|---|---|
| Orbita prototype | 100% | 100% | 100% | 100% | 1.000 | 4 556 ms | 4 938 ms | 39.5% |
| Exa fast | 40% | 50% | 50% | 50% | 0.433 | 245 ms | 645 ms | 64.0% |
| Tavily basic | 10% | 15% | 25% | 25% | 0.150 | 1 030 ms | 2 313 ms | 0% |
| Linkup standard | 10% | 30% | 30% | 50% | 0.203 | 1 809 ms | 2 886 ms | 0% |
Quality used one request per question/provider. Latency used six fixed sentinel queries × two clean passes, giving 12 samples per provider; all four providers had zero request errors. Orbita's p50 and p95 are prototype end-to-end measurements, not a production SLA.
Average number of unique domains in each top ten from the same 20-query quality run.
| Provider | Average results | Average unique domains |
|---|---|---|
| Orbita | 10.0 | 4.3 |
| Exa | 10.0 | 7.5 |
| Tavily | 9.1 | 8.3 |
| Linkup | 10.0 | 8.6 |
Unique-domain count is a diversity indicator, not a quality score. Orbita's 4.3 shows that the deliberately narrow frozen corpus repeated publishers more often than the three web-scale providers.
The figures below come from provider documentation or marketing; they are not results from the Orbita benchmark above.
| Provider | Public price used / quoted | Vendor-published latency or service claim |
|---|---|---|
| Exa | $7 / 1k Fast searches, up to 10 results | Fast <350 ms end-to-end p50 in Exa's 2.0 test; configurable 180 ms–1 s |
| Tavily | $5–8 / 1k Basic/Fast at published credit prices | No numeric public latency SLA; the API reports response_time per request |
| Linkup | $5 / 1k Standard raw results | Standard 1–3 s; homepage claims SLA-backed 99.9% uptime |
| Serper | $0.30–1.00 / 1k by prepaid volume | Usually 1–2 s, sometimes 2–4 s; not measured here because no current project key was present |
Sources: Exa pricing, Exa 2.0, Tavily credits, Tavily Search API, Linkup pricing, Linkup modes, and Serper.
Hard test budget: $1.00. Estimated spend at published rates: $0.640 for completed web-provider calls, plus a conservative $0.01 Orbita reserve. The web-provider amount is an estimate, not an invoice export.
llms.txt, llms-full.txt and an OpenAPI description.↑↓ to move · ↵ to open · esc to close