GuidesSEARCH APIS
Tavily API from the browser
Tavily's API sends Access-Control-Allow-Origin: * on preflights, keyless calls and requests with tvly- keys, so you call it from the browser directly, no proxy needed. The exception is a 401 for a missing key or one not starting with tvly-, which arrives without the header and shows up as a CORS error. cors.dev's free proxy (no signup, GET and HEAD, about 600 requests a minute per IP) handles the other half: reading the result pages, which mostly send no CORS headers. We tested on 28 October 2026; Tavily's free plan has 1,000 credits a month.
How Tavily answers browser requests
You can call https://api.tavily.com from browser JavaScript directly, no proxy needed. We tested it on 28 October 2026 with Origin: https://example.com, the header a browser page sends.
The preflight always passes: OPTIONS /search answered 200 with Access-Control-Allow-Origin: * and allows authorization, content-type and x-tavily-access-mode, and /extract and /usage answered the same way. We had no valid key, so we never saw a keyed 200, but every response that reached Tavily's application carried Access-Control-Allow-Origin: *.
| Request | Status | Access-Control-Allow-Origin | What your code sees |
|---|---|---|---|
| OPTIONS preflight on /search | 200 | * | Preflight passes |
| Keyless POST /search | 200 | * | Search results |
| Keyless POST /extract | 200 | * | Extracted page text |
| POST /search with an invalid tvly- key | 401 | * | Readable JSON error |
| POST /search with a key not starting tvly- | 401 | Missing | CORS error, TypeError |
| POST /search with no key | 401 | Missing | CORS error, TypeError |
| POST /search with an empty body | 422 | * | Validation error, query missing |
| Keyless POST /crawl | 404 | Missing | CORS error, unsupported_endpoint |
| Keyless call over the hourly cap | 429 | * | Readable hourly_cap_reached error |
Skip the free cors.dev proxy for Tavily's API. It allows only GET and HEAD, so a POST gets 405 method_not_allowed, an Authorization header gets 400 header_not_allowed, and X-Tavily-Access-Mode is dropped and listed in X-Cors-Dropped-Headers, which makes Tavily answer 401.
Why a missing or malformed key looks like a CORS error
Tavily's docs show keys as tvly-YOUR_API_KEY, sent as Authorization: Bearer <key>. With Bearer tvly-x, shaped like a key but invalid, Tavily answered 401 with Access-Control-Allow-Origin: * and {"detail":{"error":"Unauthorized: missing or invalid API key."}}, and /extract, /crawl and /map did the same.
With Bearer x, or no key at all, the same 401 arrived without Access-Control-Allow-Origin, and the legacy api_key body field behaved alike. The browser hides that response, fetch() rejects with a TypeError (Chrome says "Failed to fetch"), and the console reports a missing Access-Control-Allow-Origin header.
So a Tavily CORS error usually means a bad key. The DevTools Network tab still shows the real 401; look for a key read from an empty input, an undefined environment variable, or a key wrapped in spaces or quotes.
Search from the browser with keyless mode
Send X-Tavily-Access-Mode: keyless on POST /search or POST /extract and leave out Authorization. No account, no key. Tavily says keyless responses are identical to keyed ones, that keyless access is rate-limited, and that a valid key wins if you send both.
A keyless search for "what is cors" with max_results: 2 answered 200 with 2,478 bytes of JSON: results with url, title, content and score, MDN's CORS guide on top. A keyless /extract of https://example.com returned the page text in results[].raw_content.
async function tavilySearch(query) {
const response = await fetch('https://api.tavily.com/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tavily-Access-Mode': 'keyless',
},
body: JSON.stringify({ query, max_results: 5 }),
});
const data = await response.json();
if (!response.ok) {
const message = data.error?.message ?? data.detail?.error ?? response.statusText;
throw new Error(`Tavily ${response.status}: ${message}`);
}
return data.results;
}
const results = await tavilySearch('what is cors');
for (const { score, title, url } of results) {
console.log(score.toFixed(2), title, url);
}Tavily does not publish the keyless limit. After about 30 keyless calls from one IP within a few minutes, we got 429 with Access-Control-Allow-Origin: *, a Retry-After of 72 seconds (107 in a rerun) and error.code hourly_cap_reached; the message offers a key or pay-per-request through x402. /crawl, /map and /research always need a key.
Use an API key in a browser app
A key in browser JavaScript is visible to anyone who opens the network tab, and cors.dev does not change that.
- Public demos: Use keyless mode, so there is no key to expose.
- Bring-your-own-key apps: Each user pastes their own key and spends their own credits.
- Internal tools: Your own key in the page is acceptable for your team.
- Public products on your key: Call Tavily from your backend.
Trim a pasted key and check the tvly- prefix before calling fetch(), so a typo shows a clear message instead of a CORS error.
async function tavilySearchWithKey(query, rawKey) {
const apiKey = rawKey.trim();
// A key that does not start with tvly- gets a 401 without CORS headers
if (!apiKey.startsWith('tvly-')) {
throw new Error('Paste a Tavily API key; it starts with tvly-');
}
const response = await fetch('https://api.tavily.com/search', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, search_depth: 'basic', max_results: 5 }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(`Tavily ${response.status}: ${data.detail?.error ?? response.statusText}`);
}
return data.results;
}
const userKey = document.querySelector('#tavily-key').value;
const results = await tavilySearchWithKey('cors proxy', userKey);
console.log(results);Read Tavily's result pages through the free proxy
Tavily sends CORS headers; most pages in results[].url do not. MDN's CORS guide, the top result, answered 200 with 259,288 bytes of HTML and no Access-Control-Allow-Origin, and the konghq.com article in second place sent 377,539 bytes without it. Through https://proxy.cors.dev/ both arrived intact with CORS headers.
- Prefix the URL
Put
https://proxy.cors.dev/in front of the resulturl. No signup,GETandHEAD, about 600 requests a minute per IP, pages up to 6 MiB, a 30-second deadline, no caching. - Parse the HTML
Read the body as text and hand it to
DOMParserfor the title and text; proxy refusals arrive inX-Cors-Error.
async function readPage(url) {
const response = await fetch('https://proxy.cors.dev/' + url);
const proxyError = response.headers.get('X-Cors-Error');
if (proxyError) throw new Error(`cors.dev: ${proxyError}`);
if (!response.ok) throw new Error(`${url} answered ${response.status}`);
const html = await response.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
doc.querySelectorAll('script, style, nav, header, footer').forEach((node) => node.remove());
return {
title: doc.title,
text: doc.body.textContent.replace(/\s+/g, ' ').trim(),
};
}
// A result URL from a Tavily search
const page = await readPage('https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS');
console.log(page.title, page.text.length);Tavily's /extract is the alternative: cleaned text instead of raw HTML, keyless or at 1 credit per 5 successful URLs (basic) or 2 per 5 (advanced). The proxy route costs no Tavily credits.
Tavily pricing, credits and rate limits
We read Tavily's pricing and docs on 28 October 2026. The free Researcher plan gives 1,000 API credits every month with no credit card; Enterprise is custom.
| Plan | Credits per month | Price | Price per credit |
|---|---|---|---|
| Researcher | 1,000 | Free | None |
| Pay as you go | Billed per use | $0.008 per credit | $0.008 |
| Project | 4,000 | $30 a month | $0.0075 |
| Bootstrap | 15,000 | $100 a month | $0.0067 |
| Startup | 38,000 | $220 a month | $0.0058 |
| Growth | 100,000 | $500 a month | $0.005 |
- search: 1 credit at basic depth, 2 at advanced.
- extract: 1 credit per 5 successful URLs on basic, 2 per 5 on advanced.
- map: 1 credit per 10 pages; crawl costs its map plus extract credits.
- research: 4 to 250 credits, depending on the model.
Development keys allow 100 requests a minute and production keys 1,000; /crawl is capped at 100 a minute and /research at 20. Over a limit, Tavily answers 429 with Retry-After.
Good questions.
Why does fetch() to the Tavily API fail with a CORS error?
Usually the key is empty, undefined or does not start with tvly-. Tavily answers those requests with a 401 that has no Access-Control-Allow-Origin header, so the browser hides it and fetch() throws a TypeError. The DevTools Network tab shows the real 401.
Do I need a CORS proxy for api.tavily.com?
No. Tavily sends Access-Control-Allow-Origin: * on preflights, keyless calls and requests with tvly- keys, so call it directly. The free cors.dev proxy would refuse the POST with 405; use it only to GET the result pages Tavily links to.
How many keyless Tavily searches can I make?
Tavily does not publish the number. In our 28 October 2026 test, about 30 keyless calls from one IP within a few minutes led to 429 with hourly_cap_reached and a Retry-After of 72 seconds (107 in a rerun). The 429 carries Access-Control-Allow-Origin: *, so your code can read the error.
Does Tavily keyless mode work for /crawl or /research?
No. Keyless mode covers POST /search and POST /extract only. A keyless POST /crawl answered 404 with unsupported_endpoint and no CORS header, which the browser reports as a CORS error.
Should I read Tavily result URLs with /extract or the cors.dev proxy?
Use /extract for cleaned text, keyless or at 1 credit per 5 URLs on basic extraction. Use https://proxy.cors.dev/ for the full HTML, parsed with DOMParser, at no Tavily credit cost and about 600 requests a minute per IP.