SDK TypeScript
@databolsa/sdk — cliente tipado da Serving API, com tipos gerados do contrato.
npm install @databolsa/sdkimport { DataBolsa } from "@databolsa/sdk";
const db = new DataBolsa("https://api.databolsa.com", {
apiKey: process.env.DATABOLSA_API_KEY,
});
const petr4 = await db.objects.resolveOne("PETR4", { kind: "equity_security" });
const fechamento = await petr4.facts.history("close", { from: "2026-01-01" });
const issuer = await petr4.issuer();
const indicadores = await issuer.facts.latest();- ESM-only, tipos gerados do contrato OpenAPI via
openapi-typescript— um método por operação, mesmos nomes da referência (getObjectHistory,rankObjects…). - Exporta os tipos de domínio (
Stock,Fii,ObjectHistoryResponse, …) e os tipos crus do contrato (paths,components,operations). - Erros da API viram exceções com o
detaildoproblem+json; endpoints fora do preview do seu deploy lançamNotInPreviewError.
Objetos: comece pelo que você conhece
db.objects é a entrada object-first. Você resolve um ticker, CNPJ, ISIN ou nome para o
objeto do grafo e navega por fatos, relações e capítulos sem saber qual operação responde
por baixo. Os métodos flat continuam ao lado, inalterados.
// Papel → cotações, fatos, emissora
const petr4 = await db.objects.resolveOne("PETR4", { kind: "equity_security" });
const quotes = await petr4.market.quotes.history({ from: "2025-01-01" });
const close = await petr4.facts.history("close", { from: "2025-01-01" }); // uma série, com régua
const fatos = await petr4.at("2025-06-30").facts.latest(); // o tempo mora no handle
const petrobras = await petr4.issuer();
// Companhia → instrumentos emitidos → rating do papel
const debentures = await petrobras.instruments({ subkind: "debenture" });
const ratings = await debentures[0].credit.ratings.list({ scale: "national_br" });
const doEmissor = await petrobras.credit.ratings.list({ perAgency: true }); // uma nota por agência e escala
// Relações vêm do vocabulário do grafo: um método por verbo, gerado
const detentores = await petr4.holders({ limit: 20 }); // fundos que detêm o papel
const emitidos = await petrobras.issued(); // papéis, debêntures e ofertasDescobrir e executar sem conhecer o objeto
Nem toda capacidade tem sujeito, e nem sempre se sabe qual Function serve a pergunta. As três fachadas de descoberta respondem isso sem sair do SDK:
const candidatas = await db.functions.list({ query: "carteira do fundo" });
const spec = await db.functions.describe("funds.holdings.latest"); // input_schema, output_schema
const r = await db.functions.execute("funds.holdings.latest", {
subject: { resolve: "HGLG11" },
input: { limit: 20 },
});
r.result; // o corpo; `r.subject` diz qual objeto respondeu
await db.functions.execute("macro.regime.get", {}); // Function sem sujeito
await db.capabilities.discover({ query: "criar carteira" }); // primitivas, Functions e Actions
await db.modules.list(); // core e extensões instaladasexecute é tipado pelo catálogo: input é o schema da Function e result é o dela, não o de
uma rota por baixo. executeAny é a porta sem tipo para Function publicada depois deste SDK.
O que o handle sabe:
| Chamada | O que faz |
|---|---|
describe() | o mapa completo do objeto (getObject), buscado uma vez |
facts.latest({ facts? }) · facts.history(medida, { from, to, limit }) | medidas com data-base, unidade e escala |
links(rel, { direction, limit }) · related(rel, direction, { kind }) | atravessa uma relação; related devolve handles |
aspects() · aspect(nome, params, { series }) | os capítulos do tipo, cada um com available (este objeto tem dado?); executa um pelo nome |
at(data) | o mesmo objeto numa data: fatos e retratos recebem at, séries recebem to |
fn("funds.holdings.latest", params) | executa uma Function pelo id; input e resposta tipados pelo SCHEMA DELA no contrato (FunctionsByKind diz o que cada tipo publica; Function fora do tipo não compila). fnAny(id, params) é a porta sem tipo para Function publicada depois do SDK |
holders(), issued(), issuer(), holdings()… | um método por relação do tipo, gerado do vocabulário, tipado nas duas pontas: petr4.holders() devolve FundHandle[]. Uma página por default ({ limit, cursor }); { all: true } segue o cursor até o fim. Arestas lidas uma vez por handle |
code | o código negociado (ticker, código da debênture), quando a aresta o trouxe |
Handles tipados: EquitySecurityHandle (market.quotes, market.indicators, market.dividends,
issuer()), CompanyHandle (securities(), instruments(), paper(ticker), credit.ratings,
market.quotes com series), InstrumentHandle (credit.ratings, market.quotes, issuer()) e
FundHandle (profile(), market.quotes, portfolio.latest). Todos são atalhos com nome de
domínio sobre fn(); a tipagem vem do registry, não de código escrito à mão.
Os demais tipos do manifesto chegam como ObjectHandle genérico; tipo que o contrato publique
depois do SDK chega no ramo futuro (isFutureKind(h)), com kind de verdade. Sem kind,
resolveOne devolve a união discriminada: separe o ramo futuro e o switch (h.kind) estreita.
const h = await db.objects.resolveOne("PETR4");
if (isFutureKind(h)) console.log("tipo novo:", h.kind);
else if (h.kind === "company") await h.credit.ratings.list({ perAgency: true });O grão e o tipo governam a assinatura: company.market.quotes.history exige series (o preço
é do papel); facts.history(medida) e property(nome) só aceitam o que o tipo publica
(FactName<K>, PropertyName<K>); medida nova entra por facts.historyAny.
at(data) viaja com a execução e a Function decide: capítulo que só responde o vigente recusa o
corte com 409 temporal_cut_unsupported em vez de devolver o de hoje como se fosse daquela data.
O handle é o sujeito e o tempo: o objeto do handle é o sujeito da chamada, e um parâmetro que
tentasse trocá-lo não chega ao servidor. to/at divergente do at() do handle continua sendo
TemporalConflictError no cliente — nunca sobrescrito em silêncio.
Carteiras e suitability não pertencem mais ao core. Instale o cliente da extensão Wallet e
componha-o sobre a mesma origem e credencial, como mostrado em Extensões: db.use(...).
O antigo namespace db.account.portfolios foi removido para que dados e regras da carteira
permaneçam no contrato e no armazenamento próprios da extensão.
O grão está na assinatura: uma companhia com PETR3 e PETR4 não tem "um preço".
petrobras.market.quotes.history() lança AmbiguousPaperError listando os papéis; escolha com
{ series: "PN" } ou paper("PETR4").
| Erro | Quando | Traz |
|---|---|---|
ObjectNotFoundError | nada casou o texto | query |
AmbiguousObjectError | mais de um candidato plausível | candidates[] |
AspectUnavailableError | o TIPO não tem o capítulo (sem dado não é erro: responde vazio) | available[] |
AmbiguousPaperError | capítulo de papel sem escolher o papel | tickers[] |
UnknownFactError | a medida não existe para o objeto | fact |
TemporalConflictError | to do chamador diverge do at() do handle | parameter, at, pedido |
As recusas que dependem do dado ou do schema são do SERVIDOR, em application/problem+json com
details.code: subject_ambiguous (traz candidates), function_not_applicable (traz kinds),
series_required (traz options), temporal_cut_unsupported e invalid_input (traz accepted).
O vocabulário inteiro está em Como uma chamada acontece. Uma
recusa só, para o SDK, a CLI, o MCP e quem chama a rota direto.
A mesma resposta como tabela
db.objects.table(query) executa uma ObjectQuery (getObjectHistory ou rankObjects) e
devolve a tabela canônica: colunas com papel (time, measure, dimension, label,
identity, status), tipo e régua, linhas de escalares e, em meta.query, a própria consulta
para reexecutar. É o que um notebook desenha sem conhecer a operação por baixo: time no eixo
x, measure no y, dimension separa séries.
const t = await db.objects.table({
operation: "getObjectHistory",
subject: { entity_id: petr4.id },
input: { facts: "close", from: "2025-01-01" },
});
t.schema.columns.find((c) => c.role === "measure")?.unit; // "brl"
const rank = await db.objects.table({ operation: "rankObjects", input: { kind: "equity_security", fact: "dy_12m", limit: 10 } });Séries com réguas diferentes no mesmo lote deixam unit da coluna value nula, trazem a régua
por linha e avisam em meta.warnings (mixed_scales). Parâmetro inventado em input é 400,
como na operação especializada.
Extensões: db.use(...)
O cliente core compõe com o SDK de uma extensão sobre a mesma origem e credencial:
import { DataBolsa } from "@databolsa/sdk";
import { wallet } from "@databolsa/wallet-sdk";
const db = new DataBolsa("https://api.databolsa.com", { apiKey: process.env.DATABOLSA_API_KEY });
const carteira = db.use(wallet({ workspace: "org_abc123" })); // workspace é opcional
const { data } = await carteira.listPortfolios();O plugin recebe só { baseUrl, fetch } — o fetch já leva o Authorization do
cliente. Veja Wallet.
Chave no servidor, nunca no browser
A chave identifica a SUA conta. Em apps web, chame a API do seu backend (ou de um
route handler) e mantenha DATABOLSA_API_KEY como secret do servidor.
Código aberto: packages/core/sdk.