Skip to content

SDK reference

@budvo/sdk is a tiny library with no dependencies for talking to Budvo from an app@1 add-on. It handles the bridge's request ids, timeouts and errors, and gives you typed methods for everything Budvo offers. It speaks bridge protocol 1.x, so put "sdk": "^1.0.0" in your manifest's compat.

Install

Add-ons can't load outside scripts, so the SDK has to be a file in your folder.

One file. Download budvo-sdk.js, put it next to your page and load it before your own script. It sets window.Budvo.

html
<script src="budvo-sdk.js"></script><script src="app.js"></script>

With npm. The package has the same file, an ES module build with TypeScript types, the manifest schema and the budvo-addon command line tool.

terminal
npm install --save-dev https://budvo.nulldaily.com/sdk/budvo-sdk-1.0.0.tgzcp node_modules/@budvo/sdk/budvo-sdk.js my-addon/app/

If you bundle your add-on, import it instead and let your bundler include it:

javascript
import { Budvo } from "@budvo/sdk";

Connect

javascript
const pc = await Budvo.connect();

connect() says hello with context.get and resolves a client once Budvo answers. Outside Budvo (opened directly in a browser) it fails with a no_host error, so you can show a friendly message instead.

javascript
let pc;try {  pc = await Budvo.connect();} catch (err) {  document.body.textContent = "Open this add-on from Budvo.";  throw err;}
OptionDefault
timeout10000Milliseconds to wait for each answer. net.fetch waits at least 30 seconds.
parentwindow.parentWhere requests go. Tests and custom hosts pass their own.
selfwindowWhere answers arrive.

Context

pc.context holds what Budvo said at connect time. Theme events keep appearance and tokens current; await pc.refresh() asks again.

json
{  "addon": { "id": "pixel-pets", "version": "1.2.0" },  "sdk": "1.0.0",  "budvo": "0.4.0",  "appearance": "dark",  "tokens": { "bg": "#0a0b0d", "accent": "oklch(0.8 0.11 182)" },  "currency": "USD",  "locale": "en-US",  "debt_mode": true,  "privacy": false}
Field
addonYour add-on's id and the version that's running
sdk, budvoThe bridge protocol and Budvo server versions
appearancelight or dark
tokensBudvo's current design tokens, including the owner's theme. See Runtimes.
currency, localeFor formatting money: USD, en-US
debt_modeThe household is focused on paying off debt
privacyPrivacy mode is on: amounts are hidden, and data methods may answer not_allowed

Methods

Every method returns a promise. Money is always an integer number of cents in the household's currency; dates are YYYY-MM-DD. Nothing returns account numbers or transactions.

MethodBridge methodPermission
pc.ui.toastui.toastnone
pc.ui.resize, pc.ui.autoResizeui.resizenone
pc.summarysummary.readsummary:read
pc.debtsdebts.readdebts:read
pc.goalsgoals.readgoals:read
pc.jar.readjar.readjar:read
pc.jar.pledgejar.pledgejar:pledge
pc.storage.get, pc.storage.setstorage.get, storage.setstorage
pc.net.fetch, pc.net.jsonnet.fetchnet
pc.league.*league.create, league.join, league.list, league.publish, league.read, league.invite, league.leave, league.deleteleague:share
pc.call(method, params)anywhatever that method needs

ui.toast

javascript
await pc.ui.toast("Biscuit loved that.");

Shows a short message in Budvo. Params: { message }. Result: {}.

ui.resize

javascript
await pc.ui.resize(420);        // the frame's height in CSS pixelsconst stop = pc.ui.autoResize(); // follow the page's height from now on

Asks Budvo to size the frame, for the iOS app and inline cards. Params: { height }. Result: {}. autoResize(element) watches element (the whole page by default) with a ResizeObserver and returns a function that stops it.

summary

javascript
const s = await pc.summary();
json
{ "safe_to_spend_cents": 42350, "debt_total_cents": 1284700, "debt_free_on": "2027-11-01", "next_payday": "2026-10-02" }

debt_free_on and next_payday can be null. In privacy mode it answers not_allowed unless the owner allowed your add-on.

debts

javascript
const debts = await pc.debts();
json
[{ "id": "debt_1", "name": "Visa", "kind": "credit_card", "balance_cents": 590962, "apr_bps": 2499, "payoff_on": "2027-01-28" }]

