Pular para o conteúdo
AdigitalMAX AdigitalMAX API v1
Operar

Erros

A API responde erro no formato RFC 9457, com dezesseis códigos estáveis e um código de correlação em toda resposta. É por esse código, o requestId, que um chamado de suporte se resolve em minutos em vez de três dias de troca de e-mails.

>>> PENDENTE DE BACKEND <<<

Os dezesseis códigos e o formato da resposta vêm do contrato canônico docs/CONTRATO_API.openapi.yaml. A correspondência entre código e status HTTP indicada na tabela abaixo é a esperada, e será conferida contra o servidor quando ele subir. Ramifique sempre pelo campo codigo, e não pelo status: assim, um ajuste de status não quebra a sua integração.

Formato da resposta

Todo erro devolve um problem details da RFC 9457, com Content-Type: application/problem+json. O campo que o seu código deve ramificar é codigo, e não o status HTTP nem o texto.

404 Not Found
{
  "type": "https://docs.adigitalmax.com.br/erros.html#nao_encontrado",
  "title": "Recurso não encontrado",
  "status": 404,
  "detail": "Recurso não encontrado.",
  "instance": "/v1/documentos/6f2a1c94-8b3d-4e57-9a20-71c5f0d3ab48",
  "codigo": "NAO_ENCONTRADO",
  "requestId": "01J9M4X7QK2ZB8N3P6R0T5V2WY"
}
Os campos, e o que fazer com cada um.
CampoObrigatórioPara quê
codigosimÉ o que o seu código deve ramificar. Estável: um código nunca muda de nome dentro da mesma versão da API, e um status HTTP pode carregar mais de um código.
typesimURI que aponta para a explicação. Aponta para esta página, na âncora do código.
titlesimResumo curto e legível do tipo de problema.
statussimO mesmo status HTTP da resposta, repetido no corpo para sobreviver a intermediários que o percam.
detailnãoTexto sobre esta ocorrência. Não o mostre ao seu usuário final e não o use em condicional: ele é para quem lê o log.
instancenãoO caminho que produziu o erro.
requestIdnãoO código de correlação. Registre sempre.
errosnãoPresente apenas em erro de validação. Arranjo de objetos com campo e mensagem.

A mensagem é deliberadamente pobre. Ela nunca revela nome de tabela, consulta, rastro de pilha nem a razão exata de uma negação entre organizações. Isso não é preguiça: qualquer variação de texto entre dois casos de negação vira um canal lateral que permite deduzir, de fora, se um recurso existe. O detalhe completo do que aconteceu fica no nosso log, ligado ao seu requestId, e o suporte alcança ele em segundos.

O código de correlação

O requestId identifica uma requisição específica no nosso log. Registre-o em todo log de chamada à nossa API, e não só quando der erro: a pergunta que mais atrasa suporte é "o documento saiu duplicado ontem à tarde", e com o identificador da requisição que criou cada um a resposta é imediata.

// Um cliente que registra o requestId em sucesso e em falha, e que
// distingue erro retentavel de erro definitivo. Copie inteiro.

const BASE = "https://api.adigitalmax.com.br/v1";

export class ErroApi extends Error {
  constructor(status, corpo) {
    super(`${corpo.codigo}: ${corpo.title}`);
    this.name = "ErroApi";
    this.status = status;
    // Ramifique por codigo, e nunca por status nem por texto.
    this.codigo = corpo.codigo;
    this.requestId = corpo.requestId;
    this.detail = corpo.detail;
    this.erros = corpo.erros ?? [];
  }

  /** 429 e 5xx passam; o resto e defeito seu e retentar nao ajuda. */
  get retentavel() {
    return this.status === 429 || this.status >= 500;
  }
}

const dormir = (ms) => new Promise((r) => setTimeout(r, ms));

