Article

CORS Errors and API Integration Nightmares in Vibe-Coded Apps

Access blocked by CORS policy, API calls timing out, and mysterious 403 errors—why API integrations fail in production and how to fix them for good.

November 6, 2025
8 min read
By Deploy Your Vibe Team
corsapiintegrationfetchwebhooks

CORS Errors and API Integration Nightmares in Vibe-Coded Apps

Your app loads perfectly. Users can navigate around, see content, interact with the UI. Then they try to use a feature that calls an external API—and everything breaks. The browser console shows the dreaded message: "Access to fetch at 'api.example.com' has been blocked by CORS policy."

CORS errors and API integration failures are among the most confusing issues for non-technical founders, yet they're incredibly common in AI-generated applications. Let's demystify them.

What Is CORS and Why Does It Exist?

Cross-Origin Resource Sharing (CORS) is a security feature built into web browsers. It prevents malicious websites from making unauthorized requests to APIs on your behalf.

How It Works

When your app at https://yourapp.com tries to call an API at https://api.stripe.com, the browser:

  1. Sends a preflight request (OPTIONS) to the API
  2. Checks if the API allows requests from yourapp.com
  3. If allowed, sends the actual request
  4. If not allowed, blocks it and shows a CORS error

The Development vs. Production Gap

AI coding tools often run in special environments that bypass CORS during development:

  • Bolt.new WebContainer - No CORS restrictions in preview
  • Replit - Proxy handles CORS automatically
  • v0 Preview - Runs on localhost, often CORS-free
  • Lovable Preview - Built-in CORS bypass

Then you deploy to production, and suddenly every API call fails.

The Four Types of CORS Errors

1. No 'Access-Control-Allow-Origin' Header

Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

Translation: The API server didn't explicitly allow your domain to access it.

Fix (if you control the API):

// Express.js example
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://yourapp.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

Fix (if you don't control the API): Use a backend proxy (explained below).

2. Credentials Mode Mismatch

Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Credentials' header in the response is ''
which must be 'true' when the request's credentials mode is 'include'.

Translation: You're trying to send cookies/auth headers, but the API isn't configured for it.

Fix:

// Frontend
fetch('https://api.example.com', {
  credentials: 'include',  // Sends cookies
});

// Backend must respond with
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', 'https://yourapp.com'); // Can't use *

3. Preflight Request Failure

Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not
have HTTP ok status.

Translation: The OPTIONS request (preflight) failed before the actual request was sent.

Fix: Handle OPTIONS requests explicitly:

// Express.js
app.options('*', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://yourapp.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(200);
});

4. Wildcard with Credentials

Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Origin' header in the response must not
be the wildcard '*' when the request's credentials mode is 'include'.

Translation: You can't use Access-Control-Allow-Origin: * when sending cookies.

Fix:

// ❌ Won't work with credentials
res.header('Access-Control-Allow-Origin', '*');

// ✅ Specify exact origin
const allowedOrigins = ['https://yourapp.com', 'https://staging.yourapp.com'];
if (allowedOrigins.includes(req.headers.origin)) {
  res.header('Access-Control-Allow-Origin', req.headers.origin);
  res.header('Access-Control-Allow-Credentials', 'true');
}

The Backend Proxy Solution

When you don't control the API (using third-party services like weather APIs, stock data, etc.), create a proxy through your own backend:

Why This Works

  • Your frontend calls YOUR backend (same origin, no CORS)
  • Your backend calls the third-party API (server-to-server, no CORS)
  • Your backend returns the data to your frontend

Implementation

