Pular para o conteúdo principal

JavaScript (Node.js)

Cliente para Node 20+ usando o fetch nativo. Sem dependências externas. Para uma versão TypeScript, basta tipar as funções (o snippet abaixo é compatível direto com .ts).

Pré-requisitos

  • Node.js 20 ou superior (fetch nativo).
  • Credenciais em variáveis de ambiente:
export BANQER_TENANT=sua-empresa
export BANQER_CLIENT_ID=...
export BANQER_CLIENT_SECRET=...

Cliente completo

// banqer-client.mjs
const TENANT = process.env.BANQER_TENANT;
const CLIENT_ID = process.env.BANQER_CLIENT_ID;
const CLIENT_SECRET = process.env.BANQER_CLIENT_SECRET;

const API_BASE = `https://api-${TENANT}.banqer.com.br`;
const AUTH_URL = `https://auth.banqer.com.br/realms/${TENANT}/protocol/openid-connect/token`;

const tokenCache = { accessToken: null, exp: 0 };

async function fetchToken() {
const body = new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
});
const res = await fetch(AUTH_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
});
if (!res.ok) throw new Error(`Token request failed: ${res.status} ${await res.text()}`);
const json = await res.json();
// Renova em 80% do TTL para evitar expirar em transito.
tokenCache.accessToken = json.access_token;
tokenCache.exp = Date.now() + json.expires_in * 0.8 * 1000;
return tokenCache.accessToken;
}

async function getToken() {
if (tokenCache.accessToken && Date.now() < tokenCache.exp) {
return tokenCache.accessToken;
}
return fetchToken();
}

export class BanqerError extends Error {
constructor({ status, reason, message }) {
super(`[${status} ${reason}] ${message}`);
this.status = status;
this.reason = reason;
}
}

async function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}

export async function call(method, path, { params, maxAttempts = 3 } = {}) {
const url = new URL(API_BASE + path);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null) url.searchParams.set(k, v);
}
}

let attempt = 0;
let refreshed = false;
while (true) {
attempt += 1;
const token = await getToken();
const res = await fetch(url, {
method,
headers: { Authorization: `Bearer ${token}` },
});

if (res.status === 401 && !refreshed) {
// Token expirou em transito. Renova e tenta uma vez.
tokenCache.accessToken = null;
refreshed = true;
continue;
}

if (res.status === 429 && attempt < maxAttempts) {
const wait = Number(res.headers.get('Retry-After') || 5);
await sleep(wait * 1000);
continue;
}

if (res.status >= 500 && res.status < 600 && attempt < maxAttempts) {
// Backoff exponencial com jitter, teto 30s.
const wait = Math.min(2 ** attempt * 1000 + Math.random() * 1000, 30000);
await sleep(wait);
continue;
}

if (!res.ok) {
let body = {};
try { body = await res.json(); } catch { /* erro sem body JSON */ }
throw new BanqerError({
status: res.status,
reason: body?.details?.[0]?.reason ?? 'UNKNOWN',
message: body?.message ?? res.statusText,
});
}
return res.json();
}
}

/**
* Iterador assincrono sobre uma colecao paginada.
* Uso: for await (const ficha of paginar('/v1/fichas', { atrasoMin: 90 })) { ... }
*/
export async function* paginar(path, params = {}, { pageSize = 200, key } = {}) {
let token = '';
while (true) {
const body = await call('GET', path, {
params: { ...params, pageSize, pageToken: token },
});
const arrayKey = key ?? Object.keys(body).find((k) => Array.isArray(body[k]));
for (const item of body[arrayKey]) yield item;
token = body.nextPageToken || '';
if (!token) return;
}
}

Uso

// app.mjs
import { call, paginar } from './banqer-client.mjs';

// Chamada simples
const { credores } = await call('GET', '/v1/credores');
console.log(credores);

// Detalhe de uma ficha
const ficha = await call('GET', '/v1/fichas/8aa4b425-5fed-4cea-b970-044b36ea9d73');
console.log(ficha.saldoDevedor);

// Iterar fichas em atraso > 90 dias
for await (const ficha of paginar('/v1/fichas', { atrasoMin: 90 })) {
console.log(ficha.idFichaCobrancaExibicao, ficha.saldoDevedor);
}

Alternativa com openid-client

Se sua integração tiver outros provedores OIDC, ou se você quiser delegar o cache de token a uma biblioteca, use openid-client (referência oficial em Node):

npm install openid-client
import * as openid from 'openid-client';

const issuer = new URL(`https://auth.banqer.com.br/realms/${TENANT}`);
const config = await openid.discovery(issuer, CLIENT_ID, CLIENT_SECRET);
const tokenSet = await openid.clientCredentialsGrant(config);
// tokenSet.access_token, tokenSet.expires_in

Você ainda precisa fazer cache do tokenSet se quiser evitar uma chamada ao token endpoint por request.

Para uso no navegador

Não exponha o client_secret no front-end. O fluxo Client Credentials é exclusivamente backend-to-backend. Se sua aplicação web precisa consumir a API, faça as chamadas a partir do seu servidor (Next.js Route Handler, Express, etc.) e devolva apenas o que o navegador precisa.

Erros comuns

SintomaCausa provável
fetch is not definedNode 18 ou inferior. Atualize para 20+ ou use node-fetch.
401 invalid_client no token endpointCredenciais erradas
403 MISSING_SCOPESua credencial não tem o escopo. Veja Escopos.
self signed certificate in certificate chainProxy corporativo. Configure NODE_EXTRA_CA_CERTS.