Interfaze

OpenWebSearch

docs

blog

help

OpenWebSearch API

copy markdown

Use one API to query multiple web search providers. Let auto pick the provider for each query, or name a provider or an ordered fallback list yourself. Results come back in one normalized response either way, while the original provider payload remains available under raw.

Base URL: https://api.openwebsearch.ai

Quickstart

Create an API key, save it as OPENWEBSEARCH_API_KEY, and send your first search request.

export OPENWEBSEARCH_API_KEY="ows_..."

provider: "auto" is the simplest way to start: the gateway reads the query and picks the provider for you. The response reports which one served it.

fetch

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: "auto",
    query: "What changed in browser automation this week?",
    max_results: 10,
  }),
});

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

Use with a coding agent

This reference is also published as an agent skill, so an agent can integrate OpenWebSearch without being walked through the API.

npx skills add https://openwebsearch.ai

To give an agent search as a tool it can call directly, rather than an API to write code against, connect the MCP server instead.

Authentication

Send your API key as a bearer token with every API request.

Authorization: Bearer YOUR_API_KEY

Authentication failures return a standard 401 error envelope.

Endpoints

MethodPathDescription
POST/v1/searchSearch through one provider or an ordered fallback list.
GET/v1/providersDiscover providers, result limits, and parameter support.
POST/mcpMCP server for agents and MCP-capable editors.

Search request

Send a JSON body to POST /v1/search.

ParameterTypeRequiredDescription
querystringYesA non-empty search query.
providerstringYes, unless providers is setOne provider slug, or "auto" to let the query choose the provider.
providersstring[]Yes, unless provider is setOrdered provider fallback list. Takes precedence over provider.
allow_fallbacksbooleanNoEnables fallback through providers. Defaults to true for lists with multiple entries.
strict_paramsbooleanNoReject unsupported parameters instead of dropping them with a warning. Defaults to false.
max_resultsintegerNoNumber of results. Defaults to 10 and is capped per provider.
countrystringNoTwo-letter ISO country code, such as "us".
include_domainsstring[]NoRestrict results to these domains.
exclude_domainsstring[]NoExclude results from these domains.
start_datestringNoEarliest publication date in YYYY-MM-DD format.
end_datestringNoLatest publication date in YYYY-MM-DD format.
recencystringNoOne of hour, day, week, month, or year.
safe_searchstringNoOne of off, moderate, or strict.
provider_optionsobjectNoProvider-native options keyed by provider slug.

You must provide either provider or a non-empty providers list; there is no implicit default. Send provider: "auto" when you have no preference.

Supported providers

These are the values provider and providers accept today.

ProviderSlugMax resultsBest at
Autoauto10Routing each query to the best-fit provider
Bravebrave20Independent broad-web and media search
Exaexa100People, companies, and semantic discovery
Perplexityperplexity20Citation-backed, real-time answers
Parallelparallel20Token-dense excerpts for AI agents
Valyuvalyu20Academic, financial, and proprietary data
Apify Serpapify100Localized SERP features and rankings

Slugs are case-sensitive. An unrecognized one returns a 400.

Unified response

Every successful request returns the same top-level shape. The provider field identifies the provider that actually served the request, including when a fallback was used or when auto chose the provider. It is always a concrete provider slug, never "auto".

{
  "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": []
}

Result fields

FieldTypeDescription
titlestring or nullResult title.
urlstringCanonical result URL.
snippetstring or nullShort excerpt or description.
contentstring or nullFull text when supplied by the provider.
published_datestring or nullProvider-supplied publication date. Never fabricated.
sourcestring or nullDomain or source name.
rawanyOriginal untouched provider result.

Any provider-supplied relevance score is available only inside raw; it is not normalized to a top-level field.

usage.cost is the USD cost of the request and usage.results_count is the number of normalized results returned.

Automatic provider selection

Set provider to auto and the gateway inspects the query and routes it to the provider best suited to answer it.

fetch

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({
    query: "academic papers on CRISPR off-target effects",
    provider: "auto",
    max_results: 5,
  }),
});

// "perplexity" — the provider `auto` routed to, not "auto".
const { provider } = await response.json();

Billing is unchanged: the request is charged at the rate of the provider that served it, which usage.cost reports as usual.

What works with auto

The portable filters—country, include_domains, exclude_domains, start_date, end_date, recency, and safe_search—are all accepted and applied to the chosen provider. Because that provider is only known once routing has happened, any param_unsupported warning names the resolved provider rather than auto:

