# Introducing OpenWebSearch

URL: https://interfaze.ai/blog/introducing-openwebsearch

[OpenWebSearch](https://openwebsearch.ai) is a router for web indexes like Google SERP, Exa, Brave, Perplexity and more. The router standardizes both the API inputs and outputs, so that you can easily switch between different providers with centralized billing.

Like LLMs, web indexes are becoming commoditized with different indexes having different strengths and weaknesses with access to niche data, performance and cost. Every large model lab including Interfaze has to build their own internal mini-Google for training and eventually launch that index as a service.

So the bet is we'll start seeing a lot more web indexes as a service in the next few years.

## Search is worth more than parameters

DeepMind made this case back in 2022. In [this paper](https://arxiv.org/abs/2203.05115), Lazaridou et al. took Gopher models from 44M up to 280B parameters and compared closed-book answering against the same models conditioned on Google Search results.

On Natural Questions and HotpotQA, the 7B model with search beat the 280B model without it. On Natural Questions, so did the 1B.

Conditioning on as few as 5 retrieved paragraphs was enough for the 7B model to pass closed-book Gopher-280B, which the authors summed up as "suggesting that searching the Internet is worth more than 273 billion parameters".

## Your first search

1. Sign in at [openwebsearch.ai](https://openwebsearch.ai)
2. Create a key and save it

It's a plain REST endpoint that takes and returns JSON, so it drops into any language and wires up as a tool call like any other API.

**fetch · typescript**

```typescript
const response = await fetch("https://api.openwebsearch.ai/v1/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPENWEBSEARCH_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    provider: "exa",
    query: "What changed in browser automation this week?",
    max_results: 5,
  }),
});

const { results, provider, usage } = await response.json();

console.log(`${provider} served ${results.length} results for $${usage.cost}`);
```

**requests · python**

```python
import os
import requests

response = requests.post(
    "https://api.openwebsearch.ai/v1/search",
    headers={"Authorization": f"Bearer {os.environ['OPENWEBSEARCH_API_KEY']}"},
    json={
        "provider": "exa",
        "query": "What changed in browser automation this week?",
        "max_results": 5,
    },
)

data = response.json()

print(f"{data['provider']} served {len(data['results'])} results for ${data['usage']['cost']}")
```

**curl · bash**

```bash
curl -X POST https://api.openwebsearch.ai/v1/search \
  -H "Authorization: Bearer $OPENWEBSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "exa",
    "query": "What changed in browser automation this week?",
    "max_results": 5
  }'
```

## One response shape

Every successful request returns the same top-level object, whichever index served it.

```json
{
  "id": "req-3f0c...",
  "provider": "exa",
  "query": "browser automation",
  "results": [
    {
      "title": "Browser automation in 2026",
      "url": "https://example.com/browser-automation",
      "snippet": "A look at what changed across headless browsers this year.",
      "content": "Full page content when available.",
      "published_date": "2026-08-02",
      "source": "example.com",
      "raw": {}
    }
  ],
  "usage": {
    "cost": 0.007,
    "results_count": 1
  },
  "warnings": []
}
```

The result fields are the ones RAG and agent pipelines already expect, so you don't need a per-provider adapter:

| Field            | Description                                          |
| ---------------- | ---------------------------------------------------- |
| `title`          | Result title                                         |
| `url`            | Canonical result URL                                 |
| `snippet`        | Short excerpt or description                         |
| `content`        | Full text when the provider supplies it              |
| `published_date` | Provider-supplied publication date, never fabricated |
| `source`         | Domain or source name                                |
| `raw`            | The original untouched provider result               |

The `provider` field always names the index that actually served the request, including when a fallback kicked in.

## Fallbacks for when an index goes down

Pass an ordered `providers` list instead of a single `provider` and the router walks it for you.

```json
{
  "query": "latest advances in fusion energy",
  "providers": ["exa", "perplexity", "brave"],
  "allow_fallbacks": true,
  "max_results": 5
}
```

The first successful response wins. It moves to the next index when one returns no results, times out, or has a transient failure, and an invalid request stops immediately instead of burning through your whole list.

## Provider-specific options

Not every index supports every filter. `GET /v1/providers` reports each provider's result cap and whether a given parameter is `native`, `emulated`, or `unsupported`, so you can discover capabilities at runtime instead of hardcoding them.

Unsupported parameters are dropped and reported in `warnings` by default. Set `strict_params: true` to get a `400` instead of a silent drop.

When you need a native feature that isn't part of the unified schema, scope it under `provider_options`:

```json
{
  "query": "transformer architecture",
  "provider": "exa",
  "provider_options": {
    "exa": {
      "type": "fast",
      "contents": { "text": true, "highlights": true }
    }
  }
}
```

## Providers

There's no universally best index. Historical depth, people data, freshness and ranking quality all vary, so the point is to pick per job rather than per integration.

| Provider   | Best for                                 | Status      |
| ---------- | ---------------------------------------- | ----------- |
| Parallel   | Token-dense excerpts for AI agents       | Live        |
| Brave      | Independent broad-web and media search   | Live        |
| Exa        | People, companies and semantic discovery | Live        |
| Perplexity | Citation-backed, real-time answers       | Live        |
| Valyu      | Academic, financial and proprietary data | Live        |
| Apify Serp | Localized SERP features and rankings     | Live        |
| Tavily     | Fresh news and agent-ready research      | Coming soon |
| Bing       | Broad web, local, news and image results | Coming soon |
| Interfaze  | Research, social and financial indexes   | Coming soon |

| Octen | Real-time and multimodal web search | Coming soon |

More are on the way, and `GET /v1/providers` is always the source of truth for live slugs, caps and capabilities.

## Extracting the full page

While search gives you the right pages, it's not always easy to extract the content of that page in a structured or markdown format.

Depending on the pages like government sites or linkedin profiles, bot checks and other anti-scraping measures, it's difficult to extract the content of that page in a structured or markdown format.

You can use [Interfaze](https://interfaze.ai?demo=web) to scrape any page as it figures out the best way to extract the content using AI with access to a full browser and proxies built in.

**Interfaze SDK · typescript**

```typescript
import { Interfaze, responseFormat } from "interfaze";
import { z } from "zod";

const interfaze = new Interfaze();

const ArticleSchema = z.object({
  title: z.string(),
  author: z.string().optional(),
  published_date: z.string().optional(),
  key_points: z.array(z.string()),
});

// results[0].url came back from the OpenWebSearch call above
const response = await interfaze.chat.completions.create({
  messages: [{ role: "user", content: `Extract the article details from ${results[0].url}` }],
  response_format: responseFormat(z.toJSONSchema(ArticleSchema), "article_schema"),
});

console.log(JSON.parse(response.choices[0]?.message.content ?? "{}"));
```

**Vercel AI SDK · typescript**

```typescript
import { createOpenAI } from "@ai-sdk/openai";
import { generateText, Output } from "ai";
import { z } from "zod";

const interfaze = createOpenAI({
  baseURL: "https://api.interfaze.ai/v1",
  apiKey: process.env.INTERFAZE_API_KEY,
});

const ArticleSchema = z.object({
  title: z.string(),
  author: z.string().optional(),
  published_date: z.string().optional(),
  key_points: z.array(z.string()),
});

const { output } = await generateText({
  model: interfaze.chat("interfaze-beta"),
  output: Output.object({ schema: ArticleSchema }),
  prompt: `Extract the article details from ${results[0].url}`,
});

console.log(output);
```

**LangChain SDK · typescript**

```typescript
import { ChatInterfaze } from "@interfaze-ai/langchain";
import { z } from "zod";

const interfaze = new ChatInterfaze({ apiKey: process.env.INTERFAZE_API_KEY });

const ArticleSchema = z.object({
  title: z.string(),
  author: z.string().optional(),
  published_date: z.string().optional(),
  key_points: z.array(z.string()),
});

const structuredModel = interfaze.withStructuredOutput(ArticleSchema);

const article = await structuredModel.invoke(`Extract the article details from ${results[0].url}`);

console.log(article);
```

**Interfaze SDK · python**

```python
from typing import Optional
from interfaze import Interfaze
from pydantic import BaseModel

interfaze = Interfaze()

class ArticleSchema(BaseModel):
    title: str
    author: Optional[str] = None
    published_date: Optional[str] = None
    key_points: list[str]

# data["results"][0]["url"] came back from the OpenWebSearch call above
response = interfaze.chat.completions.parse(
    messages=[
        {
            "role": "user",
            "content": f"Extract the article details from {data['results'][0]['url']}",
        }
    ],
    response_format=ArticleSchema,
)

print(response.choices[0].message.parsed)
```

**LangChain SDK · python**

```python
import os
from typing import Optional
from interfaze_langchain import ChatInterfaze
from pydantic import BaseModel

interfaze = ChatInterfaze(api_key=os.environ["INTERFAZE_API_KEY"])

class ArticleSchema(BaseModel):
    title: str
    author: Optional[str] = None
    published_date: Optional[str] = None
    key_points: list[str]

structured_llm = interfaze.with_structured_output(ArticleSchema)

article = structured_llm.invoke(
    f"Extract the article details from {data['results'][0]['url']}"
)

print(article)
```

When you want the page itself rather than specific fields, run it as a task instead. The scraper task has a fixed pre-defined output, which makes it faster and cheaper than asking for a custom schema:

**Interfaze SDK · typescript**

```typescript
const page = await interfaze.tasks.scrape(results[0].url);

console.log("Web Scrape Results:", page);
```

**Interfaze SDK · python**

```python
page = interfaze.tasks.scrape(data["results"][0]["url"])

print("Web Scrape Results:", page)
```

Extraction uses your `INTERFAZE_API_KEY`, so it's a separate call from the search. Full details are in the [web scraping docs](https://interfaze.ai/docs/web/web-scraping).

## Billing and usage

Pricing is dynamic because it follows the provider you route to, not a flat platform rate. Most providers bill per request, so a 10-result search costs the same as a 5-result one.

Every response carries its own `usage.cost` in USD, so spend is attributable request by request rather than reconciled at the end of the month.

The only platform fee is 5% on credit purchases. Top up \$100 and you're charged \$105, with the full \$100 landing in your balance. Nothing is added on top of a search itself, so the cost you see on a response is the cost you pay.

## Zero data retention by default

Query text and the results we return are not retained. We keep only the metadata needed to bill and show usage: the request id, the provider that served it, the cost and the latency.

One caveat worth knowing is that whichever provider a request routes to applies its own retention policy to the query it receives. If you need a specific guarantee, check the policies of the indexes you route to and pin your provider list accordingly.

## Takeaway

- **One index:** set `provider` and send a normal REST request.
- **High availability:** pass an ordered `providers` list and requests fall through automatically.
- **Switching:** change one request field instead of rewriting an integration.
- **Native features:** reach them through `provider_options` and read them back under `raw`.
- **Billing:** one prepaid balance across every provider, with `usage.cost` on every response.

Web indexes will keep multiplying, so the integration you write today should outlive whichever one currently wins.

- [Get an API key](https://openwebsearch.ai)
- [Read the OpenWebSearch docs](https://openwebsearch.ai/docs)
- Using Interfaze directly? Web search is [built into the model](https://interfaze.ai/docs/web/web-search)
- Join the [Discord](https://interfaze.ai/discord)
