GuidesCHATGPT SITES
ChatGPT Sites and CORS errors
A ChatGPT Site runs its page code in the visitor's browser, so a fetch() to an API that sends no Access-Control-Allow-Origin header fails with TypeError: Failed to fetch. Put https://proxy.cors.dev/ in front of the API URL to fix it: the free tier needs no signup or key, allows about 600 requests a minute per visitor and returns responses up to 6 MiB. Of 13 APIs that real Sites call, tested from a chatgpt.site origin on 6 November 2026, the 7 without CORS headers answered 200 through the proxy and 4 already work directly.
Why fetch() fails on a ChatGPT Site
ChatGPT Sites builds, hosts and shares a website from a prompt in ChatGPT or Codex, and it has been in public beta since 9 July 2026. A published Site loads from its own chatgpt.site subdomain, such as https://, or from a custom domain you connect.
The page code runs in the visitor's browser. A fetch() from that page to another domain, such as query1.finance.yahoo.com, is a cross-origin request, so the browser only hands the response to your script when the API answers with an Access-Control-Allow-Origin header that allows the Site's origin. Yahoo Finance and every RSS feed we tested send no such header, and Chrome logs this:
Access to fetch at 'https://query1.finance.yahoo.com/v8/finance/chart/AAPL' from origin 'https://stock-pulse.example.chatgpt.site' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
TypeError: Failed to fetchSetting mode: 'no-cors', as the message suggests, does not help: the browser returns an opaque response with status 0 and an empty body. None of the 19 public Sites we checked on 6 November 2026 sends a Content-Security-Policy header, so the request does leave the page; the missing CORS header on the API is what blocks the response.
Fix it with the proxy.cors.dev prefix
Put https://proxy.cors.dev/ in front of the full API URL, so https:// becomes https://. The proxy fetches the API from a server, where CORS does not apply, and answers your page with Access-Control-Allow-Origin set to the Site's origin.
- No signup and no key:
GETandHEADrequests work right away, from any origin. - About 600 requests a minute per IP. Each visitor's browser sends its own requests, so the limit applies per visitor, with no daily or monthly quota.
- Responses up to 6 MiB, and the proxy waits up to 30 seconds for the API to answer.
- Your script can read
X-Cors-ErrorandRetry-Afteron every response. Cookies are never forwarded.
// Use this for APIs that send no Access-Control-Allow-Origin header
const PROXY = 'https://proxy.cors.dev/';
async function fetchThroughProxy(url) {
const response = await fetch(PROXY + url, { signal: AbortSignal.timeout(30_000) });
if (!response.ok) {
const reason = response.headers.get('X-Cors-Error') ?? `HTTP ${response.status}`;
throw new Error(`${new URL(url).hostname}: ${reason}`);
}
return response;
}
const chart = await fetchThroughProxy(
'https://query1.finance.yahoo.com/v8/finance/chart/AAPL?range=1mo&interval=1d',
).then((response) => response.json());
const { meta } = chart.chart.result[0];
console.log(meta.symbol, meta.regularMarketPrice, meta.currency);
const feed = await fetchThroughProxy('https://feeds.bbci.co.uk/news/business/rss.xml').then((response) =>
response.text(),
);
const xml = new DOMParser().parseFromString(feed, 'application/xml');
console.log([...xml.querySelectorAll('item > title')].slice(0, 3).map((title) => title.textContent));ChatGPT already writes this pattern on its own. Between 2 October and 6 November 2026, 72 ChatGPT Sites sent 239,328 requests through proxy.cors.dev and 86% came back 200. 48 of them load Yahoo Finance, which is 87% of that traffic, and the public ones we read mostly build the call as 'https://.
Ask ChatGPT to make the change
You do not have to edit the code by hand. Ask ChatGPT for the change in the Site editor:
- Open sites
On the web, select More, then sites, or go to
chatgpt.com/sites, and open the Site. - Select Edit
In the preview, select Edit. Under Describe website edits, paste the requests from the list below.
- Review before it goes live
Every Sites deployment URL is a production deployment. Ask ChatGPT to save a version without deploying it, check that the data loads, then ask it to deploy that version.
Ask for these changes:
- Send requests to APIs that return no CORS headers to
https://proxy.cors.dev/followed by the full API URL. - Keep direct calls to APIs that already send
Access-Control-Allow-Origin. - Allow up to 30 seconds per request.
- After a
429, wait the number of seconds inRetry-Afterbefore trying again, and never retry in a loop. - Load Yahoo Finance quotes 20 symbols per request with
/v7/finance/spark. - Send no
Authorizationor API key headers through the proxy.
APIs ChatGPT Sites call, tested
On 6 November 2026 we called 13 APIs that real Sites use, with the origin https://, once through proxy.cors.dev and once directly. 7 send no CORS header and answered 200 through the prefix, 4 already send CORS headers and work directly, and 2 fail either way.
| API | Through proxy.cors.dev | Direct from the Site | What to do |
|---|---|---|---|
| Yahoo Finance chart | 200 | Blocked, no CORS header | Use the prefix |
| Yahoo Finance spark | 200 | Blocked, no CORS header | Use the prefix |
| Yahoo Finance headlines RSS | 200 | 403, no CORS header | Use the prefix |
| BBC News RSS | 200 | Blocked, no CORS header | Use the prefix |
| Federal Reserve press RSS | 200 | Blocked, no CORS header | Use the prefix |
| EIA Today in Energy RSS | 200 | Blocked, no CORS header | Use the prefix |
| NOS news RSS | 200 | Blocked, no CORS header | Use the prefix |
| SEC EDGAR submissions | 200, not needed | Works, sends * | Call it directly |
| Eastmoney quotes | 200, not needed | Works, echoes the origin | Call it directly |
| Radio Browser | 200, not needed | Works, sends * | Call it directly |
| CoinGecko simple price | 429 from CoinGecko | Works, sends * | Call it directly |
| Yahoo Finance quote | 401, needs a Yahoo cookie | 401, no CORS header | Use chart or spark |
| Google News RSS | 503 from Google | Blocked, no CORS header | Use another feed |
CoinGecko answered 429 through the proxy but works directly, so call it from the page like the other APIs that send CORS headers. Yahoo's /v7/finance/quote needs a Yahoo cookie and crumb, which cors.dev never forwards, so use / or /v7/finance/spark instead. Google answers the proxy's servers with a 503 most of the time: 68% of the Google News requests Sites sent through the proxy since 2 October 2026 got one, so load a feed such as BBC News RSS.
Stay under the rate limits
Pick the Yahoo Finance endpoint by how many tickers you load. / returns one symbol with its price, previous close and history. / returns up to 20 symbols per request, and 21 symbols get a 400, so a 300-ticker watchlist is 15 spark requests instead of 300 chart requests.
Since 2 October 2026, all 11,107 requests cors.dev answered with 429 rate_limited came from one Site whose page went past 600 requests a minute, and the same Site drew 6,016 of the 6,347 429 answers Yahoo sent to Sites. Tell the two apart with X-Cors-Source: gateway means cors.dev's limit, with Retry-After: 60, and upstream means the API answered 429 itself.
Pages also cancelled 1,907 requests before the answer arrived. Generated code sets its own timeouts, and the public Sites we read used 2.5, 12 and 18 seconds, while the proxy gives a slow API up to 30 seconds.
const PROXY = 'https://proxy.cors.dev/';
// After a 429, wait as long as Retry-After asks, up to 3 tries
async function getJson(url, tries = 3) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(PROXY + url, { signal: AbortSignal.timeout(30_000) });
if (response.ok) return response.json();
if (response.status !== 429 || attempt === tries) {
throw new Error(response.headers.get('X-Cors-Error') ?? `HTTP ${response.status}`);
}
const seconds = Number(response.headers.get('Retry-After')) || 10;
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}
// Yahoo's spark endpoint answers for up to 20 symbols per request
async function loadWatchlist(symbols) {
const quotes = {};
for (let start = 0; start < symbols.length; start += 20) {
const batch = symbols.slice(start, start + 20).map(encodeURIComponent).join(',');
const body = await getJson(
`https://query1.finance.yahoo.com/v7/finance/spark?symbols=${batch}&range=1d&interval=1d`,
);
for (const { symbol, response } of body.spark.result) {
const { regularMarketPrice: price, chartPreviousClose: previous, currency } = response[0].meta;
quotes[symbol] = { price, change: Number((price - previous).toFixed(2)), currency };
}
}
return quotes;
}
console.table(await loadWatchlist(['AAPL', 'MSFT', 'NVDA', 'ASML.AS', '^GSPC', 'BTC-USD']));API keys, POST and server code
The free tier refuses Authorization and API key headers such as X-Api-Key with 400 header_not_allowed. Other headers it does not forward are dropped and listed in X-Cors-Dropped-Headers. A key in the query string is forwarded as it is, and every visitor can read it in the page.
A secret key belongs on the Site's server, not in the page. Store it under More actions, then Settings, where Sites keeps hosted environment variables and secrets, and ask ChatGPT to call that API from server-side code. A proxy cannot hide a key: anything the page sends, its visitors can read.
For POST, PUT, PATCH or DELETE, request bodies up to 1 MiB, or Authorization and custom headers from the page, use cors.dev Pro: $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card. Sign up with Google, create a Connection with the Site's exact chatgpt.site origin, any custom domain and the API hosts, and send its publishable key in X-Cors-Key. Throughput is 3,000 requests a minute per account and 100 in flight, and it suits APIs where each visitor enters their own key.
Good questions.
Does proxy.cors.dev work with a custom domain on a ChatGPT Site?
Yes. The free tier accepts any origin, so the Site works the same on its chatgpt.site address and on a connected custom domain. With cors.dev Pro, add both origins to the Connection. OpenAI's docs say custom domains are not available in Enterprise workspaces at launch.
Does it work on a workspace-only Site or one with Sign in with ChatGPT?
Yes. A Site restricted to a workspace, or one that offers Sign in with ChatGPT, still runs its page code in the visitor's browser and sends its own origin. cors.dev needs no ChatGPT sign-in: 51 of the 72 Sites that called the proxy by 6 November 2026 sit behind a ChatGPT login.
Do requests to proxy.cors.dev count toward ChatGPT Sites hosting usage?
Sites hosting is free through 31 December 2026. From 1 January 2027 OpenAI plans to meter usage above each plan's included monthly allowance, with traffic measured as the requests your Site handles and proposed at about 16 credits per 100,000. A request from the visitor's browser to proxy.cors.dev goes straight to cors.dev and never reaches your Site.
Why does my Site get 429 Too Many Requests through the proxy?
Either cors.dev or the API is limiting it. X-Cors-Source: gateway with X-Cors-Error: rate_limited means one visitor went past about 600 requests a minute; wait the 60 seconds in Retry-After. X-Cors-Source: upstream means the API itself answered 429, as Yahoo Finance and CoinGecko do, so send fewer requests or batch them.
Why do requests show as cancelled in the browser's network tab?
The Site's own code aborted them. Generated code can add a timeout with AbortController or AbortSignal.timeout(), and a short one cancels the request while the proxy is still waiting on a slow API, which it allows up to 30 seconds. Ask ChatGPT to raise the timeout to 30 seconds.