{
  "provider": "perplexity",
  "warnings": [
    {
      "code": "param_unsupported",
      "provider": "perplexity",
      "detail": "safe_search"
    }
  ]
}

If a filter is a correctness requirement rather than a preference, pin the provider yourself instead — with auto, which filters survive depends on where the query is routed.

Limits and restrictions

ConstraintBehavior with auto
max_resultsCapped at 10. Higher values are clamped and reported as max_results_clamped.
providers["auto"] is valid, but auto cannot be combined with other slugs in the list.
strict_paramsNot supported. Sending it returns 400.
provider_optionsNot supported, since the target provider is not known ahead of time. Returns 400.

The last two are rejected rather than ignored:

{
  "error": {
    "message": "Invalid request parameters: provider_options: `provider_options` is not supported with provider \"auto\"",
    "type": "invalid_request_error",
    "code": "invalid_request",
    "request_id": "req-9b902f06..."
  }
}

Reach for an explicit provider or a providers fallback list when you need any of those.

Provider selection and fallback

Set provider for a single-provider request:

fetch

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({
    query: "latest advances in fusion energy",
    provider: "brave",
    max_results: 5,
  }),
});

Set providers for an ordered fallback chain:

fetch

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({
    query: "latest advances in fusion energy",
    providers: ["exa", "perplexity", "brave"],
    allow_fallbacks: true,
    max_results: 5,
  }),
});

The gateway returns the first successful response. It tries the next provider when a provider returns no results, times out, or has a transient failure. Invalid requests stop immediately and do not trigger fallback. Results from multiple providers are never blended.

Provider capabilities

Not every provider supports every filter in the same way. Each unified parameter has one of three support levels:

LevelMeaning
nativeThe provider supports the parameter directly.
emulatedOpenWebSearch translates or approximates the parameter.
unsupportedThe provider cannot honor the parameter.

Use capability discovery before choosing a provider or fallback order:

fetch

const response = await fetch("https://api.openwebsearch.ai/v1/providers", {
  headers: {
    Authorization: `Bearer ${process.env.OPENWEBSEARCH_API_KEY}`,
  },
});

const { providers } = await response.json();
{
  "providers": [
    {
      "slug": "brave",
      "max_results_cap": 20,
      "params": {
        "country": "native",
        "include_domains": "emulated",
        "exclude_domains": "emulated",
        "start_date": "emulated",
        "end_date": "emulated",
        "recency": "native",
        "safe_search": "native"
      }
    }
  ]
}

By default, unsupported parameters are dropped and reported in warnings. Set strict_params: true to return a 400 instead.

auto is listed alongside the providers with a max_results_cap of 10 and an empty params object. That empty object means capability depends on the provider chosen at request time, not that filters are ignored — read the support levels off the provider that actually served the request.

Provider-specific options

Use provider_options when you need a native provider feature that is not part of the unified schema. These fields are passed to the selected provider. Gateway-owned safety and cost controls (per-provider result caps and sanitized options) are always re-applied, so they cannot be overridden.

fetch

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({
    query: "transformer architecture",
    provider: "exa",
    provider_options: {
      exa: {
        type: "fast",
        contents: { text: true, highlights: true },
      },
    },
  }),
});

Provider-specific options are intentionally not portable. Keep them scoped under the matching provider slug, and note that they cannot be combined with provider: "auto", which does not know the target provider up front.

Warnings

Non-fatal adjustments are returned in warnings[].

CodeMeaning
param_unsupportedThe provider cannot honor a requested parameter, so it was dropped.
max_results_clampedmax_results exceeded the provider limit and was reduced.
domains_truncatedA translated domain-filter expression exceeded the provider limit.
recency_emulatedrecency was translated into a provider date filter.

Errors

All errors use the same envelope:

{
  "error": {
    "message": "Human-readable description",
    "type": "invalid_request_error",
    "code": "invalid_request",
    "param": "query",
    "provider": "exa",
    "request_id": "req-3f0c..."
  }
}
HTTP statusTypeTypical cause
400invalid_request_errorInvalid JSON, parameters, dates, or provider.
401authentication_errorMissing or invalid API key.
403permission_errorThe project cannot make the request.
429rate_limit_errorRate limit exceeded.
500internal_errorUnexpected API error.
502provider_errorA provider failed or all fallbacks were exhausted.
503service_unavailable_errorNo available provider for the request, or a temporary dependency outage. Retry.

When relevant, errors include param and provider. Every response and error carries a request id (id on success, request_id on errors) — include it when contacting support.

Rate limits

Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. A 429 response also includes Retry-After.

MCP server