apr_bps is the APR in basis points: 2499 is 24.99%. Privacy mode applies. The bridge answers { "debts": [...] }; the SDK returns the list.

goals

javascript
const goals = await pc.goals();
json
[{ "id": "goal_1", "name": "Emergency fund", "target_cents": 300000, "progress_cents": 112500, "target_on": "2027-04-17" }]

Privacy mode applies. The bridge answers { "goals": [...] }; the SDK returns the list.

jar.read

javascript
const jar = await pc.jar.read();
json
{ "balance_cents": 1250, "target": { "id": "debt_1", "name": "Visa" }, "smash_day": "2026-10-04", "smashed_total_cents": 8300 }

The jar's balance, where it goes when it's smashed, and how much has been smashed so far.

jar.pledge

javascript
const r = await pc.jar.pledge(150, "Fresh fish for Biscuit");if (r.pledged_cents === 0) showMessage("The jar is full for today. See you tomorrow!");else if (r.capped) showMessage("That reached today's limit.");
json
{ "pledged_cents": 150, "capped": false, "left_today_cents": 350 }

Adds to the jar. Never moves money. Params: { amount_cents, note }, where amount_cents is a whole number above 0 and note is a short line the owner sees, like what the pledge was for.

  • The daily limit is the smaller of your manifest's pledge.daily_max_cents and the owner's setting.
  • Above the limit, the rest of the pledge is dropped and capped is true. pledged_cents is what actually went in, possibly 0.
  • When capped is true, don't retry. Tell the owner kindly; the limit is doing its job.
  • left_today_cents is what your add-on may still pledge today.

storage

javascript
const pet = await pc.storage.get("pet", { name: "Biscuit", mood: 3 }); // fallback when nothing is savedawait pc.storage.set("pet", { ...pet, mood: 4 });await pc.storage.set("pet", null); // clears it

Any JSON value, per add-on and household: everyone in the household sees the same data. Up to 64 KB for your whole add-on; the SDK refuses a single value over that with too_large before sending it. Bridge params: { key } and { key, value }; results { value } (null when nothing is saved) and {}.

net

javascript
const r = await pc.net.fetch("https://api.example.com/v1/things?limit=5");// { status: 200, body: "..." }const data = await pc.net.json("https://api.example.com/v1/things?limit=5");

A GET through Budvo to a host in your manifest's network list. The response must be JSON or text, up to 1 MB. net.fetch resolves with { status, body } for any status; net.json parses the body and fails with network_error unless the status is 200 to 299.

league

javascript
const { league, invite } = await pc.league.create({ name: "Sunday crew", alias: "Ron" }); // invite.code: "ABCD-EFGH"await pc.league.join("ABCD-EFGH", "Sam");                   // on a friend's Budvoconst leagues = await pc.league.list();// At pick time: commit (keep side and salt in storage until kickoff).const { commit, salt } = Budvo.commitPick("nfl:401547417", "home");await pc.league.publish(league.id, [{ ref: "nfl:401547417", season: "2026", locked_at: kickoff, commit }]);// After kickoff: reveal, then score once it settles.await pc.league.publish(league.id, [{ ref: "nfl:401547417", side: "home", salt, points: 15, result: "win" }]);const { leaderboard, members, entries } = await pc.league.read(league.id);

Leagues with friends on other Budvo servers. Budvo keeps them on the Budvo website and sends only the display name a person chose, a random member id per league and entries with exactly these fields: ref, commit or side and salt, points, season, locked_at (unix seconds) and result (win, loss, push, void). Anything else is refused with invalid, so amounts, stakes and the jar can't be sent by mistake.

  • A pick is committed as a hash before locked_at and revealed after it; a reveal that doesn't match, a commit after the lock or a reveal before it are refused per entry (results[i].error). Others see your side only after the lock.
  • It works only when the server is linked to a Budvo account (not_linked) and the owner switched on "Share leagues" for your add-on (leagues_off). The first create, join or publish makes Budvo ask the person once; if they say no, the call fails with declined.
  • Invite codes are single use unless reusable: true, and expire after 7 days (expires_in_days, 1 to 30). Display names are 1 to 24 characters and unique in a league.
  • Rows expire 90 days after a season's last lock. pc.league.leave(id) removes you; the owner can pc.league.delete(id).

