rwa-feeds
v0.2.0
Published
Live market data for real-world-asset valuation — commercial property price growth, cap-rate anchors, vacancy, construction cost and metro labour data, from FRED and the BLS. No API key required.
Maintainers
Readme
rwa-feeds
Live market data for valuing real-world assets, from public sources that need no API key.
Commercial property price growth, the cap-rate anchors (risk-free rate, credit spread, mortgage rate), vacancy, construction-materials cost, landlord pricing power, and metro-level labour data — fetched together, dated, and attributed to the series they came from.
npm install rwa-feedsZero runtime dependencies. Node 18+ (uses global fetch).
Why it exists
An asset with no continuous market — a building, a private credit book — has no price to look up, so it has to be valued. The property facts come from whoever discloses them. The market it trades in does not: the risk-free rate, where commercial property prices have actually moved, what construction materials cost this month, whether the metro is adding jobs. Those are all observable, published, and free.
This package fetches them, and refuses to invent them.
Use it
import {fetchMarketSnapshot, formatSnapshot} from "rwa-feeds";
const snapshot = await fetchMarketSnapshot({metro: "austin"});
console.log(snapshot.treasury10y.value); // 4.68
console.log(snapshot.treasury10y.asOf); // "2026-08-14"
console.log(snapshot.treasury10y.ageDays); // 4
console.log(snapshot.treasury10y.url); // the exact URL it came from
// Or as a block to hand a valuation model:
console.log(formatSnapshot(snapshot));- 10-year Treasury yield: 4.68% (as of 2026-08-14, FRED DGS10)
- US corporate credit spread (OAS): 0.8% (as of 2026-08-14, FRED BAMLC0A0CM)
- 30-year fixed mortgage average: 6.67% (as of 2026-08-13, FRED MORTGAGE30US)
- US commercial property price growth: -7.01% (as of 2025-04-01, 17 months old, FRED COMREPUSQ159N)
- US rental vacancy rate: 7.3% (as of 2026-04-01, 5 months old, FRED RRVRUSQ156N)
- PPI, lessors of nonresidential buildings: 178.921 (as of 2026-07-01, FRED PCU5311205311202)
- PPI, construction materials: 372.301 (as of 2026-07-01, FRED WPU1361)
- CPI, rent of primary residence: 447.963 (as of 2026-07-01, BLS CUUR0000SEHA)
- austin metro unemployment rate: 4.1% (as of 2026-06-01, FRED AUST448URN)
- austin metro nonfarm employment: 1,426.4k (as of 2026-06-01, FRED AUST448NA)From the terminal
npx rwa-feeds snapshot --metro denver # everything, dated
npx rwa-feeds snapshot --json # the same, machine-readable
npx rwa-feeds series MORTGAGE30US --last 8
npx rwa-feeds bls CUUR0000SEHA
npx rwa-feeds list # every series, and what each is forIndividual series
Any FRED or BLS series id works, not just the curated set:
import {fetchFredSeries, fetchBlsSeries} from "rwa-feeds";
const mortgage = await fetchFredSeries("MORTGAGE30US");
mortgage.latest; // { date: "2026-08-13", value: 6.67 }
mortgage.observations; // the full history, oldest first
const rent = await fetchBlsSeries("CUUR0000SEHA");Three design decisions worth knowing
Publication lag is data, not a detail. Every input carries ageDays. The 10-year
Treasury is a few days old; US commercial property price growth is quarterly and published
in arrears, so it can be more than a year behind. Both would otherwise read as "today", and
a valuation that treats them alike is wrong in a way nobody can see. formatSnapshot states
the lag out loud past a quarter.
There is no fallback, and no silent omission either. No bundled sample data, no
last-known-good cache, no default standing in for a failed fetch. The risk-free rate is
required — lose it and fetchMarketSnapshot throws, because a cap rate built without it has
no foundation. Every other series is corroborating evidence: if one cannot be fetched its
field is null and it is listed in snapshot.unavailable with the reason, and
formatSnapshot prints UNAVAILABLE this fetch — treat as unknown, do not assume a value.
That distinction is the whole point. Failing everything when one supporting index is rate-limited is brittle — the BLS free tier is a few dozen requests a day, and hitting it should not stop a valuation. Substituting a number for the missing one would be worse. So the gap is reported instead, and whatever consumes the snapshot is told what it does not know.
A 200 is not proof of data. BLS answers an unknown series id with
REQUEST_SUCCEEDED and an empty result; a 200 carrying an HTML page is not CSV. Both are
checked, because a fetcher that shrugs at an unexpected body is one refactor away from
parsing a maintenance page into a cap rate.
Sources
| Source | Endpoint | Key |
|---|---|---|
| FRED (St. Louis Fed) | fred.stlouisfed.org/graph/fredgraph.csv | none |
| BLS | api.bls.gov/publicAPI/v1 | none (--bls-key opts into v2) |
Both are US federal statistical agencies publishing openly. Pass blsApiKey for BLS v2 if
you need its higher quota; nothing else changes.
Metros
austin and denver today, each mapped to its MSA unemployment and nonfarm-employment
series. FRED_METRO_SERIES is exported — adding a metro is two series ids, and any US MSA
FRED publishes can be added the same way.
Licence
MIT. Built for SwarmNAV, a staked multi-agent valuation oracle, and extracted because the data layer is useful on its own.
