Node.js / TypeScript server SDK for the Malvo API
(Open Finance Brasil). One MalvoClient class manages the apiKey lifecycle and
exposes one method per endpoint: connectors, items, accounts, transactions,
investments, loans, bills, identity, categories, consents, webhooks and
merchants — plus minting connect tokens for the hosted widget.
Talks to a single host: https://api.malvo.io. Data aggregation only — no
payment initiation.
Methods take positional arguments and return paged envelopes:
const accounts = await malvo.fetchAccounts(itemId);
const txs = await malvo.fetchAllTransactions(accounts.results[0].id);Short, un-prefixed type names are exported alongside the Malvo* ones (Item,
Account, Transaction, Connector, Investment, Loan, IdentityResponse,
CreditCardBills, PageResponse, …).
npm install @malvo/serverRequires Node 18+ (uses the global fetch). Zero runtime dependencies.
import { MalvoClient } from "@malvo/server";
const malvo = new MalvoClient({
clientId: process.env.MALVO_CLIENT_ID!,
clientSecret: process.env.MALVO_CLIENT_SECRET!,
});
// apiKey is fetched lazily, cached, refreshed and retried for you.
const { results: connectors } = await malvo.fetchConnectors({ sandbox: true });Keep secrets on the server.
clientId/clientSecret(and the apiKey the SDK derives from them) must never reach the browser. The only value safe to send to a frontend is the 30-minute connect token.
| Step | What | TTL |
|---|---|---|
| 1 | clientId + clientSecret → apiKey (managed internally by the SDK) |
2h |
| 2 | malvo.createConnectToken(...) → accessToken (the connect token) |
30 min |
| 3 | Frontend opens the widget with the connect token | — |
import express from "express";
import { MalvoClient } from "@malvo/server";
const malvo = new MalvoClient({
clientId: process.env.MALVO_CLIENT_ID!,
clientSecret: process.env.MALVO_CLIENT_SECRET!,
});
const app = express();
app.post("/api/malvo/connect-token", async (req, res) => {
// createConnectToken(itemId?, options?) — pass an itemId to mint an
// update-mode token; omit it (undefined) for a new connection.
const { accessToken } = await malvo.createConnectToken(undefined, {
clientUserId: req.user.id,
webhookUrl: process.env.MALVO_WEBHOOK_URL,
});
res.json({ accessToken }); // the frontend passes this to the widget
});After the item/created / item/updated webhook fires, read the item's data.
fetchAllTransactions(accountId) walks the cursor and returns every
transaction in one array:
const { results: accounts } = await malvo.fetchAccounts(itemId);
for (const account of accounts) {
const txs = await malvo.fetchAllTransactions(account.id);
for (const tx of txs) {
console.log(tx.date, tx.description, tx.amount);
}
}Or grab a single page: fetchTransactions(accountId, options) (page-based,
{ results, total, page, totalPages }) or fetchTransactionsCursor(accountId, options) (cursor-based, { results, next }). For huge accounts,
streamTransactions(accountId) is an async iterator that pages lazily.
Every method throws MalvoApiError on a non-2xx response. Branch on the stable
codeDescription, never on the human message:
import { MalvoApiError } from "@malvo/server";
try {
await malvo.fetchItem(itemId);
} catch (err) {
if (err instanceof MalvoApiError && err.codeDescription === "ITEM_NOT_FOUND") {
// handle missing item
} else {
throw err;
}
}401 (apiKey expired) is retried automatically once, and 429 is backed off up
to maxRetries times respecting Retry-After.
import { parseWebhookEvent } from "@malvo/server";
app.post("/webhooks/malvo", express.json(), (req, res) => {
const event = parseWebhookEvent(req.body); // typed, throws on malformed input
res.sendStatus(200); // ack fast; deduplicate by event.eventId
if (event.event === "transactions/created") {
// re-fetch the affected account's transactions
}
});Malvo webhooks have no HMAC signature — secure the endpoint with the custom
headers you register (e.g. a bearer secret) and/or by allowlisting Malvo's
egress IP from the Dashboard.
Webhooks are the source of truth. Persist connections and data from the
item/*andtransactions/*webhooks; don't rely on a single response.
Full reference: https://malvo.io/docs/sdks/node