// Backend proxy endpoint (Next.js API route example)
// pages/api/proxy.ts
export default async function handler(req, res) {
  const { url, ...options } = req.body;

  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_KEY}`,
      },
    });

    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    res.status(500).json({ error: 'API call failed' });
  }
}

// Frontend call
const data = await fetch('/api/proxy', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url: 'https://external-api.com/data',
    method: 'GET',
  }),
});

Bolt.new WebContainer CORS Limitations

Bolt.new runs in a WebContainer, which has built-in CORS restrictions that cannot be disabled through the UI. As of December 2024, StackBlitz is working on improving CORS handling, but limitations remain.

Workarounds for Bolt.new

1. Use CORS-Anywhere Proxies (development only)

const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
const targetUrl = 'https://api.example.com/data';
fetch(proxyUrl + targetUrl);

⚠️ Warning: Never use public proxies in production—they can steal your data.

2. Configure API Provider

Some APIs let you whitelist the Bolt.new preview domain:

  • Find your Bolt.new preview URL (e.g., abc123.bolt.new)
  • Add it to your API's allowed origins

3. Move to Production

WebContainer CORS issues often disappear when deploying to a real hosting platform like Vercel or Netlify.

Edge Function CORS Errors

Cloudflare Workers, Vercel Edge Functions, and Netlify Edge Functions have unique CORS requirements.

Common Edge Function CORS Mistake

// ❌ Missing CORS headers in edge function
export default async function handler(req: Request) {
  const data = await fetchSomeData();
  return new Response(JSON.stringify(data));
}

When called from a different origin, this fails.

Correct Edge Function CORS

// ✅ Proper CORS headers
export default async function handler(req: Request) {
  // Handle preflight
  if (req.method === 'OPTIONS') {
    return new Response(null, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
    });
  }

  const data = await fetchSomeData();

  return new Response(JSON.stringify(data), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
    },
  });
}

API Key Exposure in Client-Side Code

AI tools frequently commit this cardinal sin: putting API keys directly in frontend code.

// ❌ API key visible to anyone
const response = await fetch('https://api.openai.com/v1/chat', {
  headers: {
    'Authorization': 'Bearer sk-abc123...',  // Exposed!
  },
});

Anyone can open DevTools, see your API key, and rack up charges on your account.

The Secure Pattern

// ✅ Backend handles API calls
// Frontend
const response = await fetch('/api/openai', {
  method: 'POST',
  body: JSON.stringify({ prompt: 'Hello' }),
});

// Backend (keeps API key secret)
export default async function handler(req) {
  const { prompt } = req.body;

  const response = await fetch('https://api.openai.com/v1/chat', {
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({ prompt }),
  });

  return response.json();
}

API Rate Limiting and Timeouts

AI-generated code rarely includes retry logic, rate limit handling, or proper timeout configuration.

Problems This Causes

  • API calls fail randomly when you hit rate limits
  • Slow APIs cause the entire app to hang
  • No user feedback when requests take too long

Robust API Call Pattern

async function fetchWithRetry(url: string, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout

      const response = await fetch(url, {
        ...options,
        signal: controller.signal,
      });

      clearTimeout(timeout);

      // Handle rate limiting
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 5;
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return await response.json();

    } catch (error) {
      if (i === retries - 1) throw error;
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, 2 ** i * 1000));
    }
  }
}

Webhook Integration Failures

AI tools set up webhooks, but they fail in production due to:

1. Wrong Webhook URL

// ❌ Still pointing to localhost
const webhookUrl = 'http://localhost:3000/api/webhook';

// ✅ Use production URL
const webhookUrl = process.env.WEBHOOK_URL || 'https://yourapp.com/api/webhook';

2. Missing Signature Verification

Webhooks without verification let attackers send fake events:

// ✅ Verify webhook signatures (Stripe example)
import Stripe from 'stripe';

export default async function handler(req) {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  try {
    const event = Stripe.webhooks.constructEvent(
      req.body,
      sig,
      webhookSecret
    );
    // Process event
  } catch (err) {
    return new Response('Webhook signature verification failed', { status: 400 });
  }
}

3. Blocking Webhook Responses

Webhooks must respond quickly (< 5 seconds). Process heavy tasks asynchronously:

// ✅ Respond immediately, process later
export default async function handler(req) {
  const event = await validateWebhook(req);

  // Acknowledge receipt immediately
  res.status(200).send('Received');

  // Process asynchronously (job queue, background worker)
  await queue.add('process-webhook', { event });
}

When API Issues Get Complex

Sometimes the problem isn't obvious:

  • API works in Postman but fails from your app
  • Intermittent failures only under load
  • API calls succeed but return wrong data
  • Requests work on desktop but fail on mobile

These scenarios often involve headers, authentication flows, or platform-specific networking quirks that require expert debugging.


API integrations blocking your launch? Book a hardening sprint and we'll integrate, secure, and optimize your APIs professionally.