@get-asset/sdk

AssetProvider

Wraps your app, configures the API client (base URL + access token), and stores the shared reporting range (dateFrom/dateTo) that every date-driven component reports on, plus the accounting basis the statement components report on. Required for any @get-asset/sdk or @get-asset/react compound to fetch.

import { AssetProvider } from "@get-asset/sdk";

Props

PropTypeDefaultDescription
baseUrlstringhttps://api.getasset.comOrigin of the Asset API. Set once on the client; every subsequent fetch uses it. Override only when pointing at a staging or self-hosted instance.
businessIdstringBusiness whose financials you're displaying. Forwarded as the business_id path parameter on every request. Optional — omit it during flows that create a business (e.g. onboarding) and set it later with setBusinessId.
accessTokenrequiredstring | (businessId?: string) => Promise<string>Either a literal token string or an async function that returns one. The function form receives the current businessId (undefined when none is set) so it can mint a token scoped to that business. The provider keeps the latest token in a ref so every in-flight request picks up the current value.
timeZonestringruntime IANA time zoneIANA time zone used to resolve user-local picker dates such as today. Pass it explicitly during SSR for deterministic output.
defaultDateFromCalendarDateStringJanuary 1 of the current yearInitial inclusive start of the shared reporting range, in YYYY-MM-DD form. Must be supplied together with defaultDateTo — one alone throws. See Reporting range.
defaultDateToCalendarDateStringDecember 31 of the current yearInitial inclusive end of the shared reporting range, in YYYY-MM-DD form. Must be supplied together with defaultDateFrom, and must not fall before it.
dateFromCalendarDateStringControlled inclusive start of the shared reporting range. Must be supplied together with dateTo — one alone throws. When set the provider mirrors these props instead of owning the range; see Controlling the range.
dateToCalendarDateStringControlled inclusive end of the shared reporting range. Must be supplied together with dateFrom, and must not fall before it.
onDateRangeChange(range: { start, end }) => voidCalled with the inclusive range a picker selected, whenever it differs from the current range. Controlled consumers adopt it into their own state to move the range; uncontrolled consumers can use it to observe changes (e.g. to mirror the selection into a URL).
defaultBasis"accrual" | "modified_cash"resolved from the Business's countryInitial value for the shared basisstate that every statement component reports on. Omit and each component resolves it from the Business's country instead — which costs a request, so set it when you already know the basis. See Accounting basis.
childrenrequiredReactNodeYour app tree.

Access token