OpenWebSearch is also a remote Model Context Protocol server, so an agent or an MCP-capable editor can call search as a tool instead of you writing an HTTP integration. It is the same gateway, the same key, and the same providers as the REST API.

Endpoint: https://api.openwebsearch.ai/mcp

The transport is Streamable HTTP and the server is stateless: every call is a POST, no session is negotiated, and no Mcp-Session-Id is issued or expected. GET and DELETE return 405, since there is no server-initiated stream and no session to terminate.

Connect a client

Most clients take a URL and headers. Authenticate with the same ows_ key you use for REST, as a bearer token:

{
  "mcpServers": {
    "openwebsearch": {
      "url": "https://api.openwebsearch.ai/mcp",
      "headers": {
        "Authorization": "Bearer ows_..."
      }
    }
  }
}

If you are calling the endpoint directly rather than through a client library, two headers are required: Content-Type: application/json, and an Accept that lists both application/json and text/event-stream. Responses are SSE-framed, so the JSON-RPC payload arrives on a data: line. Accept: */* is rejected with a 406.

curl https://api.openwebsearch.ai/mcp \
  -H "Authorization: Bearer $OPENWEBSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "web_search",
      "arguments": { "query": "browser automation", "max_results": 3 }
    }
  }'

Tools

ToolDescription
web_searchSearch the web and return ranked results with snippets. Billed like POST /v1/search.
list_providersReport each provider's result cap, parameter support, option catalog, and blocked options. Free.

web_search takes query (the only required argument) plus the same portable filters as the REST API: max_results, country, include_domains, exclude_domains, start_date, end_date, recency, safe_search, allow_fallbacks, and provider_options.

Provider selection works differently here. providers is optional — omit it and the gateway routes through a default fallback chain, which is the recommended path since a model has no basis for picking an index. Set it only when a specific provider matters:

{
  "query": "transformer architecture",
  "providers": ["exa"],
  "max_results": 5,
  "include_domains": ["arxiv.org"]
}

list_providers takes one optional argument, slug, to return a single provider instead of all of them. It needs no credentials and is never billed, so an agent can call it to discover capabilities before spending anything. It returns more detail than GET /v1/providers does: alongside max_results_cap and params, each entry carries notes explaining emulated or unsupported behavior, an options JSON Schema of the full provider_options catalog, and blocked_options mapping each refused option to the reason.

How the tool differs from POST /v1/search

The MCP tool is tuned for models rather than programmatic callers, so four things behave differently. These are the ones that will bite when porting a REST integration:

RESTOver MCP
provider (singular)Not a parameter. It is ignored rather than rejected, and the default chain answers instead.
provider: "auto"Unavailable. providers accepts only concrete slugs, so ["auto"] fails validation.
strict_paramsNot a parameter. Unsupported filters are always dropped and reported in warnings.
providers is requiredOptional. Omitting it selects the gateway's default chain.

Results are normalized exactly as they are over REST, with two adjustments that keep a tool result from swamping a context window:

  • raw is never included. It exists for programmatic REST consumers and is unusable by a model.
  • content is capped per result, with a marker noting how many characters were omitted. The key is absent entirely when the provider supplied no page text.

Every response carries both a text content block and structuredContent, which hold the same payload: id, provider, query, results, usage, and warnings when there are any. As with REST, provider names the index that actually served the request.

Errors over MCP

Anything you can cause inside a tool — an invalid argument, a rejected key, a provider outage, a rate limit — comes back as a normal tool result flagged isError, not as a JSON-RPC fault, so the agent can read the reason and recover. The text names the status, the error type, and whether retrying is worthwhile:

openweb web_search failed (429 rate_limit_error, retryable): Too many requests. Please try again shortly. — retry after 30s.

Omitting the Authorization header entirely is the one credential case handled at the transport layer: it returns 401 with a WWW-Authenticate challenge, which is what prompts a client to ask for a key. A key that is present but invalid or out of credits returns 200 with an isError result explaining which of the two it is, because clients tend to treat a 401 as "server unavailable" and never show the model the message.

Rate limits are the same per-project limits as REST, but MCP has no header channel, so X-RateLimit-* headers are not sent and the retry delay rides in the error text instead. Two transport ceilings also apply: a request body is capped at 1 MB, and a JSON-RPC batch at 20 messages.

More examples

Filters with strict parameter handling

fetch

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({
    query: "transformer architecture",
    provider: "exa",
    max_results: 3,
    include_domains: ["arxiv.org"],
    start_date: "2024-01-01",
    strict_params: true,
  }),
});