# How to Connect External API Endpoints (Stripe, OpenAI) to Lovable

- Tool: Lovable
- Difficulty: Advanced
- Fix time: ~15 min
- Compatibility: All Lovable versions
- Last updated: March 2026

## TL;DR

To connect external APIs (Stripe, OpenAI, weather services, etc.) to a Lovable project, create a Supabase Edge Function that acts as a server-side proxy. Your React frontend calls the Edge Function, which adds the API key from Secrets and forwards the request to the external service. This avoids CORS errors and keeps API keys secure. Store all API keys in Cloud tab → Secrets — never in frontend code.

## Why external API calls from Lovable need a server-side proxy

When you want your Lovable app to call an external API like OpenAI, Stripe, or a weather service, you cannot make the request directly from your React frontend code. Two problems prevent this.

First, CORS (Cross-Origin Resource Sharing) blocks browser requests to different domains unless the API explicitly allows your domain. Most third-party APIs do not add your Lovable domain to their CORS headers because they expect server-to-server communication, not browser requests.

Second, security. External APIs require secret keys for authentication. If you put an API key in your React code, it is visible to anyone who opens the browser developer tools. Even environment variables with the VITE_ prefix are embedded in the built JavaScript — they are not truly secret.

The solution is a server-side proxy using Supabase Edge Functions. Your frontend sends a request to your own Edge Function (same origin, no CORS). The Edge Function reads the API key from Secrets (truly secure, never in browser code) and makes the request to the external API. The Edge Function then returns the response to your frontend.

- Calling external APIs directly from React components — blocked by CORS
- API keys in frontend code — visible in browser developer tools
- VITE_ prefixed API keys — still embedded in built JavaScript, not truly secret
- No error handling for API failures — app crashes when the external service is down
- Rate limiting not handled — too many requests from the frontend exhaust API quotas

## Error messages you might see

**Access to fetch at 'https://api.openai.com/v1/chat/completions' from origin 'https://your-app.lovable.app' has been blocked by CORS policy**

You are calling the OpenAI API directly from the browser. OpenAI does not allow browser-origin requests. Create a Supabase Edge Function proxy to make this call server-side.

**401 Unauthorized: Incorrect API key provided**

The API key you are sending is invalid, expired, or missing. Check Cloud tab → Secrets and verify the key matches what the API provider dashboard shows. Make sure the Edge Function reads the correct secret name.

**429 Too Many Requests**

You have exceeded the external API's rate limit. Add retry logic with exponential backoff in your Edge Function, and consider caching responses to reduce the number of API calls.

**TypeError: Failed to fetch**

This generic browser error usually means the request was blocked by CORS. Check the Network tab in developer tools for the actual response — the browser hides CORS error details in the generic TypeError.

## Before you start

- A Lovable project open in the editor
- An API key or token from the external service (OpenAI, Stripe, etc.)
- The external API's documentation (endpoint URLs, request format, authentication method)
- Access to Cloud tab → Secrets and Cloud tab → Edge Functions

## How to fix it

### 1. Store the external API key in Cloud tab Secrets

*API keys must be stored securely on the server — never in frontend code or VITE_ variables*

Click the + button next to Preview to open the Cloud tab. Go to Secrets and click Add Secret. Enter the secret name (for example OPENAI_API_KEY or STRIPE_SECRET_KEY) and paste the API key value. Do not use the VITE_ prefix for secret API keys — they should only be accessible from Edge Functions, not from the browser. The secret is encrypted and automatically available to all Edge Functions via Deno.env.get().

Before:

```
// WRONG: API key in frontend code — exposed to everyone
const response = await fetch('https://api.openai.com/v1/chat/completions', {
  headers: { 'Authorization': 'Bearer sk-abc123secretkey' },
});
```

After:

```
// CORRECT: API key stored in Secrets, accessed only in Edge Functions
// Cloud tab → Secrets → Add: OPENAI_API_KEY = sk-abc123secretkey
// Edge Functions access it with: Deno.env.get('OPENAI_API_KEY')
```

**Expected result:** The API key appears in the Secrets list. It is encrypted and only accessible from Edge Functions.

### 2. Create an Edge Function proxy for the external API

*The Edge Function makes the API call server-side, avoiding CORS and keeping keys secure*

Prompt Lovable in Agent Mode to create an Edge Function. Be specific about which external API you are calling and what format the request and response should have. The Edge Function should: handle OPTIONS preflight requests with CORS headers, read the API key from Secrets, validate the incoming request, call the external API, and return the response. Lovable's AI will create the function file at supabase/functions/[function-name]/index.ts.

After:

```
// supabase/functions/openai-chat/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    const { messages } = await req.json();
    const apiKey = Deno.env.get('OPENAI_API_KEY');

    if (!apiKey) {
      throw new Error('OpenAI API key not configured in Secrets');
    }

    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: 'gpt-4',
        messages,
        max_tokens: 1000,
      }),
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.error?.message || 'OpenAI API error');
    }

    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
});
```

**Expected result:** The Edge Function is created and deployed. You can call it from your frontend at /functions/v1/openai-chat.

### 3. Call the Edge Function from your React frontend

*Your frontend talks to your own Edge Function (same origin) instead of the external API (cross-origin)*

In your React component or service file, replace the direct external API call with a call to your Edge Function. Use the Supabase client's functions.invoke method or a plain fetch to /functions/v1/[function-name]. Pass only the data the Edge Function needs — the API key is already stored in Secrets and does not need to be sent from the frontend.

Before:

```
// Direct external API call — blocked by CORS
async function askAI(question: string) {
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${import.meta.env.VITE_OPENAI_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'gpt-4', messages: [{ role: 'user', content: question }] }),
  });
  return response.json();
}
```

After:

