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 policyYou 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 providedThe 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 RequestsYou 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 fetchThis 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
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
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().
// WRONG: API key in frontend code — exposed to everyoneconst response = await fetch('https://api.openai.com/v1/chat/completions', { headers: { 'Authorization': 'Bearer sk-abc123secretkey' },});// 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.
Create an Edge Function proxy for the external API
The Edge Function makes the API call server-side, avoiding CORS and keeping keys secure
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.
1// supabase/functions/openai-chat/index.ts2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';34const corsHeaders = {5 'Access-Control-Allow-Origin': '*',6 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',7};89serve(async (req) => {10 if (req.method === 'OPTIONS') {11 return new Response('ok', { headers: corsHeaders });12 }1314 try {15 const { messages } = await req.json();16 const apiKey = Deno.env.get('OPENAI_API_KEY');1718 if (!apiKey) {19 throw new Error('OpenAI API key not configured in Secrets');20 }2122 const response = await fetch('https://api.openai.com/v1/chat/completions', {23 method: 'POST',24 headers: {25 'Authorization': `Bearer ${apiKey}`,26 'Content-Type': 'application/json',27 },28 body: JSON.stringify({29 model: 'gpt-4',30 messages,31 max_tokens: 1000,32 }),33 });3435 const data = await response.json();3637 if (!response.ok) {38 throw new Error(data.error?.message || 'OpenAI API error');39 }4041 return new Response(JSON.stringify(data), {42 headers: { ...corsHeaders, 'Content-Type': 'application/json' },43 });44 } catch (error) {45 return new Response(JSON.stringify({ error: error.message }), {46 status: 500,47 headers: { ...corsHeaders, 'Content-Type': 'application/json' },48 });49 }50});Expected result: The Edge Function is created and deployed. You can call it from your frontend at /functions/v1/openai-chat.
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)
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.
// Direct external API call — blocked by CORSasync 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();}// Call through Edge Function proxy — no CORS, key is secureimport { 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.
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
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.
// No error handling — UI shows nothing on failurefunction ChatComponent() { const [answer, setAnswer] = useState(''); async function handleAsk() { const data = await askAI(question); setAnswer(data.choices[0].message.content); }}// Proper loading, error, and success statesfunction 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
1import { useState, useCallback } from 'react';2import { supabase } from '@/integrations/supabase/client';34/**5 * Custom hook for calling external APIs through Edge Function proxies.6 * Handles loading, error, and success states automatically.7 *8 * Usage:9 * const { data, isLoading, error, execute } = useExternalApi<ResponseType>();10 * await execute('function-name', { key: 'value' });11 */12export function useExternalApi<T = unknown>() {13 const [data, setData] = useState<T | null>(null);14 const [isLoading, setIsLoading] = useState(false);15 const [error, setError] = useState<string | null>(null);1617 const execute = useCallback(18 async (functionName: string, body: Record<string, unknown>) => {19 setIsLoading(true);20 setError(null);2122 try {23 const { data: responseData, error: invokeError } =24 await supabase.functions.invoke(functionName, { body });2526 if (invokeError) {27 throw new Error(invokeError.message);28 }2930 setData(responseData as T);31 return responseData as T;32 } catch (err) {33 const message =34 err instanceof Error ? err.message : 'API request failed';35 setError(message);36 throw err;37 } finally {38 setIsLoading(false);39 }40 },41 []42 );4344 const reset = useCallback(() => {45 setData(null);46 setError(null);47 setIsLoading(false);48 }, []);4950 return { data, isLoading, error, execute, reset };51}Best practices to prevent this
- 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to connect a Lovable (lovable.dev) project to an external API. My project uses Vite + React + TypeScript + Supabase. The external API I want to use: - Service: [OpenAI / Stripe / weather API / etc.] - API documentation link: [URL] - Authentication: [API key / OAuth / etc.] - Endpoints I need: [list the endpoints and what they do] Please: 1. Create a Supabase Edge Function proxy for this API 2. Show me how to store the API key securely 3. Create a React hook for calling the Edge Function with loading/error states 4. Add proper error handling and validation in the Edge Function 5. Show me how to handle rate limiting
Create a Supabase Edge Function at supabase/functions/[API-NAME]-proxy/index.ts that proxies requests to [EXTERNAL API URL]. The Edge Function should: handle OPTIONS preflight with CORS headers, read the API key from Secrets using Deno.env.get('[API_KEY_NAME]'), validate the request body, call the external API endpoint, and return the response. Also create a React hook at src/hooks/use[ApiName].ts that calls this Edge Function using supabase.functions.invoke with loading, error, and data states. Store the API key in Cloud tab → Secrets as [API_KEY_NAME].
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation