Pular para o conteúdo

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.

Vlad Tkachenko12 min de leitura
Um envelope devolvido com um carimbo amarelo atravessado na frente e a aba aberta, com a carta dobrada ainda lá dentro.

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.

A resposta carrega o status e o motivo. O supabase-js passa uma frase para o seu app, e o motivo continua preso ao erro e no log da função.

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.

StatusO que aconteceuOnde está o motivo
401Recusada antes de o seu código rodar, ou o seu código recusouA resposta. Invalid JWT ou Missing authorization header é a plataforma
404Não há função com esse nome publicada neste projetoA resposta diz Requested function was not found
500O seu código rodou e lançou um erro, ou devolveu 500 de propósitoA aba Logs
503A função nem chegou a iniciarA aba Logs: um import, um erro de sintaxe ou a configuração da função
504Nenhuma resposta em 150 segundosA aba Invocations, depois o que o seu código estava esperando
546Acabou o tempo de CPU ou a memóriaA aba Logs diz qual
Qualquer outro 4xxO seu próprio código escolheu, por exemplo ao repassar a resposta de um serviçoA 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 Authorization nenhum. O supabase-js adiciona um para você. Um fetch simples 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.

O status diz até onde a requisição chegou. Um 401 no portão nunca alcançou o seu código; um 401 devolvido pelo serviço chamado, sim.

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.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Faça backup do seu banco Supabase antes da próxima mudança

O Reeve guarda uma cópia criptografada em horários programados e a restaura para você se algo quebrar.

Ver backups do Supabase

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.