Here's a mistake that is unbelievably easy for a solo founder to make. You're building a site, a research tool, a newsletter, a dashboard, and you decide it would be great to show stock prices next to your content. You search "free stock API," sign up in five minutes, get a key, and ship a ticker widget by dinner.
Congratulations: there's a decent chance you just violated your data license.
Not because the API was sketchy. Because of a distinction the free tier's landing page didn't lead with: an API being free to call is not the same as the data being licensed for public display. Most free market-data tiers are licensed for personal, non-commercial use: your own portfolio tracker, your own research. The moment prices render on a page that other people can load, you're redistributing market data, and redistribution is exactly the thing exchanges charge for and vendors' free tiers exclude.
Nobody tells you this because the incentives are misaligned: the vendor wants signups, the exchange's fee schedule is a PDF nobody reads, and your widget works perfectly in the demo. The failure mode isn't a build error. It's a letter from a lawyer or an invoice you didn't budget for, and sometimes it's a partner's procurement team asking the one question you can't answer.
We went through this properly, and the process we landed on is entirely repeatable by a team of one. This post is that process. (Usual caveat, and it matters more than usual here: this is an engineering write-up, not legal advice. Licensing terms change and your situation is yours. When real money or risk is on the line, pay a professional for an hour.)
The five words that decide everything
Market-data licensing sounds impenetrable, but almost every decision reduces to five distinctions. Learn these and you can read any vendor's terms with your eyes open:
- Display vs. non-display. Showing prices to humans on a screen is "display use." Feeding them into an algorithm is "non-display." They're licensed differently. A public website is display use.
- Internal vs. redistribution. Using data yourself vs. passing it to anyone else, and rendering it on a public page is passing it to anyone else. Redistribution is the expensive, restricted category.
- Real-time vs. delayed. Data that is minutes old is dramatically cheaper to license, often free at the exchange level, because its trading value has evaporated while its context value (what your readers actually need) remains.
- Consolidated vs. single-venue. "The" stock price most people picture is consolidated across all US exchanges, and consolidated data has its own (expensive) licensing regime. Data from one exchange is a different, far cheaper product, as long as you never present it as the consolidated picture.
- Exchange fee vs. vendor fee. The exchange charges for the data itself; the vendor charges for delivering it to you. A $0 exchange fee does not make the pipeline free, and a paid vendor subscription does not by itself grant you the exchange's display rights.
The trap in one sentence: free tiers usually license personal, internal, non-display-ish use, while your website needs public display with redistribution, the opposite corner of the matrix.
The exception worth knowing about
Once you know the vocabulary, you can go hunting for a lawful corner of the matrix that a small team can actually afford. In US equities, one is well-documented: delayed data from IEX Exchange.
At the time we verified this (mid-2026, against IEX's published fee schedule and market-data policies; check the current documents yourself, since they do get revised): delayed IEX market data, defined as data at least fifteen minutes old, carried a $0 exchange fee and an explicit right of further redistribution, subject to display requirements, specific attribution wording, a prominent delay message, and honest labeling. IEX also doesn't hand delayed data to you directly; you obtain it through one of the market-data providers they list, and that delivery is where your actual monthly cost lives.
The trade-offs are real and worth stating plainly, because your integrity here is the whole point:
- The data is at least 15 minutes old, always, and you must say so.
- It's one venue. IEX's share of US trading volume is small. Prices can differ from the consolidated tape, and volume figures describe that venue only. You must never present it as "the" market-wide number.
- The display requirements are obligations, not suggestions.
For a research or context product ("what's this company's stock roughly doing while you read about them"), that trade is excellent. For anything resembling a trading tool, it isn't, and no amount of engineering changes that. Decide which product you're building before you decide anything else.
The process (the part you can copy)
Here's the repeatable part, what we'd do again at any size:
1. Write down what you actually need. Delayed OK? Single venue OK? Which capabilities: last price, bid/ask, depth, history? Every "no" you can live with removes a digit from the price and a page from the contract.
2. Read the primary documents, not the pricing page. The exchange's fee schedule and market-data policies, and the vendor's actual license terms. Save copies. Blog posts (including this one) go stale; the documents are the truth. And know that these documents get revised; two revisions can even disagree with each other. When readings conflict, take the stricter one and write that decision down.
3. Keep a decision record with verification dates. One markdown file: what you're using, under which terms, verified on which date, with what open questions. This is twenty minutes of work that turns "I think we're fine" into something you can show a partner, a buyer, or a lawyer. Re-verify before anything goes live, and date the re-verification.
4. Get the specific rights in writing from the vendor. Before contracting, ask directly: does this tier permit public display on my site? Does it include the exchange's data product, with original event timestamps? What exactly is this field: is that "previous close" the exchange's official close, or your own blended calculation? Vague answers to precise questions are answers.
5. Treat every product and every provider as a separate grant. A contract covering one data product does not license another; a right granted through one provider does not follow the same data arriving through a different one. Model your integration the same way, per product, per provider, so the code can't accidentally outrun the paperwork.
6. Build the compliance rules into the code, fail-closed. More below. This is the engineering half, and it's where a small team can genuinely shine.
7. Never fall back to an unlicensed source. The strongest temptation arrives on day one of an outage: "just use the scraped/free/unofficial source until the real one is back." Decide in advance, in code, that the answer is an honest "unavailable" state. An empty widget is a state; an unlicensed number is a liability.
Engineering patterns that keep you honest
The insight that shaped our whole build: compliance rules you enforce in code are the cheapest kind. A lawyer reviews a document once; a server-side gate reviews every single observation forever. Five patterns carried most of the weight:
Fail closed, everywhere. The default configuration serves nothing. An unknown provider name serves nothing. A missing credential is an explicit "unhealthy" state, not a silent retry against something else. Activating a data source is a deliberate code change, an entry in a registry, not the side effect of an API key existing in the environment. Keys leak into .env files and linger for years; a stray key must never be able to turn a data source on.
Enforce the delay on the data's own timestamps, server-side. Don't assume a "delayed" feed is delayed; prove it per observation. Keep the authoritative market/event timestamp on every record and gate on it:
// Eligible for public display only when the observation itself
// proves it is old enough; never trust the pipeline's label.
function displayEligible(marketTimestamp: unknown, nowMs: number): boolean {
const ts = typeof marketTimestamp === 'string'
? Date.parse(marketTimestamp)
: NaN;
if (!Number.isFinite(ts)) return false; // malformed → withhold
if (ts > nowMs) return false; // future → withhold
return nowMs >= ts + 15 * 60_000; // too fresh → withhold
}
Run this on every path that returns data (fresh fetches, cache hits, stale retention) so no cached copy or race can leak a too-fresh observation. Test the exact boundary: 14:59.999 old is blocked, 15:00.000 exactly is eligible. Missing, malformed, and future timestamps are all withheld, because "can't prove it's compliant" and "isn't compliant" must get the same treatment.
Keep "delayed" and "stale" as separate concepts. Delayed is intentional and legal: the data is supposed to be 15+ minutes old. Stale is operational: your refresh failed and you're serving an older success. If you conflate them, you'll either scare users about healthy data or hide real outages. Keep two flags, give each its own label, and let each mean one thing.
Centralize the legally significant copy. Attribution wording, the delay statement, the venue-scope disclaimer: put the exact strings in one module and make every surface render from it. The moment that copy is duplicated across components, one of the copies will drift, and the drifted one will be the one a reviewer screenshots.
Keep the boundary controlled. Your quote endpoint is a feature of your site, not a public market-data API, because redistributing data as an API is a different licensing conversation entirely. Allowlist the symbols you actually display, cap the request size, don't add permissive CORS, and return normalized display fields only, never raw upstream payloads, credentials, or vendor error text.
None of this is exotic. It's a few hundred lines and a test file, and it converts "we intend to comply" into "the server refuses not to."
Prompts that pull their weight
As with everything else on this blog: a capable LLM won't replace judgment, but it's a tireless second reader for exactly this kind of dense-document work. Prompts we actually used, generalized:
Extract obligations from terms you're evaluating:
Here are a vendor's data license terms and an exchange's market-data policy
[paste]. Build me a table of every obligation that applies to displaying this
data on a public website: attribution wording, delay requirements, display
requirements, usage reporting, notice requirements, audit rights, and
termination triggers. Quote the exact language for each, and flag every place
where the two documents disagree or where a term is ambiguous enough that I
should get written clarification before signing.
Draft the precise questions for a vendor:
I run a small public website and want to display [data product] from
[exchange] via your service. Draft a short email asking the vendor to confirm
in writing: (1) that my tier permits public external display on my site,
(2) that I receive the exchange's actual data product with original event
timestamps rather than derived values, (3) the provenance of each field I
plan to display, and (4) any obligations that pass through from the exchange
to me. Keep it specific enough that a vague answer would be conspicuous.
Audit your own code for compliance drift:
This codebase displays licensed market data under these rules: [paste your
decision record]. Find every code path that returns market data to a browser
and verify each one passes through the delay-eligibility gate; find any
user-facing copy about the data's source, delay, or scope that is not
rendered from the central copy module; and find any fallback path that could
serve data from a source other than the licensed one. Report file and line
for anything that drifts.
Turn the rules into boundary tests:
Write tests for this display-eligibility function [paste] covering: exactly
at the boundary, one millisecond inside it, missing timestamp, malformed
timestamp, future timestamp, and a cached observation re-checked after time
passes. The tests should fail if anyone ever swaps the authoritative market
timestamp for the server's receive time.
The same caution as ever applies: paste terms and code, never credentials.
The whole thing in one breath
Free-to-call is not free-to-display, and the gap between those two is where small teams get hurt. But the lawful path is genuinely within reach: learn the five distinctions, read the primary documents and date your reading, find the delayed-data corner of the market where the economics favor you, get the specific rights in writing per product and per provider, and then encode every rule into fail-closed server-side checks so the code refuses to do what the contract forbids. An honest "quotes unavailable" state on day one beats a lawyer's letter on day ninety, and when the data does light up, you'll be able to explain exactly why you're allowed to show it. That's a moat most widgets can't claim.