```
// Call through Edge Function proxy — no CORS, key is secure
import { supabase } from '@/integrations/supabase/client';

async function askAI(question: string) {
  const { data, error } = await supabase.functions.invoke('openai-chat', {
    body: { messages: [{ role: 'user', content: question }] },
  });

  if (error) throw new Error(error.message);
  return data;
}
```

**Expected result:** The API call succeeds without CORS errors. The API key is never exposed to the browser.

### 4. Add error handling and loading states to your API integration

*External APIs can fail, be slow, or rate-limit you — your UI must handle all these cases gracefully*

Wrap your API calls in proper error handling with loading states, error messages, and retry logic. Users should see a loading indicator while the request is in progress, a helpful error message if it fails, and the result when it succeeds. For rate-limited APIs, add exponential backoff in the Edge Function so temporary failures retry automatically. If integrating multiple external APIs with different error patterns requires changes across many components, RapidDev's engineers have connected 600+ Lovable projects to external APIs.

Before:

```
// No error handling — UI shows nothing on failure
function ChatComponent() {
  const [answer, setAnswer] = useState('');
  async function handleAsk() {
    const data = await askAI(question);
    setAnswer(data.choices[0].message.content);
  }
}
```

After:

```
// Proper loading, error, and success states
function ChatComponent() {
  const [answer, setAnswer] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleAsk() {
    setIsLoading(true);
    setError(null);
    try {
      const data = await askAI(question);
      setAnswer(data.choices[0].message.content);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Something went wrong');
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <div>
      <Button onClick={handleAsk} disabled={isLoading}>
        {isLoading ? 'Thinking...' : 'Ask AI'}
      </Button>
      {error && <p className="text-red-500">{error}</p>}
      {answer && <p>{answer}</p>}
    </div>
  );
}
```

**Expected result:** Users see a loading state during the request, a clear error message on failure, and the result on success.

## Complete code example

File: `src/hooks/useExternalApi.ts`

```typescript
import { useState, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client';

/**
 * Custom hook for calling external APIs through Edge Function proxies.
 * Handles loading, error, and success states automatically.
 *
 * Usage:
 *   const { data, isLoading, error, execute } = useExternalApi<ResponseType>();
 *   await execute('function-name', { key: 'value' });
 */
export function useExternalApi<T = unknown>() {
  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const execute = useCallback(
    async (functionName: string, body: Record<string, unknown>) => {
      setIsLoading(true);
      setError(null);

      try {
        const { data: responseData, error: invokeError } =
          await supabase.functions.invoke(functionName, { body });

        if (invokeError) {
          throw new Error(invokeError.message);
        }

        setData(responseData as T);
        return responseData as T;
      } catch (err) {
        const message =
          err instanceof Error ? err.message : 'API request failed';
        setError(message);
        throw err;
      } finally {
        setIsLoading(false);
      }
    },
    []
  );

  const reset = useCallback(() => {
    setData(null);
    setError(null);
    setIsLoading(false);
  }, []);

  return { data, isLoading, error, execute, reset };
}
```

## Best practices

- Always use Edge Function proxies for external API calls — never call external APIs directly from React components
- Store API keys in Cloud tab → Secrets without the VITE_ prefix — they should only be accessible from Edge Functions
- Create a custom React hook (useExternalApi) that handles loading, error, and success states consistently
- Add proper error messages in Edge Functions — return descriptive errors so the frontend can show helpful messages
- Implement retry logic with exponential backoff in Edge Functions for rate-limited or occasionally failing APIs
- Validate incoming request data in Edge Functions before forwarding to the external API — reject malformed requests early
- Log API call results in Cloud tab → Logs to monitor usage and debug failures

## Frequently asked questions

### How do I call an external API from Lovable?

Create a Supabase Edge Function that acts as a proxy. Your React frontend calls the Edge Function, which adds the API key from Secrets and forwards the request to the external API. This avoids CORS errors and keeps your API key secure.

### Can I call the OpenAI API directly from a Lovable app?

Not directly from the browser — OpenAI blocks browser-origin requests (CORS). Create an Edge Function proxy that receives your request, adds the OpenAI API key from Secrets, and calls the OpenAI API server-side. Your frontend calls the Edge Function instead.

### Where do I store API keys for external services in Lovable?

Store them in Cloud tab → Secrets without the VITE_ prefix. Keys without VITE_ are only accessible from Edge Functions (server-side), not from browser code. This keeps them truly secure. Access them in Edge Functions with Deno.env.get('API_KEY_NAME').

### How do I handle errors when calling external APIs in Lovable?

Add try/catch blocks in both the Edge Function and the React component. The Edge Function should return descriptive error messages with appropriate HTTP status codes. The React component should show loading indicators, error messages, and handle retries for transient failures.

### Can I connect to multiple external APIs in one Lovable project?

Yes. Create a separate Edge Function for each external API, or create a generic proxy function that accepts the target API as a parameter. Store each API's key as a separate secret in Cloud tab → Secrets. Use a custom React hook to call different Edge Functions with consistent loading/error handling.

### Why do I get 'Failed to fetch' when calling an external API?

This usually means CORS is blocking the request. The browser prevents your Lovable frontend from calling a different domain directly. Create an Edge Function proxy so the API call happens server-side where CORS does not apply.

### What if I need help integrating multiple external APIs?

Complex integrations with multiple APIs (Stripe payments, OpenAI chat, email services) require multiple Edge Functions, proper error handling, and coordinated secret management. RapidDev's engineers have connected 600+ Lovable projects to external APIs and can set up the complete integration architecture.

---

Source: https://www.rapidevelopers.com/lovable-issues/connecting-external-api-endpoints-to-lovable-projects
© RapidDev — https://www.rapidevelopers.com/lovable-issues/connecting-external-api-endpoints-to-lovable-projects