export async function chamar(caminho, opcoes = {}, tentativa = 1) {
  const resposta = await fetch(`${BASE}${caminho}`, {
    ...opcoes,
    headers: {
      Authorization: `Bearer ${process.env.ADM_CHAVE}`,
      Accept: "application/json",
      ...opcoes.headers,
    },
  });

  if (resposta.status === 204) return null;

  const corpo = await resposta.json();

  // Registre SEMPRE, e nao so no erro.
  console.log(
    JSON.stringify({
      nivel: resposta.ok ? "info" : "erro",
      rota: caminho,
      status: resposta.status,
      requestId: corpo.requestId ?? null,
      tentativa,
    })
  );

  if (resposta.ok) return corpo;

  const erro = new ErroApi(resposta.status, corpo);

  if (erro.retentavel && tentativa < 4) {
    // Respeite o Retry-After quando ele vier; ele nao e sugestao.
    const cabecalho = Number(resposta.headers.get("Retry-After"));
    const espera = Number.isFinite(cabecalho) && cabecalho > 0
      ? cabecalho * 1000
      : 2 ** tentativa * 500 + Math.random() * 300; // com jitter

    await dormir(espera);
    return chamar(caminho, opcoes, tentativa + 1);
  }

  throw erro;
}

Os dezesseis valores estáveis de codigo. Ramifique por eles: um mesmo status HTTP carrega mais de um código, e é o código que diz o que fazer.

O catálogo completo, com o status típico e a ação correspondente.
codigo HTTP Significa O que fazer
NAO_AUTENTICADO401 Nenhuma credencial chegou, ou o cabeçalho está malformado. Conferir o Authorization. Não retente: nada muda entre tentativas.
CREDENCIAL_INVALIDA401 A credencial chegou e não vale: chave revogada, segredo errado ou token expirado. Em chave de API, gerar outra. Em OAuth, renovar o token e refazer.
MFA_OBRIGATORIO401 A organização exige segundo fator e a sessão não o cumpriu. Só aparece na sessão do painel. Integração por chave de API não encontra este código.
SENHA_FRACA422 A senha proposta não passa na política. Idem: é do fluxo de painel, não da integração.
TOKEN_INVALIDO401 Token de uso único inválido, expirado ou já consumido: link de assinatura, convite ou redefinição de senha. Emitir outro. Link de assinatura se renova com o reenvio do convite.
ESCOPO_INSUFICIENTE403 A chave é válida, e não tem o escopo que a operação exige. Comparar os escopos da chave com o exigido na referência. Ampliar escopo é criar uma chave nova, no painel.
PAPEL_INSUFICIENTE403 O escopo está certo, e o papel do dono da credencial não alcança a operação. Este é o único 403 sobre recurso da própria organização, e ele é acionável: peça acesso ao administrador.
NAO_ENCONTRADO404 O recurso não existe, ou é de outra organização, ou já foi excluído. Conferir o identificador e o ambiente. Veja a seção dedicada.
ENTRADA_INVALIDA422 A requisição está bem formada e o conteúdo não passa nas regras. Ler erros, que aponta campo por campo. É o único código que traz o motivo específico.
DOCUMENTO_IMUTAVEL409 Você tentou alterar um documento que saiu do rascunho. Conteúdo já apresentado a alguém não muda, porque mudá-lo destruiria a prova. Cancele e crie outro.
CERTIFICADO_INDISPONIVEL409 Você pediu o certificado de um documento que ainda não foi concluído. Esperar o evento document.completed. Certificado parcial seria prova de algo que não aconteceu.
AUTENTICACAO_INCOMPLETA403 O signatário tentou assinar sem cumprir todos os fatores exigidos. Aparece no fluxo da página de assinatura. Se você monta a própria tela com metodoEntrega: "LINK", trate cumprindo os fatores pendentes.
FRANQUIA_ESGOTADA402 A franquia de envelopes do plano acabou e o excedente não está habilitado. Não retente: vai falhar igual. Avise quem cuida do contrato, ou habilite o excedente no painel.
LIMITE_EXCEDIDO429 Passou do limite de requisições da janela. Esperar o que diz o Retry-After. Veja Limites.
CONFLITO409 A operação não cabe no estado atual do recurso, fora dos casos já cobertos acima. Ler o estado antes de agir. Enviar documento já enviado e excluir pasta com conteúdo caem aqui.