Pass either a literal token (e.g. when you've already exchanged one server-side and rendered it into the page) or a function that fetches one. The function form is re-invoked when accessToken changes, so you can rotate tokens without remounting the provider.

Static string — fine when the token is short-lived but already on the client:

<AssetProvider businessId={businessId} accessToken={accessToken}>
{children}
</AssetProvider>

Async function — usually a fetch to a route that exchanges your client_id/client_secret server-side. Never put the secret in the browser.

// Module scope (or useCallback) — a stable identity, so re-renders don't re-mint.
const fetchToken = (businessId?: string) =>
fetch("/api/asset/token").then((r) => r.json()).then((j) => j.token);
<AssetProvider businessId={businessId} accessToken={fetchToken}>
{children}
</AssetProvider>
While the token resolves children render immediately — components sit in their loading state and their requests wait for the in-flight token before leaving, so a request never goes out without one. If the token fetch fails, each component surfaces the failure through its own error state; the rest of the tree is unaffected, so the provider can safely wrap your whole app.

Reporting range

The provider holds one complete, ordered, inclusive dateFrom/dateTo pair, and every date-driven component reports on it. Nothing else is stored: a month, a quarter and a year are just ranges, so a card never has to reconcile separate fields.

<AssetProvider
businessId={businessId}
accessToken={fetchToken}
defaultDateFrom="2026-04-01"
defaultDateTo="2026-04-30"
>
{/* Every date-driven component reports on April 2026. */}
{children}
</AssetProvider>

Each component resolves its own window, first match winning:

  • The component's own dateFrom/dateTo props → use them. They are atomic: pass both or neither, and passing one alone throws.
  • Props omitted → the provider's shared range.
  • No defaultDateFrom/defaultDateToon the provider either → the current calendar year, resolved in the provider's timeZone.

Cards that compare against a previous period mirror whatever range they resolved: a calendar period is compared against the previous one of the same kind (February against January, Q1 against Q4 of the prior year, a year against the year before), and any other range against the same number of days immediately before it.

Driving the range

DatePicker is the control for it — its period and range panels are two views of the same pair, and a valid choice in either updates both values immediately. Every picker instance and every component under the provider moves together, with no key or per-instance value prop.

<AssetProvider defaultDateFrom="2026-04-01" defaultDateTo="2026-04-30" {/* … */}>
<AssetDatePicker /> {/* moves the range for every component below */}
<AssetSnapshotCard /> {/* April 2026, compared against March 2026 */}
<AssetRevenueCard /> {/* follows the same range */}
<AssetExpensesCard dateFrom="2026-01-01" dateTo="2026-12-31" />
{/* detached: all of 2026, and the picker no longer moves it */}
</AssetProvider>

Controlling the range

By default the provider owns the range and the picker mutates it in place. Pass dateFrom/dateTo (with onDateRangeChange) to own it yourself instead — the provider then mirrors your props, and picker selections only call the callback until you re-render with the new values. That makes app state (URL search params, a store) the single source of truth: seed it once, adopt selections in the callback, and every screen shares one range without any per-screen sync code. defaultDateFrom/defaultDateTo are ignored while controlled.

const [range, setRange] = useRangeSearchParams(); // your state — URL params, a store, …
<AssetProvider
dateFrom={range.start}
dateTo={range.end}
onDateRangeChange={setRange}
{/* … */}
>
<DatePicker.Root>{/* … */}</DatePicker.Root>
<SnapshotCard.Root>{/* … */}</SnapshotCard.Root>
</AssetProvider>

Pinning one component

Passing dateFrom/dateTo to a component detaches it from the shared range entirely, so the picker no longer moves it. Use periodDateRange to get the calendar bounds of a month, quarter or year without spelling the dates out.

import { periodDateRange } from "@get-asset/sdk";
const q2 = periodDateRange({ type: "quarter", year: 2026, quarter: 2 });
<AssetRevenueCard dateFrom={q2.start} dateTo={q2.end} />;

Accounting basis

The statement components (RevenueCard, ExpensesCard, SnapshotCard, OperatingExpensesCard, ProfitLossCard, ProfitLossReport, and BalanceSheetReport) report on either the Accrual basis (accrual on the wire) or the Cash basis (modified_cash). Cashflow components take no basis — cashflow is a cash concept. Each component resolves its own basis, first match winning:

  • The component's basis prop set → use it.
  • Prop omitted → fall back to the provider's basis (defaultBasis, or whatever setBasis was last called with).
  • Provider basis unset → look the Business up and use its country default: Canadian Businesses report on Accrual, everyone else on Cash.

Only the last step costs a request, and components hold their own fetch until it answers, so it lands in front of every report on the page. TanStack shares the one request across them, but defaultBasisskips it altogether — set it whenever you already know which basis the viewer should see. A Business lookup that fails falls back to Cash and stays there for the life of the component: a component's retry() re-runs its report query, not the basis lookup.

<AssetProvider businessId={businessId} accessToken={accessToken} defaultBasis="accrual">
<AssetProfitLossReport basisToggle /> {/* toggle drives every card below */}
<AssetRevenueCard /> {/* follows the provider — accrual */}
<AssetRevenueCard basis="modified_cash" /> {/* pinned to cash, ignores the toggle */}
</AssetProvider>

Because the basis lives here rather than on each component, the opt-in basisToggle control can never leave a screen half cash and half accrual — flipping it on one component calls setBasis and moves every component that inherits from the provider. Components pinned with their own basis prop are the exception: they ignore the toggle, which is how you deliberately put both bases on one screen.

useAsset

The same context is readable from any child, so your own chrome can label or lay out around the range every card is reporting on. Moving the range is the picker's job — mount DatePicker (or AssetDatePicker) rather than writing to the context; every consuming card re-fetches automatically when it commits.

import { useAsset } from "@get-asset/sdk";
function RangeCaption() {
const { dateFrom, dateTo } = useAsset();
return (
<p>
Reporting on {dateFrom} to {dateTo}
</p>
);
}

Returned config

useAsset() returns the same object the provider stores internally:

PropTypeDefaultDescription
baseUrlstringAPI origin (echo of the prop).
businessIdstringActive business id, or "" when none is set.
tokenstringThe currently-resolved access token.
isAuthenticatingbooleanTrue while the token is still being (re-)provisioned for the current businessId — e.g. right after setBusinessId. Gate business-scoped actions on it so a request never goes out under a stale token.
timeZonestringResolved IANA time zone used by calendar-date controls.
dateFromCalendarDateStringInclusive start of the shared reporting range. See Reporting range.
dateToCalendarDateStringInclusive end of the shared reporting range.
basis"accrual" | "modified_cash" | nullCurrent shared accounting basis, or nullwhen none has been set — each statement component then resolves one from the Business's country. See Accounting basis.
setBasis(basis: "accrual" | "modified_cash") => voidSwitch the accounting basis for every statement component that inherits it. Backs the built-in basisToggle control, and there is no null — once a basis is set, country resolution is out of the picture.
setBusinessId(businessId: string | null) => voidPoint the provider at a business. Changing it re-invokes the accessToken function with the new id to re-provision the token — used by flows like onboarding that create a business and then switch from a creation-scoped token to a business-scoped one.
Outside a provider — calling useAsset() throws. Wrap your app in AssetProvider first.