IntegraçãoBoas Práticas

Boas Práticas

Padrões recomendados para paginação, tratamento de erros, retry e mais

Visão geral

Este guia reúne as boas práticas para construir uma integração robusta com a API Conta Simples. Aqui você encontra padrões para paginação, tratamento de erros, retry e mais — sem duplicar detalhes técnicos de endpoints, que estão na API Reference.


Paginação

A API usa paginação baseada em cursor para retornar grandes volumes de dados. O campo nextPageStartKey funciona como cursor de navegação.

Como funciona

Primeira requisição

Envie os parâmetros de filtro e limit. Não inclua nextPageStartKey.

Verifique a resposta

Se nextPageStartKey estiver presente e não for null, há mais páginas.

Próxima página

Envie a mesma requisição incluindo o nextPageStartKey da resposta anterior.

Última página

Quando nextPageStartKey for null ou ausente, você chegou ao fim.

Exemplo completo

async function getAllTransactions(
  baseUrl: string,
  token: string,
  startDate: string,
  endDate: string
) {
  const allTransactions: any[] = [];
  let nextPageKey: string | undefined;

  while (true) {
    const payload: Record<string, any> = {
      startDate,
      endDate,
      limit: 100,
    };

    if (nextPageKey) {
      payload.nextPageStartKey = nextPageKey;
    }

    const queryString = new URLSearchParams(payload).toString();

    const response = await fetch(`${baseUrl}/statements/v1/credit-card?${queryString}`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      }
    });

    const data = await response.json();
    allTransactions.push(...data.transactions);

    nextPageKey = data.nextPageStartKey;
    if (!nextPageKey) break;
  }

  return allTransactions;
}

O nextPageStartKey é um token opaco. Não tente decodificar ou modificar — use exatamente como recebido.

Regras

RegraValor
Limite mínimo por página5 itens
Limite máximo por página100 itens
Período máximo por consulta62 dias

Para períodos maiores, divida em múltiplas consultas sequenciais.


Tratamento de erros

Categorias de erros

CódigoSignificadoAção recomendada
400Parâmetros inválidosCorrija os parâmetros e tente novamente
401Token inválido ou expirado, ou assinatura recusadaRenove o token, assine de novo e repita
403Sem permissãoVerifique suas credenciais
404Recurso não encontradoVerifique o ID no path
5xxErro do servidorRetry com backoff exponencial

Diagnóstico detalhado

Veja causas, exemplos e soluções para cada código de erro.

Retry com backoff exponencial

Para erros transitórios (5xx, timeout), implemente retry automático:

Credencial com chave pública: assine cada tentativa de novo. Cada tentativa precisa de uma assinatura própria, com created e nonce novos. Gere os headers de assinatura dentro da função que executa cada tentativa. Veja Assinatura de Requisições.

const RETRYABLE_STATUS = new Set([500, 502, 503, 504]);

async function requestWithRetry(
  method: string,
  url: string,
  maxRetries = 3,
  // Chamado a cada tentativa: devolve headers novos, com assinatura nova quando houver.
  buildOptions: () => RequestInit = () => ({})
): Promise<Response> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 30_000);

      const response = await fetch(url, {
        method,
        ...buildOptions(),
        signal: controller.signal,
      });
      clearTimeout(timeout);

      if (!RETRYABLE_STATUS.has(response.status)) {
        return response;
      }
    } catch (err) {
      if (!(err instanceof DOMException && err.name === "AbortError")) throw err;
      // Trata timeout como retryable
    }

    if (attempt < maxRetries - 1) {
      const waitTime = 2 ** attempt + Math.random();
      await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
    }
  }

  throw new Error("Max retries exceeded");
}

Renovação automática de token

Para erros 401, renove o token automaticamente:

async function requestWithAuthRetry(
  method: string,
  url: string,
  // Chamado a cada tentativa: devolve headers novos, com assinatura nova quando houver.
  buildOptions: () => RequestInit & { headers: Record<string, string> }
): Promise<Response> {
  let response = await fetch(url, { method, ...buildOptions() });

  if (response.status === 401) {
    // Um 401 pode ser token expirado ou assinatura recusada.
    // Renovar o token e assinar de novo resolve os dois casos.
    invalidateTokenCache();
    const newToken = await getFreshToken();
    const options = buildOptions();
    options.headers["Authorization"] = `Bearer ${newToken}`;
    response = await fetch(url, { method, ...options });
  }

  return response;
}

Tratamento de valores e datas

Valores monetários

Valores vêm como number (decimais) em Reais (BRL):

{
  "amountBrl": 150.75
}

Use tipos decimais no seu sistema (não float) para evitar erros de arredondamento em cálculos financeiros.

Datas

ContextoFormatoExemplo
Parâmetros de entradaYYYY-MM-DD2025-01-15
Campos de respostaISO 8601 (UTC)2025-01-15T14:30:00.000Z

A timezone de resposta é UTC. Converta para o fuso local da sua aplicação se necessário.

Transações internacionais

Para transações internacionais, o campo exchangeRateUsd indica a taxa de câmbio. O valor em amountBrl já vem convertido para Reais — use exchangeRateUsd apenas como referência para auditoria.


Download de anexos

Transações podem incluir comprovantes no array attachments. Para baixá-los:

  1. Extraia o id do anexo na resposta de transações
  2. Faça GET /attachments/v1/content/{attachmentId} — veja API Reference
  3. Use o header Content-Type da resposta para determinar o formato

Tipos suportados

Content-TypeExtensãoUso típico
image/png.pngScreenshots, fotos
image/jpeg.jpgFotos, recibos escaneados
application/pdf.pdfNotas fiscais, documentos

Exemplo

import { writeFile, mkdir } from "fs/promises";
import path from "path";

async function downloadAttachment(
  baseUrl: string,
  token: string,
  attachmentId: string,
  outputDir = "./downloads"
): Promise<string> {
  const response = await fetch(`${baseUrl}/attachments/v1/content/${attachmentId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  const contentType = response.headers.get("Content-Type") ?? "";
  const extMap: Record<string, string> = {
    "image/png": ".png",
    "image/jpeg": ".jpg",
    "application/pdf": ".pdf",
  };
  const ext = extMap[contentType] ?? ".bin";

  await mkdir(outputDir, { recursive: true });
  const filepath = path.join(outputDir, `${attachmentId}${ext}`);

  const buffer = Buffer.from(await response.arrayBuffer());
  await writeFile(filepath, buffer);

  return filepath;
}

Segurança

Armazenamento de credenciais

  • AWS Secrets Manager
  • HashiCorp Vault
  • Azure Key Vault
  • GCP Secret Manager
  • Variáveis de ambiente em runtime (não em código)

A chave privada de uma credencial com assinatura deve ser tratada como a API Secret e segue as mesmas regras acima, com duas diferenças:

  • A Conta Simples só recebe a chave pública. Ninguém da Conta Simples, nem o suporte, vai pedir a chave privada.
  • A chave não pode ser trocada na credencial. Para usar outra, crie uma credencial com a chave nova, migre a integração e depois revogue a anterior.

Segregação de ambientes

Use variáveis de ambiente distintas para Sandbox e Produção:

# Sandbox
export OPENAPI_BASE_URL=https://api-sandbox.contasimples.com
export OPENAPI_API_KEY={API_KEY_SANDBOX}

# Produção
export OPENAPI_BASE_URL=https://api.contasimples.com
export OPENAPI_API_KEY={API_KEY_PRODUCAO}

Nunca use credenciais de Produção em ambientes de desenvolvimento ou testes.


Checklist de produção

  • Autenticação com renovação automática de token
  • Paginação implementada corretamente
  • Tratamento de erros por código HTTP
  • Retry com backoff exponencial para 5xx, com assinatura nova a cada tentativa
  • Credenciais e chave privada em cofre de segredos
  • Relógio dos servidores sincronizado por NTP, porque a assinatura vale por 300 segundos
  • Ambientes segregados (Sandbox vs Produção)
  • Monitoramento e alertas configurados
  • Logging estruturado com request_id

Próximos passos