Além dos códigos acima, a borda pode responder antes de a aplicação ver a requisição: 413 quando o arquivo passa de 20 MB, e 503 em manutenção. Esses dois podem chegar sem o corpo de problem details, porque quem os emite é o servidor web. Trate corpo ausente como caso possível no seu cliente.

Erros de validação, em detalhe

ENTRADA_INVALIDA é o único código que devolve erros, e é onde você vai passar a maior parte do tempo durante a integração. O formato é um arranjo de objetos com campo e mensagem, usando notação de ponto para chegar dentro de arranjo.

422 Unprocessable Content
{
  "type": "https://docs.adigitalmax.com.br/erros.html#entrada_invalida",
  "title": "Requisição inválida",
  "status": 422,
  "detail": "Quatro campos precisam de correção.",
  "instance": "/v1/documentos",
  "codigo": "ENTRADA_INVALIDA",
  "requestId": "01J9M4X7QK2ZB8N3P6R0T5V2WY",
  "erros": [
    { "campo": "signatarios.0.telefone", "mensagem": "obrigatório quando o fator SMS_OTP é exigido" },
    { "campo": "campos.1.pagina",        "mensagem": "página 9 não existe: o arquivo tem 7 páginas" },
    { "campo": "campos.2",               "mensagem": "x + largura deve ser menor ou igual a 1" },
    { "campo": "prazoEm",                "mensagem": "deve ser uma data futura" }
  ]
}
Os erros de validação que mais aparecem, e a causa real de cada um.
MensagemCausa
signatário com função ASSINAR precisa de ao menos um campo do tipo ASSINATURAVocê posicionou campo de DATA ou NOME e esqueceu o de assinatura. Documento sem onde assinar não pode ser enviado.
obrigatório quando o fator SMS_OTP é exigidoFaltou telefone em E.164. 11988887777 não serve; precisa de +5511988887777.
página N não existe: o arquivo tem M páginasCampo posicionado além do fim do PDF. Confira as páginas do documento depois de criar o rascunho.
x + largura deve ser menor ou igual a 1O campo não cabe na página. Coordenada é fração de 0 a 1, não ponto PostScript. Veja Guias.
o conjunto do signatário precisa conter o do documentofatoresExigidos do signatário reforça o do documento, e nunca reduz. Você tirou um fator em vez de acrescentar.
nível SEGURA exige ao menos um fator de OTPfatoresExigidos com apenas EMAIL e nivelAssinatura: "SEGURA". Acrescente SMS_OTP ou TOTP.
ordem duplicada em documento ordenadoDois signatários com a mesma ordem e ordenado: true. Em documento paralelo a ordem é ignorada e não dá erro.
arquivo protegido por senhaO PDF tem senha de abertura ou de permissões. Remova antes de enviar.
o tipo real do arquivo não é PDFO tipo é conferido pelo conteúdo, e não pela extensão nem pelo cabeçalho declarado. Renomear para .pdf não resolve.
deve ser uma data futuraprazoEm no passado, quase sempre por fuso: 2026-09-30 sem fuso vira meia-noite UTC, que já passou no Brasil.
valor desconhecido para a enumeraçãoErro de digitação ou valor de outra plataforma. DELIVERY_METHOD_EMAIL aqui é EMAIL; veja Migração.

Por que 404 e não 403 em recurso de outra organização

Quando você pede um documento que existe, mas pertence a outra organização, a API responde 404 com corpo e latência idênticos aos de um identificador que nunca existiu. É deliberado, e está escrito no contrato como princípio.

Responder 403 nesse caso significaria "existe, e você não pode", e isso é um oráculo de existência: permite enumerar identificadores válidos e confirmar que um contrato específico existe na plataforma. Numa plataforma de contratos, a simples confirmação de que um documento existe já é comercialmente sensível.

