Noções de segurança
"Edge Function returned a non-2xx status code": o erro real
"Edge Function returned a non-2xx status code" quer dizer que sua função do Supabase respondeu com um erro. Veja onde ler o erro real.

Em resumo
- "Edge Function returned a non-2xx status code" é o que o supabase-js diz de qualquer resposta da sua função fora dos 200. O motivo real voltou na mesma resposta, e os logs da função guardam o resto.
- Leia primeiro o número de status. 401 quer dizer que a requisição foi recusada antes de o seu código rodar, 404 que não há função com esse nome publicada, 500 que o seu código falhou, 546 ou 504 que acabaram os recursos ou o tempo.
- Duas correções rápidas calam a mensagem e abrem um buraco: chamar a API pelo seu frontend com a chave dentro, e desligar a verificação de JWT sem colocar uma checagem sua no lugar.
Você aperta no seu app o botão que chama uma Edge Function e, em vez de um resultado, o app mostra isto:
Edge Function returned a non-2xx status code
É a mesma frase, seja qual for o problema. Uma chave faltando, uma função que caiu, uma requisição recusada antes de o seu código rodar: todas dizem "Edge Function returned a non-2xx status code", então a mensagem sozinha quase não diz o que consertar.
O que ela não conta é que o motivo já chegou. O erro voltou na mesma resposta que essa frase, e o supabase-js não lê o erro para você. Pesquisar a frase traz os palpites dos outros. Ler a resposta traz a sua.
O que significa "Edge Function returned a non-2xx status code"?
A sua função respondeu, e a resposta foi um erro. É só isso que a frase diz.
Toda resposta na web carrega um código de status, um número de três dígitos que
diz como a requisição foi. Qualquer coisa nos 200 quer dizer que deu certo.
Quando o seu app chama uma função com supabase.functions.invoke, o supabase-js
olha esse número e, se ele estiver fora dos 200, entrega ao seu app uma frase
fixa no lugar dos dados. A frase está escrita palavra por palavra
no próprio supabase-js,
e o erro que a carrega se chama FunctionsHttpError.
Pense na chamada como uma carta que você manda para um escritório nos fundos. O escritório responde ao que você pediu ou devolve a carta. Quando ela volta, o supabase-js lê o carimbo no envelope e avisa que ela foi devolvida. Ele não abre o envelope. O bilhete lá dentro diz por quê, e o escritório anotou num livro de registro tudo o que aconteceu enquanto estava com a sua carta.
Dois parentes próximos parecem iguais e querem dizer outra coisa. Failed to send a request to the Edge Function quer dizer que nenhuma resposta legível voltou,
e no navegador isso costuma ser um cabeçalho CORS faltando, assunto do
post sobre essa mensagem. E
se várias partes sem relação do seu app quebraram ao mesmo tempo, confira
se o problema é o Supabase ou o seu app
antes de abrir qualquer código.
Onde está o erro real?
Em dois lugares, e você consegue ler os dois sem mudar uma linha de código.
A própria resposta. Abra o seu app publicado no Chrome, Edge ou Firefox,
aperte F12, escolha a aba Network e repita o que falhou. Clique na
requisição cujo endereço contém /functions/v1/ seguido do nome da sua função.
A coluna Status tem o número, e a aba Response tem o que a função mandou
junto: uma mensagem que o seu próprio código escreveu, um conteúdo JSON curto da
plataforma do Supabase, ou as palavras Internal Server Error.
Se você, ou a ferramenta que escreve o seu código, puder mudar a chamada, o
guia de tratamento de erros do Supabase
mostra como ler essa mesma resposta dentro do app, porque a resposta original
viaja com o erro como error.context:
const { data, error } = await supabase.functions.invoke('your-function', { body })
if (error) console.log(error.context.status, await error.context.text())
O livro de registro da função. No painel do Supabase, abra Edge
Functions, escolha a função e leia duas abas. Invocations lista cada
requisição com o código de status e quanto tempo levou. Logs guarda o que o
guia de logs do Supabase
chama de eventos da plataforma, exceções não tratadas e mensagens de log
próprias, ou seja, tudo o que o seu código imprimiu com console.log ou
console.error. Reproduza a falha uma vez, anote o horário e leia as duas abas
naquele minuto.
Leia primeiro o código de status
O número diz até onde a sua requisição chegou antes de ser devolvida, e isso decide onde o motivo está escrito.
| Status | O que aconteceu | Onde está o motivo |
|---|---|---|
401 | Recusada antes de o seu código rodar, ou o seu código recusou | A resposta. Invalid JWT ou Missing authorization header é a plataforma |
404 | Não há função com esse nome publicada neste projeto | A resposta diz Requested function was not found |
500 | O seu código rodou e lançou um erro, ou devolveu 500 de propósito | A aba Logs |
503 | A função nem chegou a iniciar | A aba Logs: um import, um erro de sintaxe ou a configuração da função |
504 | Nenhuma resposta em 150 segundos | A aba Invocations, depois o que o seu código estava esperando |
546 | Acabou o tempo de CPU ou a memória | A aba Logs diz qual |
| Qualquer outro 4xx | O seu próprio código escolheu, por exemplo ao repassar a resposta de um serviço | A resposta |
Os significados vêm da referência de códigos de status do Supabase e da página de limites. Um 404 não precisa de nada além desta tabela: compare o nome da função no seu app com a lista do painel e confira se ela está publicada no projeto com que o seu app conversa. As outras linhas têm uma seção cada, logo abaixo.
500: a sua função rodou e falhou
Alguma coisa no seu código lançou um erro, e a aba Logs tem a linha.
A página do Supabase sobre esse erro
divide o caso em dois. Se a resposta diz exatamente Internal Server Error, o
seu código caiu e o log mostra o tipo de erro e a linha onde ele aconteceu, por
exemplo TypeError: Cannot read properties of undefined. Se a resposta diz
outra coisa, o seu código escreveu essa mensagem e devolveu o 500 de propósito,
então procure na função o status que ela enviou.
Uma causa vale descartar antes de todas as outras numa função que chama a OpenAI, o Stripe ou qualquer API paga: um segredo que existe na sua máquina e não no seu projeto publicado.
Dentro da função, um segredo é lido com Deno.env.get('OPENAI_API_KEY'). Quando
não existe segredo com esse nome, a documentação do Deno diz que isso
devolve undefined em vez de
falhar. Então a função segue em frente, monta uma requisição com a palavra
undefined onde a chave deveria estar e envia. A OpenAI responde com um 401
próprio, "Incorrect API key provided", e a sua função repassa isso ou cai ao ler
uma resposta que não esperava. Na imagem do envelope: o escritório abriu a sua
carta, foi até a gaveta onde a chave deveria estar, encontrou a gaveta vazia e
devolveu um bilhete sobre uma chave errada.
O conserto fica na página Edge Function Secrets do seu projeto no Supabase.
O guia de segredos do Supabase
diz que uma função lê um segredo novo na hora, então você não precisa publicar
de novo, e que nenhum nome de segredo pode começar com SUPABASE_, porque esse
prefixo é reservado para os valores que o Supabase define para você. Um segredo
que você define para o desenvolvimento local fica na sua máquina; o projeto
publicado tem a própria lista.
401: recusada antes de o seu código rodar
Se a resposta diz Invalid JWT ou Missing authorization header, a sua função
nunca viu a requisição. A plataforma do Supabase a barrou na porta.
Essa porta é uma verificação chamada verify_jwt, ligada por padrão, que lê o
cabeçalho Authorization de cada requisição antes de o seu código rodar. A
página do Supabase sobre o 401
lista três jeitos de não passar por ela:
- A chave não bate com as chaves do seu projeto. Por exemplo, uma chave copiada de outro projeto, ou uma antiga que continua configurada em algum lugar depois de uma rotação.
- Não existe cabeçalho
Authorizationnenhum. O supabase-js adiciona um para você. Umfetchsimples para o endereço da função, ou um webhook de outro serviço, não adiciona. - O seu projeto usa as chaves de assinatura novas do Supabase. A verificação embutida só entende o formato antigo, e a solução do Supabase é desligar a verificação para essa função.
Esse último ponto vai além de um caso só. O Supabase agora recomenda desligar a verificação embutida e cuidar da autenticação direto no código da sua função. A recomendação tem duas metades, e a segunda é a que fica pelo caminho.
Se o 401 veio com uma mensagem que o seu próprio código escreveu, a plataforma
deixou a requisição passar e a sua função a recusou, ou repassou um 401 do
serviço que chamava, que muitas vezes é o segredo vazio da seção do 500. Essa
também é uma verificação diferente da que está por trás de PGRST301 nas suas
consultas ao banco, que tem uma página própria. Se
o log da função mostra invalid claim: missing sub claim, ela perguntou ao
Supabase Auth quem estava chamando e recebeu a chave do seu projeto no lugar de
um usuário logado, o que
o post dessa mensagem acompanha passo a
passo.
546 e 504: sem recursos ou sem tempo
O Supabase para uma função que consome demais, e os dois números dizem qual limite ela atingiu.
546 é processamento. A
página de limites dá a uma
função 256 MB de memória e a cada requisição 2 segundos de tempo de CPU. Passe
de qualquer um dos dois e a resposta diz
Function failed due to not having enough compute resources (please check logs),
com o código WORKER_RESOURCE_LIMIT (logs antigos dizem WORKER_LIMIT). A aba
Logs diz CPU Time exceeded ou Memory limit exceeded. A
página do Supabase sobre o 546
dá como exemplos processamento de imagens, embeddings de IA e web scraping, e a
resposta dela é fazer menos por requisição ou passar a parte pesada para outro
serviço.
504 é tempo. Uma função que não envia resposta em 150 segundos recebe um 504. O limite de CPU só conta o tempo em que o seu código está de fato calculando: a página de limites diz que ele não inclui entrada e saída assíncronas, e esperar a rede é exatamente isso. Então uma função esperando um modelo de IA lento não está gastando CPU. Ela está gastando o relógio de 150 segundos, e quando ele acaba você recebe um 504, não um 546.
Duas correções que pioram tudo
Duas mudanças fazem essa mensagem sumir em minutos, e as duas deixam você numa situação pior do que o erro.
Chamar a API direto pela página. O jeito mais rápido de uma função que
falha parar de falhar é parar de usá-la: colocar a chave numa variável VITE_ e
chamar a OpenAI direto do seu frontend. Funciona na primeira tentativa. Também
coloca a chave no arquivo que todo visitante baixa, porque esse prefixo é
uma instrução para compilar o valor dentro do seu bundle,
e ali qualquer pessoa consegue ler. Entre os apps publicados que escaneamos, uma
chave da OpenAI apareceu no JavaScript de 33, e
o que um estranho pode fazer com ela
tem um artigo próprio.
Desligar a verificação e parar por aí. Resolver um 401 desligando o
verify_jwt é a primeira metade do próprio conselho do Supabase. O
guia de configuração do Supabase
deixa claro o que isso faz sozinho: qualquer pessoa passa a poder invocar a sua
Edge Function sem um JWT válido. O endereço da sua função está no seu frontend,
à vista de todos. Se a função guarda uma chave da OpenAI, um estranho agora pode
gastá-la através de você. Se ela usa a chave secreta do projeto, que o Supabase
coloca no ambiente de toda função, a requisição de um estranho roda com uma
chave que passa por cima de todas as regras de row level security que você
escreveu.
A segunda metade, uma checagem dentro da função de quem está chamando, cabe em poucas linhas. Mover um segredo para uma Edge Function traz essas linhas e explica por que até a verificação padrão deixa passar mais gente do que o nome sugere.
O primeiro erro dá para ver de fora, e foi assim que o contamos. A nossa varredura gratuita lê o JavaScript do seu app publicado como um estranho leria e aponta cada chave que encontra, dizendo se ela é das que devem estar ali. Leva uns 20 segundos e não precisa de conta: escaneie o seu app.
Para a chave ficar onde você a colocou
O conserto de hoje pode ser desfeito pelo prompt da semana que vem. Um builder a quem você pede que faça uma função voltar a funcionar tem liberdade para pegar o atalho acima, e o seu app continua funcionando enquanto isso.
O Reeve Monitor confere a cada hora por você:
- as nove verificações a cada hora, em até três apps
- um e-mail no dia em que uma nova varredura avaliar o seu app pior que a anterior; uma chave secreta no seu JavaScript limita sozinha a nota a D
- se o app está no ar, a cada 60 segundos
- um relatório mensal do que ele viu
Uma função que roda com a sua chave secreta passa por cima de todas as regras de row level security. O Reeve Care guarda uma cópia do seu banco Supabase onde nada no seu projeto alcança.
- uma cópia criptografada conforme uma agenda, guardada no armazenamento do próprio Reeve na UE, fora da sua conta Supabase
- cada cópia verificada antes de contar, contando as linhas de cada tabela
- uma restauração em um clique que salva o que está lá agora antes de substituir
- também os seus arquivos enviados, assim que você conectar uma credencial de Storage
- tudo o que o Monitor faz
O que fazer agora
O que fazer
- Reproduza a falha uma vez e anote o horário.
- Abra a requisição na aba Network do navegador e leia o status e a resposta antes de mudar qualquer coisa.
- Leia as abas Invocations e Logs da função no painel do Supabase naquele minuto.
- Num 500 de uma função que chama uma API paga, confira se o segredo está definido na página Edge Function Secrets do projeto publicado. Depois não é preciso publicar de novo.
- Num 401 da plataforma, corrija a chave ou desligue a verificação embutida, e acrescente na mesma mudança uma checagem de quem está chamando dentro da função.
- Nunca resolva este erro movendo a chave para uma variável
VITE_.
A checagem de quem está chamando é o passo mais fácil de pular, e o guia de segredos em Edge Functions trata dela por inteiro. A checklist de segurança de 10 minutos cobre o que costuma ficar aberto num app recém-lançado.
Perguntas frequentes
O que significa "Edge Function returned a non-2xx status code"?
Significa que a sua Edge Function do Supabase respondeu com um status de erro, qualquer número fora dos 200, e o supabase-js transformou essa resposta numa frase fixa. A frase é a mesma para uma queda do código, uma chave faltando, uma requisição recusada antes de o seu código rodar e um tempo esgotado. A resposta em si, que diz qual deles foi, continua presa ao erro, e a função mantém um log próprio.
Como eu vejo o erro real por trás?
Abra o seu app publicado, aperte F12, escolha a aba Network e repita o que falhou. Clique na requisição cujo endereço contém /functions/v1/ e leia o status e a resposta: é isso que a função enviou de fato. Depois abra Edge Functions no painel do Supabase, escolha a função e leia as abas Invocations e Logs no mesmo horário. No código, o guia do Supabase lê essa mesma resposta com await error.context.json().
Por que a minha Edge Function funciona localmente e falha depois de publicada?
Confira primeiro se um segredo existe na sua máquina e não no seu projeto publicado. O ambiente local lê o próprio arquivo de ambiente, e a função publicada lê os segredos definidos no projeto. Um segredo que falta volta como undefined em vez de erro, então a função segue em frente e falha mais adiante, geralmente quando o serviço chamado recusa uma chave vazia. Defina-o na página Edge Function Secrets; o Supabase diz que um segredo novo é lido na hora, sem novo deploy.
Devo desligar a verificação de JWT para resolver um 401?
O Supabase agora recomenda desligar a verificação embutida e checar quem está chamando dentro da sua função. As duas metades importam. Desligada sozinha, a verificação não segura mais nada, e a própria documentação do Supabase avisa que assim qualquer pessoa pode invocar a sua função sem um JWT válido. Se a função guarda uma chave da OpenAI ou a chave secreta do projeto, isso é qualquer pessoa na internet gastando a sua cota ou passando por cima das suas regras de row level security.
O que significa o erro 546 numa Edge Function do Supabase?
A função ficou sem processamento: mais de 2 segundos de tempo de CPU, ou mais que a memória dela, numa requisição. A resposta diz "Function failed due to not having enough compute resources (please check logs)", e a aba Logs diz qual limite foi, CPU Time exceeded ou Memory limit exceeded. Esperar uma API lenta não conta como tempo de CPU; uma função que espera demais recebe um 504 no lugar.