Respostas e erros
Entenda os dois formatos de resposta de sucesso da API e aprenda a tratar cada erro (400, 401, 404, 429) lendo as mensagens em português do backend.
Respostas e erros
Como a API do GS Engage responde quando dá certo — e o que fazer, passo a passo, quando dá errado.
Toda vez que você chama a API, ela devolve duas coisas: um código de status (um número que resume o que aconteceu) e um corpo (os dados ou a explicação do problema). Saber ler esses dois é o que separa uma integração que "às vezes falha misteriosamente" de uma que se recupera sozinha.
Esta página responde três perguntas práticas: como vem uma resposta de sucesso, como vem um erro, e o que fazer diante de cada código. As mensagens de erro do GS Engage já chegam em português — a ideia é você reaproveitá-las direto para quem for corrigir.
A regra de ouro para tratar qualquer erro
Sempre siga o mesmo raciocínio: o que houve → por quê → como corrigir. O código de status responde "o que houve", o corpo (error.message e, no 400, error.errors) responde "por quê", e este guia te dá o "como corrigir".
Aqui está a pegadinha que mais confunde quem começa: nem toda listagem responde no mesmo formato. Existem dois, e você precisa saber qual esperar de cada endpoint, senão seu código procura data onde só tem uma lista solta.
Formato 1 — paginado: { data, meta }
É o formato da maioria das listagens. Os itens vêm dentro de data, e um bloco meta te conta quantos existem no total e quantas páginas há. Você usa isso para saber quando parar de pedir.
{
"data": [
{ "id": "507f1f77bcf86cd799439011", "name": "Maria Silva" },
{ "id": "507f1f77bcf86cd799439012", "name": "João Pereira" }
],
"meta": {
"limit": 20,
"count": 20,
"total": 137,
"page": 1,
"totalPages": 7
}
}O que isso significa para você: há 137 leads no total (total), distribuídos em 7 páginas (totalPages). Você está na página 1 e recebeu 20 itens. Peça a página 2 com ?page=2 e siga até chegar em totalPages.
Respondem neste formato paginado, por exemplo: GET /api/v1/leads, GET /api/v1/routines, GET /api/v1/prospections, GET /api/v1/conversations/threads e as demais listagens.
Formato 2 — data sem meta
Um endpoint foge à paginação: GET /api/v1/webhooks devolve a lista em data, mas sem o bloco meta (retorna todos os webhooks do projeto de uma vez, sem paginar).
{
"data": [
{ "id": "507f1f77bcf86cd799439021", "name": "Webhook CRM", "url": "https://..." }
]
}custom-fields NÃO é exceção
GET /api/v1/custom-fields é paginado normal ({ data, meta }), como a maioria. O único que vem sem meta é o GET /api/v1/webhooks. Em todos os casos os itens ficam em data — nunca numa lista solta na raiz. Se o seu código lê a resposta como um array direto, ele quebra.
// Paginado: itens em .data, com .meta
const leadsRes = await fetch(`${BASE}/leads?apiKey=${apiKey}`);
const { data: leads, meta } = await leadsRes.json();
console.log(`${leads.length} de ${meta.total} leads`);
// Webhooks: itens em .data, mas SEM .meta (sem paginação)
const whRes = await fetch(`${BASE}/webhooks?apiKey=${apiKey}`);
const { data: webhooks } = await whRes.json();
console.log(`${webhooks.length} webhooks`);# Paginado: itens em ["data"], com ["meta"]
leads_res = requests.get(f"{BASE}/leads", params={"apiKey": api_key})
body = leads_res.json()
leads, meta = body["data"], body["meta"]
print(f"{len(leads)} de {meta['total']} leads")
# Webhooks: itens em ["data"], mas SEM ["meta"]
wh_res = requests.get(f"{BASE}/webhooks", params={"apiKey": api_key})
webhooks = wh_res.json()["data"]
print(f"{len(webhooks)} webhooks")Quando algo dá errado, o código de status não é 200. O corpo muda de cara: em vez de dados, você recebe uma explicação. O caso mais rico é o 400 (Bad Request), que detalha campo por campo o que precisa ser ajustado.
O envelope de erro
Todo erro da API vem embrulhado num objeto error — nunca na raiz do corpo. Dentro dele há sempre um message (o resumo, em português) e, no caso do 400, uma lista errors detalhando campo por campo.
{
"error": {
"message": "Não foi possível criar o lead.",
"errors": [
{ "field": ["emails"], "message": "É obrigatório informar ao menos um contato." },
{ "field": ["customFields", "orcamento"], "message": "Campo personalizado não encontrado neste projeto." }
]
}
}Entre em error primeiro
O message e o errors ficam dentro de error — ou seja, resposta.error.message, não resposta.message. Se o seu código lê resposta.message direto, vai achar undefined.
Cada item de errors tem duas partes:
O caminho do campo com problema, na forma de lista (ex.: ["emails"], ["content"], ["customFields", "orcamento"]). Junte com ponto para exibir: emails, customFields.orcamento. É por ele que você sabe onde corrigir.
A explicação do problema, já em português, pronta para mostrar a quem for arrumar. É o "por quê" do erro.
As mensagens já vêm em português — reaproveite
Você não precisa traduzir nem reescrever nada. O message de cada erro chega em pt-BR direto do GS Engage. Mostre-o como está no seu log, na sua tela ou no seu ticket.
Estes são os quatro códigos de erro que você vai encontrar na prática. Para cada um: o que ele significa, a causa mais comum e o que fazer.
| Status | O que houve | Por que costuma acontecer | Como corrigir |
|---|---|---|---|
| 400 Bad Request | O pedido chegou, mas os dados estão inválidos. | Falta um contato no lead, uma nota passou de 1000 caracteres, um customField não existe, um enum recebeu valor fora da lista. | Leia a lista errors: cada field diz o campo e cada message (em pt-BR) diz o ajuste. Corrija e reenvie. |
| 401 Unauthorized | A API não reconheceu você. | A apiKey está ausente, errada ou foi revogada. | Confira o ?apiKey= na URL. Gere/copie a chave em Configurações > Configurações de API e teste com um GET /api/v1/custom-fields. |
| 404 Not Found | O recurso pedido não existe. | O id na URL está errado, aponta para um item de outro projeto, ou o item foi removido (ex.: leadId, prospectionId, webhookId inexistente). | Verifique o identificador. Liste o recurso antes (ex.: GET /api/v1/leads) para pegar um id válido. |
| 429 Too Many Requests | Você passou do limite de requisições por minuto. | Muitas chamadas na janela de 60s: 200/min de leitura ou 100/min de escrita. | Espere os segundos do header Retry-After e tente de novo, com backoff exponencial. Veja abaixo. |
401 e o alerta de segurança da apiKey
Um 401 costuma vir de uma chave errada — mas cuidado ao investigar: como a apiKey viaja na URL, ela aparece em logs, no histórico do navegador e em links copiados. Nunca cole a URL completa (com a chave) em chats ou tickets para "pedir ajuda". Trate a chave como senha.
O caso do 429: respeite o Retry-After
O 429 é o único erro que não é culpa dos seus dados — é só ritmo. O corpo segue o mesmo envelope ({ "error": { "message": "Limite de requisições excedido..." } }), mas o que importa aqui são os cabeçalhos HTTP que acompanham a resposta:
Retry-After: 12
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000000O que fazer: espere os segundos indicados em Retry-After (aqui, 12s) antes de tentar de novo. Se falhar outra vez, dobre a espera — isso é backoff exponencial (espera crescente a cada tentativa). Nunca fique martelando a API em loop apertado; você só prolonga o bloqueio.
Vamos juntar tudo. O gatilho abaixo é o mais comum: tentar criar um lead sem nenhum contato. A API recusa com 400 e explica exatamente o que faltou. O código lê a lista errors e mostra cada problema já em português.
const res = await fetch(`${BASE}/leads?apiKey=${apiKey}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Lead sem contato" }), // falta emails/phones/mobiles
});
if (!res.ok) {
const { error } = await res.json(); // tudo vem dentro de "error"
if (res.status === 400) {
console.error(`O que houve: ${error.message}`);
// Percorre cada campo com problema — a message já vem em pt-BR.
// field é uma lista (caminho) — junte com ponto para exibir.
for (const e of error.errors ?? []) {
console.error(` • ${e.field.join(".")}: ${e.message}`);
}
} else if (res.status === 401) {
console.error("apiKey ausente ou inválida — confira sua chave.");
} else if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") || 5);
console.warn(`Limite atingido — aguarde ${wait}s e tente de novo.`);
}
}res = requests.post(
f"{BASE}/leads",
params={"apiKey": api_key},
json={"name": "Lead sem contato"}, # falta emails/phones/mobiles
)
if not res.ok:
error = res.json().get("error", {}) # tudo vem dentro de "error"
if res.status_code == 400:
print(f"O que houve: {error.get('message')}")
# Percorre cada campo com problema — a message já vem em pt-BR.
# field é uma lista (caminho) — junte com ponto para exibir.
for e in error.get("errors", []):
print(f" • {'.'.join(map(str, e['field']))}: {e['message']}")
elif res.status_code == 401:
print("apiKey ausente ou inválida — confira sua chave.")
elif res.status_code == 429:
wait = int(res.headers.get("Retry-After", "5"))
print(f"Limite atingido — aguarde {wait}s e tente de novo.")Rodando o exemplo acima, a saída seria algo assim:
O que houve: Não foi possível criar o lead.
• emails: É obrigatório informar ao menos um contato.O que isso significa para o negócio: você sabe na hora que o lead não entrou porque veio sem forma de contato — e a mensagem em português já pode ir direto para quem preencheu a origem dos dados.
- Cheque o código de status antes de tratar o corpo — ele te diz qual caminho seguir.
- Percorra a lista
errorsdo400e mostre cadafield+message. - Reaproveite as mensagens em pt-BR do backend como estão.
- Respeite o
Retry-Afterno429, com backoff exponencial.
- Não assuma que toda listagem tem
meta—GET /api/v1/webhookstrazdatasemmeta. Mas os itens estão sempre emdata. - Não trate todo erro como "deu ruim" genérico — cada status pede uma ação diferente.
- Não repita a chamada em loop apertado após um
429— você prolonga o bloqueio.
Alguma listagem vem sem meta?
Sim, uma: GET /api/v1/webhooks retorna data sem meta (lista completa, sem paginação). Todas as outras — inclusive custom-fields — usam o formato paginado { data, meta }. Em todos os casos os itens ficam em data.
O 400 sempre traz a lista errors?
Sim — dentro de error. O corpo de um 400 sempre tem error.message (resumo) e error.errors (lista onde field é o caminho do campo, como lista, e message é a explicação). É de lá que você tira exatamente o que corrigir.
Recebi 401 mas tenho certeza de que a chave está certa. O que pode ser?
Confira se a chave está mesmo na URL como ?apiKey=SUA_CHAVE (é query param, não header), se não há espaços ou quebras coladas junto, e se a chave não foi revogada. Teste isolado com GET /api/v1/custom-fields: um 200 confirma que a chave é válida.
Qual a diferença entre 404 e 400?
O 404 diz que o recurso não existe (o id da URL aponta para o nada). O 400 diz que o recurso até faz sentido, mas os dados enviados estão inválidos. Um é "não achei isso"; o outro é "o que você mandou está errado".
As mensagens de erro vêm em inglês?
Não. O GS Engage devolve as mensagens de erro já em português. Você pode exibi-las direto para a pessoa que vai corrigir, sem traduzir.
Este artigo foi útil?
Paginação, filtros e ordenação
Percorra listas grandes do GS Engage página por página, filtre por status ou cadência e ordene os resultados — do jeito certo para relatórios de BI.
Glossário do GS Engage
Dicionário simples de todos os termos técnicos da API do GS Engage, do jargão de rede (endpoint, webhook) aos rótulos que você vê no app (Cadência, Levantada de Mão).