A consequência prática para você: um 404 inesperado tem três causas, e vale checar nesta ordem. Ambiente trocado, com chave de sandbox pedindo documento de produção, que é a causa mais comum; identificador de outra organização; ou documento excluído.

403, quando aparece, é sempre sobre o seu próprio recurso, com o código ESCOPO_INSUFICIENTE ou PAPEL_INSUFICIENTE. Esse é acionável, e a distinção entre os dois códigos diz exatamente o que corrigir: o escopo da chave ou o papel de quem a criou.

O que retentar, e o que não

Retentar o que não é retentável só consome a sua cota e atrasa o diagnóstico.
SituaçãoRetentar?Como
LIMITE_EXCEDIDO (429)SimEspere o Retry-After. Ele é exato, não estimativa.
500, 502, 503, 504SimEspera crescente com variação aleatória, até três vezes. Sem a variação, todos os seus processos retentam no mesmo instante.
Timeout de conexãoSim, com cuidadoSempre com Idempotency-Key. Você não sabe se a requisição chegou.
NAO_AUTENTICADO, ESCOPO_INSUFICIENTE, PAPEL_INSUFICIENTENãoNada muda entre uma tentativa e outra. Corrija a credencial, o escopo ou o papel.
CREDENCIAL_INVALIDA em OAuthUma vezRenove o token e refaça a chamada. Se falhar de novo, o consentimento foi revogado.
FRANQUIA_ESGOTADANãoRetentar não cria franquia. Alerte quem cuida do contrato.
NAO_ENCONTRADO, CONFLITO, DOCUMENTO_IMUTAVEL, ENTRADA_INVALIDANãoSão defeitos da requisição ou do estado. Corrija e mande de novo, uma vez.
CERTIFICADO_INDISPONIVELNão em laçoEspere o webhook document.completed, em vez de sondar até aparecer.

Timeout não significa que a operação falhou. Significa que você não soube o resultado. Se a criação do documento chegou ao servidor e a resposta se perdeu na volta, retentar sem Idempotency-Key cria um segundo documento e consome um segundo envelope. Com a chave, a segunda chamada devolve o primeiro resultado.

Como pedir ajuda

Escreva para contato@adigitalmax.com.br. Com as seis informações abaixo, resolvemos na primeira resposta; sem elas, a primeira resposta é um pedido delas.

  1. O requestId de uma requisição que apresentou o problema. É o item mais importante: com ele lemos o que aconteceu do nosso lado, incluindo o que não vai na resposta.
  2. O prefixo da chave usada, a parte antes do ponto, como adx_live_9f2c7d41. Nunca a chave inteira: se você mandar, ela precisará ser revogada.
  3. O ambiente, produção ou sandbox.
  4. A rota e o método, como POST /v1/documentos.
  5. O que você esperava e o que veio, incluindo o status HTTP e o codigo.
  6. Quando aconteceu, com data, hora e fuso. Se é intermitente, dois ou três horários ajudam mais que uma descrição.
Modelo de chamado
Assunto: ENTRADA_INVALIDA inesperado em POST /v1/documentos

requestId: 01J9M4X7QK2ZB8N3P6R0T5V2WY
chave (prefixo): adx_live_9f2c7d41
ambiente: producao
rota: POST /v1/documentos
quando: 2026-08-16 14:32 -03:00 (e tambem as 11:07 e as 09:44)

Esperado: 201 com o documento criado.
Recebido: 422, codigo ENTRADA_INVALIDA, com
  erros[0].campo    = "signatarios.0.telefone"
  erros[0].mensagem = "obrigatorio quando o fator SMS_OTP e exigido"

O telefone esta preenchido como "+5511988887777" no nosso payload.
Acontece so com este signatario; os outros do mesmo lote passam.

Um chamado assim é resolvido lendo o log, sem precisar reproduzir. Se você não tiver o requestId porque ainda não o registra, comece a registrar agora: o próximo incidente vai acontecer, e ele é a diferença entre minutos e dias.