A API do NueForm usa códigos de status HTTP convencionais e retorna respostas de erro em JSON estruturado. Este guia cobre o formato de resposta, todos os códigos de erro possíveis e padrões para tratar erros no seu código.
Formatos de resposta
Respostas de sucesso
Chamadas de API bem-sucedidas retornam um objeto JSON com um campo data. Endpoints de listagem também incluem um campo meta com informações de paginação.
{
"data": {
"id": "clx1abc2d3e4f5g6h7i8j9k0",
"title": "Customer Feedback",
"status": "published",
"created_at": "2026-01-15T09:30:00.000Z"
}
}
Com metadados de paginação:
{
"data": [
{
"id": "clx1abc2d3e4f5g6h7i8j9k0",
"title": "Customer Feedback"
},
{
"id": "clx2def3g4h5i6j7k8l9m0n1",
"title": "Employee Survey"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 150,
"total_pages": 8
}
}
Respostas de erro
Respostas de erro retornam um objeto JSON com um campo error contendo o code do erro, uma message legível por humanos e o código de status HTTP.
{
"error": {
"code": "NOT_FOUND",
"message": "The requested form could not be found.",
"status": 404
}
}
Códigos de erro
A tabela a seguir lista todos os códigos de erro retornados pela API:
| Código | Status HTTP | Descrição |
|---|---|---|
BAD_REQUEST | 400 | A requisição está malformada, faltam campos obrigatórios ou contém valores inválidos. |
UNAUTHORIZED | 401 | Falha na autenticação. A chave de API está ausente, malformada, revogada ou expirada. |
FORBIDDEN | 403 | O usuário autenticado não tem permissão para executar esta ação. |
NOT_FOUND | 404 | O recurso solicitado não existe ou não está acessível ao usuário autenticado. |
CONFLICT | 409 | A requisição conflita com o estado atual do recurso (por exemplo, entrada duplicada ou um formulário que ainda não pode ser publicado). |
RATE_LIMITED | 429 | Requisições em excesso. Veja Limites de taxa para detalhes. |
INTERNAL_ERROR | 500 | Ocorreu um erro inesperado no servidor. |
Exemplos de respostas de erro
400 Bad Request
Retornado quando o corpo da requisição é inválido ou faltam campos obrigatórios.
{
"error": {
"code": "BAD_REQUEST",
"message": "Field 'title' is required.",
"status": 400
}
}
401 Unauthorized
Retornado quando a autenticação falha por qualquer motivo.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired API key.",
"status": 401
}
}
403 Forbidden
Retornado quando o usuário está autenticado mas não tem permissão. Causas comuns incluem acessar recursos de outro usuário ou usar um recurso não disponível no plano atual.
{
"error": {
"code": "FORBIDDEN",
"message": "API access is not available on your current plan.",
"status": 403
}
}
404 Not Found
Retornado quando o recurso solicitado não existe.
{
"error": {
"code": "NOT_FOUND",
"message": "The requested form could not be found.",
"status": 404
}
}
409 Conflict
Retornado quando a requisição criaria uma duplicata ou conflita com dados existentes.
{
"error": {
"code": "CONFLICT",
"message": "A form with this slug already exists.",
"status": 409
}
}
409 Conflict — publicação bloqueada
Retornado por POST /api/v1/forms/:id/publish (e por PUT /api/v1/forms/:id com published: true) quando o formulário ainda não pode ir ao ar. Nada é publicado. reason diz o motivo, e question_ids lista as perguntas a corrigir. Quando upgrade_required é true, o plano do dono do formulário precisa de upgrade.
{
"error": {
"code": "CONFLICT",
"message": "E-signature contracts need a Pro plan to go live. Upgrade to publish this form, or remove the contract question.",
"status": 409,
"reason": "contract_requires_pro",
"question_ids": ["66a1b2c3d4e5f6a7b8c9d001"],
"upgrade_required": true
}
}
reason | O que corrigir |
|---|---|
contract_requires_pro, data_node_requires_pro, booking_requires_pro, payment_connect_requires_pro, webhook_requires_pro | O plano do dono do formulário (o do proprietário da equipe, em formulários de equipe) não inclui esse recurso. Faça upgrade ou remova o recurso: exclua a pergunta, limpe webhook_url ou mude a pergunta de pagamento para a opção padrão. |
booking_* | Uma pergunta de agendamento ainda não pode receber agendamentos: conecte ou reconecte a agenda dela, ou adicione os horários, o fuso horário, as opções de duração ou o número das ligações de confirmação. |
email_node_* | Falta a uma etapa de e-mail o destinatário, o nome do remetente, o assunto ou o corpo, ou ela envia para {respondent.email} em um formulário que não exige login. |
fuse_* | Uma pergunta Fuse não tem formulário Fuse, o formulário Fuse dela não existe ou não está publicado, ou a sessão fixada foi arquivada. |
composite_* | Um composite usado pelo formulário não está publicado, foi excluído ou está vazio; composite_ids lista quais são. |
Se você criar um formulário com published: true e a publicação for bloqueada, o formulário é criado mesmo assim como rascunho: a resposta é 201 Created com um objeto publish_error (reason, message, question_ids, upgrade_required), então uma nova tentativa nunca cria uma duplicata. Quando PUT /api/v1/forms/:id retorna esse erro, suas alterações foram salvas (saved_as_draft: true) e só a publicação falhou.
429 Rate Limited
Retornado quando o limite de taxa foi excedido. Inclui um cabeçalho Retry-After.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Please try again later.",
"status": 429
}
}
500 Internal Error
Retornado quando ocorre um erro inesperado no servidor. Se isso persistir, entre em contato com o suporte.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred.",
"status": 500
}
}
Erros da API de voz
A API de voz acrescenta estes códigos. Nada que termine em erro é cobrado, e os campos extras ficam ao lado de code dentro de error.
| Código | Status HTTP | Descrição |
|---|---|---|
INSUFFICIENT_CREDITS | 402 | O seu saldo não cobre a requisição. credits_required, credits_available e top_up_url dizem quanto falta e onde conseguir mais. |
TEXT_TOO_LONG | 413 | Mais caracteres falados do que uma requisição aceita (max_characters). Divida o texto em várias requisições. |
CONTENT_BLOCKED | 422 | O texto não pode ser falado segundo a nossa política de uso (categories). |
VOICE_UNAVAILABLE | 422 | A voz não existe ou você não pode usá-la (voice). |
LANGUAGE_NOT_SUPPORTED_BY_VOICE | 422 | A voz não fala esse idioma (voice, language, supported_languages). |
LINE_LANGUAGE_NOT_SUPPORTED_BY_VOICE | 422 | Algumas linhas estão em um idioma que a própria voz delas não fala. mismatches lista cada uma (speaker, voice, voice_name, language, line_count), com até quatro suggestions de vozes que falam esse idioma. Quando native_voices é false, nenhuma voz fala exatamente esse idioma: as sugestões falam um idioma próximo e podem não soar bem. |
INVALID_SCRIPT | 422 | O roteiro não pode ser falado do jeito que foi enviado (reason, por exemplo speaker_not_cast). |
CONCURRENCY_LIMIT | 429 | Gerações demais em andamento ao mesmo tempo na sua conta. Tente de novo depois de Retry-After. |
CAPACITY | 503 | Sem capacidade no momento. Tente de novo depois de Retry-After. |
WARMING_UP | 503 | O mecanismo de voz está iniciando. Tente de novo depois de Retry-After. |
MODERATION_UNAVAILABLE | 503 | Não foi possível verificar o texto agora. Tente de novo depois de Retry-After. |
AI_UNAVAILABLE | 503 | auto_convert ou auto_cast não pôde ser executado agora. Tente de novo depois de Retry-After. |
DELETE_INCOMPLETE | 503 | Uma exclusão parou no meio. Envie de novo para concluir. |
FORBIDDEN (403) e CONFLICT (409) dizem o motivo em reason: por exemplo key_limit_reached, account_gate ou scope_not_allowed, e quote_exceeds_max_credits, idempotency_conflict ou request_in_progress.
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "This request needs 30 credits; 12 are available. Nothing was charged.",
"status": 402,
"credits_required": 30,
"credits_available": 12,
"top_up_url": "https://nueform.io/wallet?tab=buy"
}
}
Tratando erros no código
JavaScript
Construa um cliente de API reutilizável que verifica erros e lança exceções tipadas:
class NueFormApiError extends Error {
constructor(code, message, status) {
super(message);
this.name = "NueFormApiError";
this.code = code;
this.status = status;
}
}
async function nueformRequest(path, options = {}) {
const response = await fetch(`https://app.nueform.io/api/v1${path}`, {
...options,
headers: {
"Authorization": `Bearer ${process.env.NUEFORM_API_KEY}`,
"Content-Type": "application/json",
...options.headers,
},
});
const body = await response.json();
if (!response.ok) {
const { code, message, status } = body.error;
throw new NueFormApiError(code, message, status);
}
return body;
}
// Usage
async function getForm(formId) {
try {
const { data } = await nueformRequest(`/forms/${formId}`);
return data;
} catch (error) {
if (error instanceof NueFormApiError) {
switch (error.code) {
case "NOT_FOUND":
console.error(`Form ${formId} does not exist.`);
return null;
case "UNAUTHORIZED":
console.error("Check your API key configuration.");
break;
case "RATE_LIMITED":
console.warn("Rate limited. Implement backoff and retry.");
break;
default:
console.error(`API error [${error.code}]: ${error.message}`);
}
}
throw error;
}
}
Python
import os
import requests
class NueFormApiError(Exception):
def __init__(self, code, message, status):
super().__init__(message)
self.code = code
self.status = status
class NueFormClient:
def __init__(self, api_key=None):
self.api_key = api_key or os.environ["NUEFORM_API_KEY"]
self.base_url = "https://app.nueform.io/api/v1"
def request(self, method, path, **kwargs):
url = f"{self.base_url}{path}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
response = requests.request(method, url, headers=headers, **kwargs)
body = response.json()
if not response.ok:
error = body["error"]
raise NueFormApiError(
error["code"], error["message"], error["status"]
)
return body
# Usage
client = NueFormClient()
try:
result = client.request("GET", f"/forms/{form_id}")
form = result["data"]
print(f"Form: {form['title']}")
except NueFormApiError as e:
if e.code == "NOT_FOUND":
print(f"Form {form_id} does not exist.")
elif e.code == "UNAUTHORIZED":
print("Check your API key configuration.")
elif e.code == "RATE_LIMITED":
print("Rate limited. Implement backoff and retry.")
else:
print(f"API error [{e.code}]: {e}")
Boas práticas
Sempre verifique o campo error
Nunca presuma que uma resposta foi bem-sucedida apenas pela presença de um corpo. Sempre verifique o campo error ou o código de status HTTP antes de processar a resposta.
Use códigos de erro específicos para controle de fluxo
Faça a correspondência pelo error.code (por exemplo, NOT_FOUND, RATE_LIMITED) em vez do código de status HTTP. Códigos de erro são estáveis, descritivos e mais fáceis de usar na lógica da aplicação.
Registre os detalhes dos erros
Inclua a resposta de erro completa nos seus logs para depuração. Juntos, code, message e status fornecem contexto suficiente para diagnosticar a maioria dos problemas sem reproduzir a requisição.
Trate erros 5xx com novas tentativas
Erros de servidor (500 Internal Error) geralmente são transitórios. Implemente lógica de novas tentativas com backoff exponencial para essas respostas. Limite as tentativas a 2--3 antes de falhar.
Não exponha mensagens de erro aos usuários finais
As mensagens de erro da API são projetadas para desenvolvedores, não para usuários finais. Mapeie os erros da API para mensagens amigáveis na sua aplicação.
Se você receber respostas 500 Internal Error persistentes, entre em contato com o suporte com os detalhes da requisição e os horários. Erros de servidor são registrados do nosso lado e investigados.