Erros

Formato

Fora do endpoint de token, todo erro tem esta forma:

{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_json",
    "message": "Requisicao invalida.",
    "doc_url": "https://doc-crpay.deskhotel.com.br/errors/invalid_json",
    "request_id": "req_01KYFJH8NS4KY0ZTBPVEG1QZTG"
  }
}

Programe contra o code, nunca contra a message. O código é estável e faz parte do contrato; a mensagem é texto para humanos e pode ser reescrita a qualquer momento.

POST /oauth/token é a exceção: ele segue o formato da RFC 6749 ({"error": "...", "error_description": "..."}), porque é o que qualquer biblioteca OAuth2 pronta espera encontrar.

request_id

Toda resposta — com erro ou sem — traz um request_id, no corpo do erro e no header Crpay-Request-Id.

Registre-o no seu log. Ao abrir um chamado, ele é o que permite encontrar exatamente a requisição em questão, em segundos, sem precisar de print de tela nem de "aconteceu por volta das três da tarde".

Tipos

type Significado O que fazer
invalid_request_error A requisição está errada Corrija e reenvie. Repetir igual não adianta.
authentication_error Token ausente, inválido, expirado ou sem escopo Obtenha um token novo; se persistir, revise os escopos.
rate_limit_error Excedeu o limite Aguarde o Retry-After e tente de novo.
api_error Falha do nosso lado Retente com espera crescente. Se persistir, abra chamado com o request_id.

Códigos

Autenticação

code HTTP Causa
unauthorized 401 Header Authorization ausente, malformado, ou token inválido/expirado
insufficient_scope 403 O token não tem o escopo exigido pelo endpoint. A mensagem diz qual falta.

Token inválido e token expirado devolvem a mesma resposta, de propósito — distinguir daria informação a quem está tentando forjar um.

Requisição

code HTTP Causa
invalid_json 400 Corpo não é JSON válido
payload_too_large 413 Corpo acima do limite
bad_request 400 Requisição inválida por outro motivo
not_found 404 Rota inexistente

Idempotência

code HTTP Causa
idempotency_key_required 400 Falta o header Idempotency-Key numa operação que movimenta dinheiro
idempotency_key_reuse 409 A mesma chave já foi usada com uma requisição diferente
idempotency_in_progress 409 Outra requisição com essa chave está sendo processada agora

Ver Idempotência.

Limite

code HTTP Causa
rate_limited 429 Limite excedido. O header Retry-After diz quantos segundos aguardar.

Servidor

code HTTP Causa
internal_error 500 Falha inesperada nossa

Como tratar

async function chamarCrpay(caminho, opcoes) {
  const res = await fetch(`https://api-crpay.deskhotel.com.br${caminho}`, opcoes);
  if (res.ok) return res.json();

  const { error } = await res.json();
  // registre SEMPRE o request_id — sem ele, o suporte trabalha no escuro
  console.error('crpay falhou', { code: error.code, request_id: error.request_id });

  switch (error.type) {
    case 'authentication_error':
      // token vencido ou escopo insuficiente: renove e tente UMA vez
      throw new ErroAutenticacao(error);

    case 'rate_limit_error': {
      const esperar = Number(res.headers.get('Retry-After') || 60);
      await new Promise((r) => setTimeout(r, esperar * 1000));
      return chamarCrpay(caminho, opcoes);
    }

    case 'api_error':
      // falha nossa: retente com espera crescente, mantendo a MESMA
      // Idempotency-Key, para nao cobrar duas vezes
      throw new ErroTemporario(error);

    default:
      // invalid_request_error: repetir a mesma requisicao nao vai funcionar
      throw new ErroPermanente(error);
  }
}

Quando retentar

Situação Retentar?
429 Sim, após o Retry-After
500 Sim, com espera crescente, mesma Idempotency-Key
Timeout de rede Sim, mesma Idempotency-Key
400, 404, 409 key_reuse Não — corrija a requisição primeiro
401, 403 Só depois de obter um token novo ou ajustar os escopos

O ponto que mais causa prejuízo em integração de pagamento: em timeout você não sabe se a operação aconteceu. Retentar sem a mesma Idempotency-Key é como cobrar de novo. Com ela, o crpay reconhece a repetição e devolve o resultado da primeira tentativa.