Seu formulário de login acabou de mostrar 101 Invalid username/password para alguém que digitou a senha certa. Ou o app recebeu um 209 segundos depois de entrar, ou um 119 numa query que funcionava ontem, ou um 142 com uma frase que ninguém da sua equipe escreveu. O número é todo o diagnóstico que você tem, e a documentação dá uma linha para cada um.
Um código de erro do backend é o campo numérico code no corpo JSON de uma requisição que falhou, separado do status HTTP; um código pode ter várias causas, e a mensagem error ao lado dele é o que as distingue. Essa frase é o post. O resto é prova, um código de cada vez.
Escrevemos um script que dispara cada código de propósito e o rodamos três vezes contra dois backends ao vivo em 24 de setembro de 2026: 48 requisições, 43 respostas de erro, 15 códigos numéricos. Todo status e corpo abaixo foi colado dessas rodadas, e as rodadas estão no repositório.
Pegue o código funcionando: o repositório companion está em github.com/templates-back4app/backend-error-codes — reproduce.sh imprime uma linha por requisição (código esperado, status HTTP, código, mensagem).
No mesmo repositório, seed.sh prepara as classes e os usuários, cloud/main.js é o Cloud Code por trás dos erros de hook, e results/ guarda as rodadas que este post cita.
Qual código significa o quê, e onde cada um é produzido?
A tabela abaixo é a resposta ao título. O código é o campo code do corpo; o status HTTP é o que curl -w '%{http_code}' imprimiu; a causa é o que fizemos para obtê-lo. O primeiro estágio dentro do backend que objeta responde, e nada depois dele roda, então a ordem das seções abaixo é a ordem em que uma requisição os encontra.
| Código | HTTP | Significado | Causa mais provável | Seção |
|---|---|---|---|---|
| 101 | 404 | Invalid username/password. | senha errada, ou um username que não existe | POST /login |
| 101 | 404 | Object not found. | um id que não existe, ou uma linha que a ACL de quem chama não deixa ver | uma linha que você vê |
| 101 | 404 | Permission denied, user needs to be authenticated. | a permissão por classe exige sessão e nenhuma foi enviada | requisição anônima |
| 119 | 400 | Permission denied for action count on class Note. | a permissão por classe não permite ninguém naquela operação; count é uma entrada própria | count vs find |
| 119 | 400 | sua própria mensagem (moderators only.) | uma Cloud Function lançou OPERATION_FORBIDDEN | count vs find |
| 142 | 400 | sua própria mensagem (username needs at least 3 characters.) | um hook beforeSave lançou VALIDATION_ERROR | cadastro 142 |
| 202 / 203 | 400 | Account already exists for this username. / …email address. | cadastro com um username ou e-mail já usado | cadastro 202 / 203 |
| 209 | 400 | Invalid session token | um token inventado, nenhum token, ou um token que fez logout | 209 após logout |
| 206 | 400 | sua própria mensagem (log in to create a note.) | um hook precisava de request.user e a requisição não tinha sessão (master key incluída) | master key 206 |
| 141 | 400 | Invalid function: "doesNotExist", ou a mensagem da exceção | função sem deploy, ou um erro não-Parse lançado no Cloud Code | 141 |
| 105 / 107 / 111 / 125 | 400 | nome de campo inválido / nome de classe inválido / tipo errado / e-mail inválido | o corpo da requisição | códigos menores |
| 200 / 201 / 204 | 400 | username / senha / e-mail ausente | o corpo da requisição | códigos menores |
| nenhum | 401 | unauthorized | sem X-Parse-Application-Id, ou um que não nomeia app nenhum | 404 vs 403 |
| nenhum | 403 | unauthorized | App ID certo, chave errada ou ausente | 404 vs 403 |
| nenhum | 400 | a frase do parser de JSON | o corpo não é JSON válido | 404 vs 403 |
| nenhum | 404 | {"message":"Not Found","error":{}} | o caminho não existe | 404 vs 403 |
Como medimos: reproduce.sh, 29 requisições contra o backend user-auth-starter e 19 contra o lockdown-lab, os dois no plano Free em USA East, cada grupo rodado três vezes entre 13:13 e 13:29 UTC em 24 de setembro de 2026.
O status é o que curl -w '%{http_code}' imprimiu; code e error são lidos do corpo pelo python3. As linhas foram idênticas nas três rodadas, com a JavaScript key e de novo com a Client key.
Por que POST /login retorna 101 Invalid username/password?
Porque a senha está errada, ou o username não existe, e o backend se recusa a dizer qual dos dois. Isso é proposital: um formulário de login que diz usuário não existe entrega a um atacante uma lista de contas. Os dois casos responderam HTTP 404 com o mesmo corpo, três rodadas em três.
# Stack: bash + curl | File: reproduce.sh (rows 8 and 9)
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/login" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "Content-Type: application/json" -d '{"username":"ana","password":"wrong"}'
{"code":101,"error":"Invalid username/password."}
HTTP 404
A correção fica do seu lado do formulário: uma mensagem genérica (usuário ou senha incorretos), um link para o fluxo de redefinição e nenhum loop de retry.
Se um usuário insiste que a senha está certa, verifique se o username tem um espaço no fim e se a conta foi criada com um username diferente do que ele digita; o e-mail não é aceito no lugar do username a menos que você o envie no campo email.
Mais um jeito de chegar a essa linha: GET /login?username=ana&password=wrong responde o mesmo 404 / 101 (linha 29). O endpoint aceita query string, então uma senha na URL acaba num log. Envie no corpo.
Por que uma linha que vejo na dashboard retorna 101 Object not found?
Porque a ACL daquela linha não inclui você. Uma linha que quem chama não pode ler e uma linha que não existe recebem o mesmo status, código e mensagem, de propósito, para que um id não possa ser sondado quanto à existência. Medimos os dois casos, como ana para um id inventado e como bob para uma nota que pertence à ana:
# Stack: bash + curl | File: reproduce.sh (data rows 4, 8 and 9)
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/Note/xxxxxxxxxx" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "X-Parse-Session-Token: $ANA_TOKEN"
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/Note/$ANA_NOTE_ID" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "X-Parse-Session-Token: $BOB_TOKEN"
{"code":101,"error":"Object not found."}
HTTP 404
O mesmo corpo voltou para o PUT do bob naquela nota, e para a master key pedindo um id que não existe (GET /classes/_User/xxxxxxxxxx).
A classe Note deste teste recebe a ACL de um hook beforeSave que concede leitura e escrita só a quem criou a linha, que é a configuração de Seu frontend fala direto com o banco de dados. Veja como proteger o backend.
A correção depende de qual caso é o seu. Se o id é real, abra a linha na dashboard e leia a coluna ACL: se ela nomeia um único id de usuário ou está vazia, quem chama não está na lista. O caminho certo então é uma Cloud Function que faz a leitura com a master key para quem deve enxergar a linha.
Se a ACL é pública e você ainda recebe 101, o id está errado; ids diferenciam maiúsculas de minúsculas.
Por que uma requisição anônima retorna 101 Permission denied, user needs to be authenticated?
Porque a permissão por classe daquela operação é Authenticated e a requisição não trazia session token. Esta é a terceira e última face do 101, e a única cuja mensagem diz o que fazer. Toda requisição anônima a Note (find, create e get por id) recebeu isso, três rodadas em três:
# Stack: bash + curl | File: reproduce.sh (data rows 1 to 3)
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/Note" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY"
{"code":101,"error":"Permission denied, user needs to be authenticated."}
HTTP 404
Repare na ordem: um GET /classes/Note/xxxxxxxxxx anônimo recebe esta mensagem, não Object not found. A permissão da classe é verificada antes de a linha ser procurada.
A correção é uma sessão: faça o login do usuário e envie X-Parse-Session-Token, ou, se a operação realmente deve ser pública, mude a permissão por classe em Database → a classe → Security → Class Level Permission.
As duas configurações levam tempo para chegar a todos os servidores: depois de uma mudança de permissão, já vimos requisições anônimas respondidas de forma inconsistente por 22 minutos, então sonde até receber a mesma resposta várias vezes seguidas.
Por que count retorna 119 quando find na mesma classe funciona?
Porque count é uma entrada própria nas permissões por classe, e a entrada estava vazia. ana, logada, recebeu 200 em GET /classes/Note e 400 / 119 em GET /classes/Note?count=1 na mesma rodada. Ninguém tinha travado count de propósito. As permissões tinham sido escritas pela schema API sem a chave count, e o backend guardou isso como count: {}, que não permite ninguém.
# Stack: bash + curl | File: reproduce.sh (data rows 5 and 6)
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/Note?count=1&limit=0" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "X-Parse-Session-Token: $ANA_TOKEN"
{"code":119,"error":"Permission denied for action count on class Note."}
HTTP 400
Essa é a forma geral do 119: a operação é negada por configuração, seja você quem for. Reproduzimos de mais duas maneiras. Uma classe Vault criada com toda permissão em {} respondeu 119 em find e em create, tanto para a ana quanto para um chamador anônimo (Permission denied for action find on class Vault.). E uma Cloud Function que verifica uma role lançou o código de propósito:
// Stack: Node.js 22.x | Parse Server 7.x | File: cloud/main.js (moderatorList)
const isMod = await new Parse.Query(Parse.Role).equalTo("name", "moderator").equalTo("users", request.user).first({ useMasterKey: true });
if (!isMod) throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, "moderators only.");
ana chamando moderatorList recebeu 400 / 119 moderators only.; bob, que está na role, recebeu a lista. A correção de um 119 nunca é um retry.
Ou a permissão está certa e o cliente não deveria fazer aquela chamada, ou está errada e a correção fica no diálogo Class Level Permission da dashboard (ou um PUT /schemas/Note com a master key, desta vez com uma entrada count).
Por que o cadastro retorna 142 username needs at least 3 characters?
Porque um hook beforeSave na classe _User lançou Parse.Error.VALIDATION_ERROR com essa frase, e o backend repassa a mensagem do hook sem alterar. 142 sempre significa o seu próprio Cloud Code disse não; o texto é o que quem escreveu o hook digitou. No backend user-auth-starter, o hook tem 17 linhas:
// Stack: Node.js 22.x | Parse Server 7.x | File: cloud/main.js (beforeSave on _User)
Parse.Cloud.beforeSave(Parse.User, (request) => {
const user = request.object;
const email = (user.get("email") ?? "").trim().toLowerCase();
if (!email) throw new Parse.Error(Parse.Error.VALIDATION_ERROR, "an e-mail address is required.");
user.set("email", email);
const username = (user.get("username") ?? "").trim();
if (username.length < 3) throw new Parse.Error(Parse.Error.VALIDATION_ERROR, "username needs at least 3 characters.");
user.set("username", username);
});
# Stack: bash + curl | File: reproduce.sh (row 13)
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/users" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "Content-Type: application/json" -d '{"username":"ab","email":"[email protected]","password":"Passw0rd!"}'
{"code":142,"error":"username needs at least 3 characters."}
HTTP 400
A correção está na requisição, e a mensagem pode ser mostrada ao usuário porque foi você quem a escreveu. O caso para ficar de olho é um 142 com uma frase que você não reconhece: procure VALIDATION_ERROR no seu Cloud Code e, se não estiver lá, olhe o beforeSave de cada classe que a requisição toca, incluindo as que um pointer arrasta junto.
Se um cadastro como ab responde 201 em vez disso, o hook não está no ar. Num backend recém-criado, o primeiro Deploy no editor de Cloud Code não envia nada; faça o deploy de novo e teste outra vez. Os detalhes estão em Como adicionar autenticação a um app web ou Flutter sem escrever um servidor de auth.
Por que o cadastro retorna 202 ou 203 Account already exists?
Porque o username (202) ou o e-mail (203) já está em outra conta, e os dois são únicos em toda a classe _User. Os dois são verificados nessa ordem: uma requisição que repete tanto o username quanto o e-mail recebe 202. Cadastramos um usuário de sonda e depois cadastramos mais duas vezes:
# Stack: bash + curl | File: reproduce.sh (rows 11 and 12)
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/users" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "Content-Type: application/json" -d '{"username":"ana","email":"[email protected]","password":"Passw0rd!"}'
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/users" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "Content-Type: application/json" -d '{"username":"other","email":"[email protected]","password":"Passw0rd!"}'
{"code":202,"error":"Account already exists for this username."}
HTTP 400
{"code":203,"error":"Account already exists for this email address."}
HTTP 400
A correção é um formulário que reage: no 202, ofereça o login com o username preenchido; no 203, ofereça a redefinição de senha, já que a pessoa provavelmente tem uma conta com outro username. As duas mensagens podem ser mostradas.
Repare que o hook acima passa o e-mail para minúsculas antes da verificação, então [email protected] e [email protected] colidem como 203; sem esse hook, seriam duas contas.
Por que a API retorna 209 Invalid session token logo depois do logout?
Porque POST /logout apagou aquela sessão no servidor, e o cliente guardou o token. Criamos uma conta (token A), fizemos logout com A e pedimos GET /users/me com A: 400 / 209. O mesmo status, código e mensagem voltaram para um token inventado e para nenhum token:
# Stack: bash + curl | File: reproduce.sh (rows 20, 21 and 24)
curl -s -w '\nHTTP %{http_code}\n' "$BASE/users/me" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "X-Parse-Session-Token: r:0000000000000000000000000000000f"
{"code":209,"error":"Invalid session token"}
HTTP 400
Uma Cloud Function pode lançar o mesmo código de propósito: whoami no backend user-auth-starter e moderatorList no lockdown-lab respondem 400 / 209 log in first. quando request.user está vazio.
A mensagem muda, o código não, então o handler do cliente é um branch só: limpe o token guardado, mostre o formulário de login e não tente de novo. Um token revogado continua revogado.
Logout revoga um token, não o usuário. Se a mesma pessoa está logada num celular e num notebook, o token do notebook continua funcionando depois que o celular sai; isso foi medido no post de autenticação e continua valendo. Se “sair de todos os dispositivos” é um recurso, ele é uma Cloud Function que apaga as linhas _Session do usuário com a master key.
Por que a master key recebe 206 Session missing de um hook beforeSave?
Porque a master key ignora as permissões por classe e as ACLs, não o Cloud Code. O hook de Note no lockdown-lab precisa de um usuário para carimbar como dono; um create com a master key e sem session token chegou a ele com request.user indefinido, e o hook lançou SESSION_MISSING:
// Stack: Node.js 22.x | Parse Server 7.x | File: cloud/main.js (beforeSave on Note)
const owner = request.user ?? note.get("owner");
if (!owner) throw new Parse.Error(Parse.Error.SESSION_MISSING, "log in to create a note.");
# Stack: bash + curl (master key: server side only) | File: reproduce.sh (data row 17)
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/classes/Note" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-Master-Key: $MASTER_KEY" \
-H "Content-Type: application/json" -d '{"text":"no owner"}'
{"code":206,"error":"log in to create a note."}
HTTP 400
Um cliente anônimo nunca vê essa linha: a permissão da classe o barra com 101 antes de o hook rodar. Então um 206 nos seus logs é quase sempre um job do lado do servidor, um script de migração ou uma importação pela dashboard que escreve com a master key e esquece que o hook espera um usuário.
A correção é passar o dono explicitamente (o hook cai para note.get("owner")), ou deixar o hook pular a verificação quando request.master é true, se é isso que você quer.
Por que uma Cloud Function retorna 141, e o que vem na mensagem?
Porque 141 é o código de o Cloud Code não produziu resultado, e há duas razões bem diferentes. A primeira é um nome de função sem deploy; a mensagem diz qual. A segunda é qualquer erro lançado dentro de um hook ou função que não seja um Parse.Error; o backend o embrulha em 141 e repassa a mensagem da própria exceção. Esbarramos nas duas:
# Stack: bash + curl | File: reproduce.sh (row 25; data row 18 uses the master key)
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/functions/doesNotExist" \
-H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-REST-API-Key: $REST_KEY" \
-H "Content-Type: application/json" -d '{}'
{"code":141,"error":"Invalid function: \"doesNotExist\""}
HTTP 400
Para a segunda razão, enviamos ao hook de Note um owner que é um objeto simples em vez de um pointer de usuário, com a master key. new Parse.ACL({"foo":"bar"}) lança um TypeError, e isto foi o que o cliente viu:
{"code":141,"error":"Tried to create an ACL with an invalid permission type."}
HTTP 400
A mensagem é o texto da exceção JavaScript, não Script failed. Isso é útil para você e perigoso para os seus usuários: a mensagem de um TypeError pode vazar nomes de variáveis e formatos de objetos.
A correção do primeiro caso é um deploy (e um segundo num backend recém-criado). A do segundo é um try/catch no hook que relança um Parse.Error com uma mensagem escolhida por você, e uma olhada na página Logs, onde está o stack trace.
Não conseguimos fazer um hook lançar um Error simples nesses backends sem fazer o deploy de Cloud Code novo, o que exige a dashboard, então o TypeError acima é o único 141 vindo de código que medimos; o embrulho é o mesmo para qualquer erro não-Parse.
Por que uma senha errada é 404 mas uma chave errada é 403?
Porque o status HTTP é decidido por duas camadas diferentes. Todo código numérico que reproduzimos chegou como HTTP 400, exceto o 101, que chega como 404 porque o backend mapeia object not found para Not Found e o erro de login reaproveita esse código. Antes de tudo isso, um gateway verifica os headers e o corpo, e as quatro respostas dele não trazem código nenhum:
# Stack: bash + curl | File: reproduce.sh (rows 1 to 6)
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/_User" # no App ID
curl -s -w '\nHTTP %{http_code}\n' "$BASE/classes/_User" -H "X-Parse-Application-Id: $APP_ID" # no key
curl -s -w '\nHTTP %{http_code}\n' -X POST "$BASE/login" -H "X-Parse-Application-Id: $APP_ID" \
-H "X-Parse-REST-API-Key: $REST_KEY" -H "Content-Type: application/json" -d '{"username": "ana", '
{"error":"unauthorized"}
HTTP 401
{"error":"unauthorized"}
HTTP 403
{"error":"Expected double-quoted property name in JSON at position 20"}
HTTP 400
| O que você enviou | HTTP | Corpo |
|---|---|---|
sem X-Parse-Application-Id, ou um App ID que não nomeia app nenhum | 401 | {"error":"unauthorized"} |
| App ID certo, chave errada ou nenhuma chave | 403 | {"error":"unauthorized"} |
| um corpo que não é JSON válido | 400 | {"error":"Expected double-quoted property name in JSON at position 20"} |
um caminho que não existe (GET /nothing-here) | 404 | {"message":"Not Found","error":{}} |
| senha errada, linha inexistente, linha oculta, sem sessão (101) | 404 | {"code":101,"error":"…"} |
| todos os outros códigos numéricos (119, 141, 142, 202, 203, 206, 209, …) | 400 | {"code":N,"error":"…"} |
Então a ordem do diagnóstico é: sem code no corpo, a requisição nunca chegou às regras do seu app, e 401 versus 403 diz qual header está errado; um 404 com código 101 é uma de três situações que a mensagem separa; qualquer outra coisa é um 400 cujo código é a história inteira.
A tabela oficial de erros lista 107 para JSON malformado; o backend ao vivo respondeu sem código.
Decida primeiro pela presença de code, depois pelo valor dele. O status HTTP só diz qual camada respondeu.
Quais códigos menores as sondas capturaram: 105, 107, 111, 125, 200, 201 e 204?
Mais sete, todos HTTP 400, todos sobre o corpo da requisição, e um deles com efeito colateral. Cada linha abaixo é três rodadas em três:
| Código | O que enviamos | error |
|---|---|---|
| 105 | POST /classes/ErrProbe com o campo "bad key" | Invalid field name: bad key. |
| 107 | POST /classes/bad-name (um hífen no nome da classe) | schema class name does not revalidate |
| 111 | POST /classes/Note com "text": 123 numa coluna String | schema mismatch for Note.text; expected String but got Number |
| 125 | POST /users com "email": "not-an-email" | Email address format is invalid. |
| 200 | POST /users sem username | bad or missing username |
| 200 | POST /login com o corpo vazio | username/email is required. |
| 201 | POST /users sem password | password is required |
| 204 | POST /requestPasswordReset sem email | you must provide an email |
O efeito colateral está no 105. O create foi recusado, e a classe ErrProbe apareceu no schema mesmo assim, vazia, com as permissões padrão. Apagamos, enviamos a requisição de novo, e ela apareceu de novo.
Se o seu schema tem classes que você não lembra de ter criado, uma escrita recusada de um cliente pode tê-las criado; reproduce.sh apaga a dele com DELETE /schemas/ErrProbe depois de cada rodada.
O 107 é a outra surpresa: a documentação lista 103 para nome de classe inválido, e o backend ao vivo respondeu 107 no POST e, no GET /classes/bad-name, um 200 simples com {"results":[]}. Um erro de digitação no nome da classe numa leitura não falha; retorna nada.
Quais códigos não conseguimos reproduzir?
Dois, e eles não estão na tabela porque não os vimos. 103 (nome de classe inválido): um GET em bad-name respondeu 200 com resultado vazio e um POST respondeu 107, três rodadas em três.
205 (nenhum usuário com aquele e-mail): POST /requestPasswordReset com um endereço que ninguém tem respondeu 200 {}, que é o comportamento certo para um formulário de redefinição e significa que seu cliente nunca verá 205 daquele endpoint.
Também não tentamos provocar o código 1 (erro interno do servidor) nem os códigos de rate limit e timeout; eles exigem condições que um script de sonda não deveria criar num backend compartilhado.
O que dá errado quando você roda reproduce.sh contra o seu próprio backend?
users ana/bob missing: run ./seed.sh first o grupo data faz login como ana e bob e para se qualquer um dos logins falhar. Rode ./seed.sh com o mesmo .env; ele cria os dois usuários, a role moderator e as classes Note e Vault, e pode ser rodado de novo sem problema. Se você mudou as senhas, exporte ANA_PASS e BOB_PASS.
401 unauthorized em toda linha o APP_ID do .env está errado ou tem um caractere sobrando no fim. Copie de novo em App Settings → Security & Keys. A linha 2 do script envia um App ID deliberadamente errado; se as linhas 3 a 29 parecem com a linha 2, o problema é o seu .env.
403 unauthorized em toda linha o App ID está certo e a JS_KEY não. Mesma página, a entrada JavaScript key. Qualquer chave de cliente serve (KEY_HEADER=X-Parse-Client-Key KEY_VALUE=… ./reproduce.sh deu as mesmas linhas); a master key só é usada onde o rótulo diz.
142 responde 201 o hook de _User não está no ar. Num backend recém-criado, o primeiro Deploy no editor de Cloud Code não envia nada: edite main.js, faça o deploy de novo e rode outra vez. O cadastro criou um usuário ab; apague em Database → _User ou com a master key.
141 Invalid function: "whoami" na linha 22, mesma causa: cloud/main.js não teve deploy, ou teve só um. As duas funções e os dois hooks moram nesse único arquivo.
206 responde 201 o hook de Note não está no ar, então o create com a master key teve sucesso e deixou uma nota sem dono e com ACL pública. Faça o deploy e depois apague a linha (migrate-acl.sh no repositório lockdown-lab remove notas sem dono).
101 responde 200 no grupo data a permissão por classe de Note ainda não chegou a todos os servidores. Já medimos até 22 minutos para uma mudança de permissão assentar; rode de novo até três rodadas concordarem, e faça um deploy de Cloud Code se travar.
python3: command not found o script lê os corpos JSON com Python 3. Instale, ou troque as duas chamadas a python3 por jq -r.
Quando decidir pelo código numérico é a jogada errada?
Quando quem chama não é o seu próprio cliente. Se você coloca uma API sua na frente do backend (um serviço Express ou FastAPI num container, digamos), os consumidores dela nunca deveriam ver 101 ou 209.
Mapeie na fronteira: 101 no login vira o seu 401, 209 vira o seu 401 com uma dica de reautenticar, 119 vira 403, 142 vira 422 com a mensagem, todo o resto 500 com o código só nos seus logs.
Quando a mensagem não foi escrita por você. As frases por trás de 142, 206, 119 vindo de uma função e 141 vindo de um throw são suas e podem ir para a tela. schema mismatch for Note.text; expected String but got Number e Tried to create an ACL with an invalid permission type. são para desenvolvedores; mostre uma mensagem genérica e registre a original no log.
Quando o código esconde a intenção de propósito. O 101 não vai dizer se uma linha existe, e o login não vai dizer se o username existe. Um cliente que tenta inferir qualquer um dos dois pelo tempo de resposta ou pelo texto da mensagem está chutando, e uma mudança no backend quebra o chute. Se o produto precisa da distinção, coloque-a numa Cloud Function para quem pode saber.
E quando o status HTTP é o único sinal. 401, 403, JSON malformado e caminhos desconhecidos não trazem código; um handler que começa por body.code quebra neles. Verifique o status primeiro, depois a presença de code, depois o valor.
O que você deveria rodar contra o seu próprio backend em seguida?
./reproduce.sh authcom o seu próprio.env. Compare as 29 linhas com a tabela deste post; uma linha diferente é ou um hook que você tem (bom) ou um que você achava que tinha (ainda sem deploy).- Uma linha por hook que você escrever. Todo
throw new Parse.Error(...)no seu Cloud Code merece uma linha no script, para que a mensagem que seus usuários vão ver seja uma que você já leu. - Um handler para o 209. Limpe o token, mostre o formulário de login, nunca tente de novo. É o código que você mais vai ver em produção, e o de correção mais simples.
- Trave, depois sonde. Se você mudar uma permissão por classe, rode o grupo
dataaté três rodadas concordarem; a porta fecha em todos os servidores em algum momento, não de uma vez. A trava deNotee os 22 minutos estão em Seu frontend fala direto com o banco de dados. Veja como proteger o backend. - Contas primeiro. As linhas 101, 202, 203 e 209 vêm das seis chamadas REST de Como adicionar autenticação a um app web ou Flutter sem escrever um servidor de auth; se o seu app ainda não tem classe de usuários, esse post é o passo anterior.