Learn why Lovable fails to detect custom API endpoints, plus how to set them up and integrate APIs with proven best practices.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Lovable often can't detect or use custom API endpoints properly because the Preview/build environment is not the same as your local dev server: environment variables and Secrets aren't automatically imported, local dev proxies or servers (Vite/webpack proxies, Docker, local-only backends) don't run in Lovable, and CORS/origin/allowlist differences or build-time vs runtime environment baking cause the client to point to the wrong URL or fail at runtime.
// Prompt 1: find where API base URL is defined
// Search the repository for common env keys and client references and show matches.
Please search the repo for these tokens and return file paths with the matching lines: API_URL, API_BASE, NEXT_PUBLIC_API, VITE_API_URL, process.env., import.meta.env, fetch('/api'), axios.create, new URL(,).
Then open the top 3 files that look like API clients (show full file contents) and indicate whether the base URL is hard-coded, uses an env var, or is relative.
// Prompt 2: check Lovable Secrets/environment
// Confirm whether the env keys found above are configured in Lovable.
Open the project's Secrets/Environment settings in Lovable. List which secret names exist (masked values okay). For each env key referenced by the code (from Prompt 1), state if a matching secret exists. If not present, add a note explaining the likely runtime impact.
// Prompt 3: reproduce and capture Preview network errors
// Use the Preview Console/Network and collect exact details of failed API calls.
Open the Preview and run the user flow that triggers the failing API call. Copy the full network request URL, request headers, response status, response body or error, and any console CORS/errors. Paste those raw logs here.
// Prompt 4: detect dev-proxy or local-only routing
// Search for dev proxy/rewrites used only in local dev configurations.
Search for vite.config.*, webpack.config.*, next.config.js, and look for devServer.proxy, server.proxy, rewrites, or dev-only middleware. Show the relevant config block and explain whether this proxy would run in Lovable Preview (hint: it will not).
// Prompt 5: check for server/edge functions or API routes
// Verify whether API endpoints are part of the app and how they are built/deployed.
List files under pages/api, src/pages/api, api/, functions/, or server/ and show their contents. For each, state whether it's a Next/API route, serverless function, or a placeholder that requires external deployment.
// Prompt 6: flag build-time env usage
// Identify env keys used at build time vs runtime.
Search for process.env.<KEY> and import.meta.env.<KEY>. For each key used, mark if it's read during build (e.g., in top-level module scope) or at runtime (inside functions). Explain which ones must be set as Lovable Secrets before building/publishing.
// Prompt 7: note when terminal/CI is needed
// If analysis finds local-only services or native tooling, create a clear note for external steps.
If any dependency requires a local dev server, Docker, or terminal build step, create DEPLOY_NOTES.md in project root with a one-paragraph explanation: "This project depends on X which cannot run in Lovable Preview. To test fully, sync to GitHub and run Y locally or deploy to your target environment (terminal required)." Show the file contents.
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
Create a server-side route file inside your Lovable project (an API endpoint file under an /api or /pages/api folder depending on your framework), store any secret keys using Lovable’s Secrets UI, update the client to call that internal endpoint (fetch('/api/your-endpoint')), Preview to test, and Publish when ready. If you need native packages or a custom runtime that requires running terminal commands, export/sync to GitHub and perform installs/builds outside Lovable (terminal required).
Paste into Lovable Chat Mode. Ask Lovable to detect framework and create the endpoint. This prompt instructs Lovable to add a server endpoint file and the handler code.
// Please create a server API endpoint in the correct location for this project.
// If this repo uses Next.js (package.json contains "next" or next.config.js exists), create:
// src/pages/api/custom-endpoint.ts
// Otherwise create a generic serverless endpoint at:
// api/custom-endpoint.ts
//
// Add the following TypeScript handler code in the new file.
// The handler reads process.env.EXTERNAL_API_KEY (set via Lovable Secrets UI).
// It proxies a request to https://example.com/data and returns JSON.
Create file: src/pages/api/custom-endpoint.ts // or api/custom-endpoint.ts if not Next.js
Content:
// file: src/pages/api/custom-endpoint.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const key = process.env.EXTERNAL_API_KEY
if (!key) {
return res.status(500).json({ error: 'Missing server secret: EXTERNAL_API_KEY' })
}
// Proxy to external API using the secret
const upstream = await fetch('https://example.com/data', {
headers: { 'Authorization': `Bearer ${key}` }
})
const data = await upstream.json()
return res.status(200).json({ ok: true, data })
}
Use Lovable’s Secrets panel — this is UI work, not a file edit. Paste and follow this instruction in Lovable to create the secret.
// Open the Secrets panel in Lovable Cloud for the current project.
// Add a new secret with key: EXTERNAL_API_KEY
// Value: (paste your external API key or token)
// Scope: choose the environment(s) you will Preview/Publish to
// Save the secret.
Tell Lovable to create/update a client helper that calls the new endpoint and to wire it into an existing page or component.
// Create file: src/lib/fetchCustom.ts
// Add a small wrapper the app can import and use.
Create file: src/lib/fetchCustom.ts
Content:
// file: src/lib/fetchCustom.ts
export async function fetchCustomData() {
// This calls the internal endpoint we just added
const res = await fetch('/api/custom-endpoint')
if (!res.ok) throw new Error('Failed to fetch custom endpoint')
return res.json()
}
// Update an example page/component that needs this behavior, e.g. src/pages/index.tsx
// Insert a simple useEffect call to fetch data on load and log it.
Update file: src/pages/index.tsx
Find: inside the main component function
Insert:
// file: src/pages/index.tsx (modified)
import { useEffect } from 'react'
import { fetchCustomData } from '../lib/fetchCustom'
useEffect(() => {
fetchCustomData().then(console.log).catch(console.error)
}, [])
Use Lovable’s Preview and Publish controls — this tests the server endpoint using the environment and secrets you set.
// In Lovable UI:
// 1. Click Preview. Load the page that triggers fetchCustomData.
// 2. Confirm the Preview console or response shows the proxied data.
// 3. If okay, click Publish to deploy with the configured secret in that deployment environment.
If the endpoint requires installing binaries or new packages that Lovable cannot add in-app, use GitHub sync/export and do the install/build outside Lovable.
// Outside Lovable (terminal required):
// 1. Export or sync the project to GitHub from Lovable.
// 2. Clone locally, run npm install (or yarn), run build, push changes back to GitHub.
// 3. Re-sync Lovable with the updated repo if you changed runtime configs.
The short answer: treat custom APIs as a well-encapsulated, configurable client + server-side proxy for secrets. Store credentials in Lovable Secrets, put timeouts/retries/type-checking in a small shared client module, call secret-backed endpoints only from server-side routes (or external serverless functions), and use Lovable Preview/Publish + GitHub sync only when you need CLI or extra deployment control.
Please create a new file src/lib/apiClient.ts and add the following. This is a small shared wrapper with timeout, exponential backoff retry, typed JSON handling, and explicit errors. Use process.env.CUSTOM_API_BASE and process.env.CUSTOM_API_KEY for configuration (we'll add them to Lovable Secrets next).
// src/lib/apiClient.ts
// Small fetch wrapper for custom APIs with timeout, retries, and JSON handling
type FetchOptions = RequestInit & { retry?: number; timeoutMs?: number; useKey?: boolean; };
const DEFAULT_TIMEOUT = 8000;
const DEFAULT_RETRIES = 2;
function timeoutFetch(input: RequestInfo, init: RequestInit, ms: number) {
// simple AbortController timeout
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), ms);
const merged = { ...init, signal: controller.signal };
return fetch(input, merged).finally(() => clearTimeout(id));
}
async function fetchWithRetries(path: string, opts: FetchOptions = {}) {
const base = process.env.CUSTOM_API_BASE || "";
const url = base + path;
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
const retries = opts.retry ?? DEFAULT_RETRIES;
const headers: Record<string, string> = { ...(opts.headers as Record<string,string> || {}) };
if (opts.useKey) {
// use server-only key from env (do not expose to client-side bundle)
const key = process.env.CUSTOM_API_KEY;
if (!key) throw new Error("Missing CUSTOM_API_KEY in server environment");
headers["Authorization"] = `Bearer ${key}`;
}
let attempt = 0;
let backoff = 300;
while (true) {
attempt++;
try {
const res = await timeoutFetch(url, { ...opts, headers }, timeoutMs);
const text = await res.text();
const body = text ? JSON.parse(text) : null;
if (!res.ok) {
const err: any = new Error(`API ${res.status} ${res.statusText}`);
err.status = res.status;
err.body = body;
throw err;
}
return body;
} catch (err) {
if (attempt > retries) throw err;
// simple exponential backoff
await new Promise(r => setTimeout(r, backoff));
backoff *= 2;
}
}
}
export { fetchWithRetries };
Open Lovable Cloud > Secrets for this project and add the following keys (do not commit actual secrets to code):
Only apply this if your project uses Next.js. Create src/pages/api/proxy.ts with this code. The frontend will call /api/proxy to avoid exposing CUSTOM_API_KEY.
// src/pages/api/proxy.ts
// Server-side proxy: forwards client requests to the external API using server secret.
import type { NextApiRequest, NextApiResponse } from "next";
import { fetchWithRetries } from "../../lib/apiClient";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// simple validation: only allow POST to /do-action
if (req.method !== "POST") return res.status(405).end();
const payload = req.body;
const apiRes = await fetchWithRetries("/do-action", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
useKey: true, // instructs apiClient to add server key
});
return res.status(200).json(apiRes);
} catch (err: any) {
console.error("proxy error", err);
return res.status(err?.status || 500).json({ error: String(err?.message || err) });
}
}
Create or update src/components/ApiTester.tsx and call the server proxy (so no secret in browser). This UI shows error handling and loading state.
// src/components/ApiTester.tsx
import React, { useState } from "react";
export default function ApiTester() {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
async function run() {
setLoading(true);
setError(null);
try {
const r = await fetch("/api/proxy", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ input: "test" }),
});
const json = await r.json();
if (!r.ok) throw new Error(json?.error || "Request failed");
setResult(json);
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
}
return (
<div>
<button disabled={loading} onClick={run}>{loading ? "Running…" : "Run API"}</button>
{error && <div style={{color:"red"}}>{error}</div>}
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.