Events

javascript
const stop = pc.on("theme", ({ appearance, tokens }) => {  console.log("now", appearance);});stop(); // stop listening

Budvo pushes theme when the owner switches light and dark mode or picks another theme. pc.context is updated before your handler runs. A handler that throws doesn't stop the others.

Theme helpers

javascript
pc.syncTheme(); // apply Budvo's look now and whenever it changes

syncTheme() sets every token as a CSS variable on the root element (--bg, --surface, --accent and so on, the same names as the token table), sets color-scheme, a data-appearance attribute and a dark class in dark mode, and follows theme events. It returns a function that stops following.

For more control, Budvo.applyTheme(tokens, options) does one update:

OptionDefault
appearancelightlight or dark
rootdocument.documentElementWhere the variables go
prefix----pc- gives --pc-accent, if your own CSS already uses --accent
defaultstrueFill tokens the host didn't send with Budvo's defaults

Values that aren't plain colors are ignored. Budvo.DEFAULT_TOKENS has Budvo's own light and dark tokens, handy for previews outside Budvo.

Money

javascript
Budvo.formatMoney(1250);                                   // "$12.50"Budvo.formatMoney(500, { compact: true });                 // "$5"Budvo.formatMoney(100, { sign: true });                    // "+$1.00"Budvo.formatMoney(123456, { currency: "EUR", locale: "de-DE" }); // "1.234,56 €"pc.formatMoney(150);                                         // in the household's currency and locale

Errors

Every failure rejects with a BudvoError. Check code; message is written for people.

javascript
try {  await pc.debts();} catch (err) {  if (Budvo.isBudvoError(err, "not_allowed")) hideDebtCard();  else throw err;}
CodeFromWhen
not_allowedBudvoThe add-on lacks the permission, or privacy mode hides the data
unknown_methodBudvoThe server doesn't know the method, for example an older server
bad_requestBudvo or SDKMissing or invalid params, like a pledge of 0
too_largeBudvo or SDKStorage over 64 KB, or a response over 1 MB
network_errorBudvo or SDKnet.fetch couldn't reach the host, or net.json got a bad status
not_linked, leagues_offBudvoLeague methods: the server isn't linked, or the owner hasn't switched on league sharing
declinedBudvoThe person said no to Budvo's question (a pledge, or sharing with a league)
timeoutSDKNo answer in time
no_hostSDKNot running inside Budvo, or the connection was closed
bad_responseSDKAn answer that doesn't make sense, like net.json on a body that isn't JSON

err.method names the bridge method that failed.

The protocol

You don't need this with the SDK, but here's what goes over the wire. Messages are plain objects posted with window.parent.postMessage, and Budvo answers the frame the same way.

text
request:  { "budvo": 1, "id": "<unique>", "method": "jar.pledge", "params": { ... } }response: { "budvo": 1, "id": "<same>", "result": { ... } }       or { "budvo": 1, "id": "<same>", "error": { "code": "not_allowed", "message": "..." } }event:    { "budvo": 1, "event": "theme", "data": { ... } }

The SDK ignores messages that don't come from the parent window, matches answers to requests by id, and times out requests that get no answer.

Command line

The npm package includes budvo-addon:

terminal
npx budvo-addon init my-addon      # a working app@1 add-on to start fromnpx budvo-addon dev my-addon       # run it at http://localhost:5190 in a stand-in for Budvonpx budvo-addon validate my-addon  # check it the way Budvo and the marketplace will

dev serves your add-on the way Budvo does, in a sandboxed frame with the same content security policy, next to a panel of made-up household data: a jar with a daily limit you can lower, a new-day button, light and dark mode, privacy mode, debt mode, an offline switch for net.fetch, and a log of every bridge message. net.fetch really fetches, but only from the hosts in your manifest. Storage lives in your browser.

TypeScript

The npm package ships types for everything: Context, Summary, Debt, Goal, Jar, PledgeResult, FetchResult, ThemeTokens, ErrorCode and the rest.

typescript
import { Budvo, type PledgeResult } from "@budvo/sdk";const pc = await Budvo.connect();const r: PledgeResult = await pc.jar.pledge(100, "A claw machine play");