Short answer: this message hides the real error. Your app called a Supabase Edge Function (the small bit of server code that sends emails, takes payments or calls an AI service) and it went wrong. The real reason is in the function's reply and in its logs. Read those first, then fix the cause. Don't keep asking Lovable to "fix the error" without them. That is how the credit loop starts.
Two messages, two meanings
| What you see | What it means |
|---|---|
Edge Function returned a non-2xx status code | The function was reached and it replied with an error (a 4xx or 5xx status). The reason is in the reply. |
Failed to send a request to the Edge Function | The browser never got a reply it could read. Usually a CORS problem, a wrong or missing function, or a paused backend. |
Both come from the Supabase JavaScript library, not from your own code. That's why they all look the same.
Step 1: find the real error
In the browser
- Open your live site in a private window.
- Press F12 and open the Network tab.
- Do the thing that fails.
- Click the red request with
/functions/v1/in its name. - Write down the function name, the status number and the Response text.
The response headers also include sb-error-code. It names the problem, for example BOOT_ERROR, WORKER_ERROR or NOT_FOUND.
In the logs
- Lovable Cloud: More > Cloud > Edge functions, click the function, then View logs.
- Your own Supabase project: Dashboard > Edge Functions > your function > Logs (and Invocations for the status codes).
Pick the time it failed and read the first error line.
Make the app show the real reason. If your code only shows a toast, ask Lovable to log the reply body like this:
import { FunctionsHttpError } from "@supabase/supabase-js";
const { data, error } = await supabase.functions.invoke("send-email", { body });
if (error instanceof FunctionsHttpError) {
console.error("Function failed", error.context.status, await error.context.text());
}Step 2: match the status to the cause
| Status | Most likely cause | Go to |
|---|---|---|
| 400 | The function didn't like what the app sent (missing field, bad JSON) | Cause 6 |
| 401 | No valid login token, or the function expects a signed-in user | Cause 4 |
| 404 | Wrong function name, or the function isn't deployed | Cause 5 |
| 500 | The function crashed, often because a secret is missing | Causes 1 and 2 |
| No status, CORS error in the Console | The browser blocked the call | Cause 3 |
| Every function fails at once | The backend is paused | Cause 8 |
The causes, in the order to check them
1. A secret is missing or has the wrong name
The function reads keys like STRIPE_SECRET_KEY or RESEND_API_KEY with Deno.env.get("NAME"). If the name doesn't match exactly, it gets nothing and usually crashes with a 500.
- Lovable Cloud: check Secrets for the exact name the code uses. Capitals and underscores matter.
- Own Supabase: Edge Functions > Secrets.
- Lovable won't let you add names starting with
SUPABASE_. You don't need to. Supabase already gives every functionSUPABASE_URLand its keys. - Keys that start with
sk_,sb_secret_orservice_rolebelong in Secrets only. Never in the front end.
2. The function crashes and nobody catches it
The log shows an uncaught error or WORKER_ERROR. Wrap the work in try / catch and send back a clear message, with the CORS headers, so the next failure is readable:
try {
// the function's work
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return Response.json({ error: message }, { status: 500, headers: corsHeaders });
}3. CORS: the browser blocks the call
This gives "Failed to send a request to the Edge Function", and the Console shows something like blocked by CORS policy. Before the real call, the browser sends a quick check (an OPTIONS request). The function must answer it, and every reply must carry the CORS headers, errors included.
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
// ...
});Newer versions of @supabase/supabase-js (2.95.0 and later) let the function import these headers instead: import { corsHeaders } from "npm:@supabase/supabase-js@^2/cors". That keeps the list up to date.
If the function only allows one site (for example https://lovable.dev), calls from your preview link or your own domain get blocked. Allow every address the app really runs on.
4. 401: the function wants a logged-in user
By default, Edge Functions check for a valid login token before your code runs. You get a 401 when:
- the user isn't signed in, or their session ran out
- the call goes out before the app has loaded the session
- a webhook (Stripe, for example) calls the function. Webhooks don't send a Supabase login token.
Fixes: make sure a session exists before calling. For webhooks, turn JWT checking off for that function only and check the webhook's own signature in the code instead. Only do this for functions that truly need no login.
New-style keys (sb_publishable_...) are not login tokens. They go in the apikey header, not Authorization: Bearer.
5. 404: the function isn't there
- Check the name in
supabase.functions.invoke("...")matches the deployed function exactly. Look out for typos and capital letters. - Some Lovable users report edits to a function that were never deployed. Ask Lovable to redeploy that function, then check Edge functions for the new update time.
NOT_FOUND_FUNCTION_BLOBmeans the function exists but its code bundle is missing. Redeploy it. If that doesn't help, contact Lovable or Supabase support.
The browser often shows a platform 404 as a CORS error. If CORS looks right, check the logs for a 404.
6. 400 or 500 from your own code or another service
If the log shows your own message, or an error from Stripe, OpenAI, Resend or another service, the function did its job. It's passing on someone else's "no". Common reasons: a wrong or expired API key, no credit left on that account, or a missing field in what the app sent. Fix it at the source.
7. Timeouts and limits
IDLE_TIMEOUT means the function took too long to reply. WORKER_LIMIT or WORKER_RESOURCE_LIMIT means it used too much memory or CPU. Look for slow outside calls, big database queries or loops. Break big jobs into smaller ones.
8. Everything fails at once: the backend is paused
Edge Functions don't run while a Lovable Cloud project is paused. A free Supabase project also pauses when it's idle. Resume it, wait a few minutes, then test again.
A better prompt for Lovable
Paste the real error, not just the toast:
My edge function "<name>" fails when I <action>.
Status: <number>. Response: <paste>. Log line: <paste>.
Fix only this cause. Don't change other functions or the database.
Then tell me which secret names the function reads.How to check it's fixed
- Open the live site in a private window.
- Do the action again, signed in and signed out if both should work.
- In the Network tab, the
/functions/v1/request now returns 200. - The function logs show a success, and the email, payment or record really happened.