logo
IntegrationBest practices

Best practices

Recommended patterns for pagination, error handling, retries, and more

Overview

This guide collects best practices for a reliable Conta Simples API integration. It covers pagination, errors, retries, and more — without duplicating per-endpoint details, which live in the API Reference.


Pagination

The API uses cursor-based pagination for large result sets. The nextPageStartKey field is the cursor.

How it works

First request

Send filter parameters and limit. Do not send nextPageStartKey.

Check the response

If nextPageStartKey is present and not null, more pages exist.

Next page

Send the same request with the nextPageStartKey from the previous response.

Last page

When nextPageStartKey is null or missing, you are done.

Full example

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;
}

nextPageStartKey is opaque. Do not decode or modify it — use it exactly as returned.

Rules

RuleValue
Minimum per page5 items
Maximum per page100 items
Maximum range per request62 days

For longer periods, run multiple sequential queries.


Error handling

Error categories

CodeMeaningSuggested action
400Invalid parametersFix parameters and retry
401Token invalid or expiredRefresh the token and retry
403Insufficient permissionsCheck your credentials
404Not foundVerify path IDs
5xxServer errorRetry with exponential backoff

Detailed troubleshooting

Causes, examples, and fixes for each error code.

Retry with exponential backoff

For transient errors (5xx, timeout), use automatic retry:

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

async function requestWithRetry(
  method: string,
  url: string,
  maxRetries = 3,
  options?: 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,
        ...options,
        signal: controller.signal,
      });
      clearTimeout(timeout);

      if (!RETRYABLE_STATUS.has(response.status)) {
        return response;
      }
    } catch (err) {
      if (!(err instanceof DOMException && err.name === "AbortError")) throw err;
      // Treat timeout as 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");
}

Automatic token refresh

For 401, refresh the token and retry:

async function requestWithAuthRetry(
  method: string,
  url: string,
  options: RequestInit & { headers: Record<string, string> }
): Promise<Response> {
  let response = await fetch(url, { method, ...options });

  if (response.status === 401) {
    invalidateTokenCache();
    const newToken = await getFreshToken();
    options.headers["Authorization"] = `Bearer ${newToken}`;
    response = await fetch(url, { method, ...options });
  }

  return response;
}

Values and dates

Money amounts

Amounts are represented as decimal numbers in Brazilian reais (BRL):

{
  "amountBrl": 150.75
}

Use decimal types in your stack (not binary floats) to avoid rounding errors in financial math.

Dates

ContextFormatExample
Request parametersYYYY-MM-DD2025-01-15
Response fieldsISO 8601 (UTC)2025-01-15T14:30:00.000Z

Response timestamps are in UTC. Convert to your app’s local timezone as needed.

International transactions

For international items, exchangeRateUsd carries the FX rate. amountBrl is already in BRL — use exchangeRateUsd only for audit context.


Downloading attachments

Transactions may list receipts in the attachments array. To download them:

  1. Read the attachment id from the transaction response
  2. GET /attachments/v1/content/{attachmentId} — see API Reference
  3. Use the response Content-Type to detect format

Supported types

Content-TypeExtensionTypical use
image/png.pngScreenshots, photos
image/jpeg.jpgPhotos, scanned receipts
application/pdf.pdfInvoices, documents

Example

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;
}

Security

Storing credentials

  • AWS Secrets Manager
  • HashiCorp Vault
  • Azure Key Vault
  • GCP Secret Manager
  • Runtime environment variables (not in code)

Environment separation

Use different env vars for Sandbox and Production:

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

# Production
export OPENAPI_BASE_URL=https://api.contasimples.com
export OPENAPI_API_KEY={API_KEY_PROD}

Never use Production credentials in development or test.


Production checklist

  • Authentication with automatic token refresh
  • Pagination implemented correctly
  • Per–HTTP code error handling
  • Exponential backoff for 5xx
  • Credentials in a secret store
  • Sandbox vs Production kept separate
  • Monitoring and alerts
  • Structured logging with request_id

Next steps