Keep the key server-side
TheX-API-Key secret belongs in your backend, an agent runtime config, or a secret manager. Never anywhere a user or a model can read it.
- No
STOCKCONTEXT_API_KEYin a browser bundle, mobile app, public repo, prompt, log line, or screenshot. - Proxy client calls through your own backend; the key never reaches the browser.
- The raw secret is shown once at creation. Store it in a secret manager, not a config file you commit. New keys come from the dashboard.
- On suspected leak, recreate the key and swap it in. Revocation takes effect within seconds (the auth cache holds a verified key for at most 15 seconds).
- Do not cache
UNAUTHORIZEDorPLAN_UPGRADE_REQUIREDresponses as if they were data; a fixed key or upgrade should take effect immediately.
Cache by data family
A snapshot during market hours goes stale in seconds; a 10-K section never changes. Cache each family on its own clock. These are starting points to tune against your traffic, not API contracts:| Data family | Suggested cache |
|---|---|
| Search and coverage | Hours to days |
| Snapshot during market hours | 30–120 seconds |
| Snapshot after close | 5–30 minutes |
| Valuation, fundamentals, earnings, dividends | 1–24 hours |
| Technicals, price action, history | 5–30 minutes for active views; longer for offline reports |
| Filing metadata and section text | Days to indefinite |
| Insider activity | 1–24 hours |
| Calendar | 1–24 hours |
search, coverage, snapshot, and valuation are reachable on a Free key; every other family in this table needs Starter or Builder and returns 403 PLAN_UPGRADE_REQUIRED on a Free key. See plans and limits for the route gating.
Two hooks make these TTLs self-tuning. Every payload carries cache_age_seconds (0 means freshly computed), so you can see how warm the served data already was. And every response carries freshness: if it comes back stale or degraded, shorten your own TTL or show the caveat rather than caching it long.
Cache keys
Key by route plus normalized params. A snapshot key is as simple as:series, form, fields), and apply defaults so range=1y and an omitted range hit the same entry. A history key with sorted series looks like nox:history:AAPL:range=1y:series=close,rsi_14. The REST filings param is form (comma-joined, e.g. form=10-K,10-Q); the MCP filings_list tool calls the same argument forms (an array).
If two plans or keys can see different data or quotas, scope the key with
nox:snapshot:AAPL:builder or a per-key hash. A shared cache that ignores plan scope can serve a Free-route miss as a hit, or leak one tenant’s warmer data to another.Retries and rate limits
Wrap every call in the retry loop that honorsRetry-After, caps attempts, and retries only retryable: true. The code lives in errors and retries; use it directly rather than reimplementing it here.
Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix epoch seconds); watch Remaining against Limit to back off before you hit a 429. The per-plan minute and quota numbers live on plans and limits.
SEC routes need a loading state
Filing and insider routes read from EDGAR. A cold fetch for a heavy filer can be slow, so do not block a page render on it. Run those routes in a background job or behind a loading state, and cache the result hard, because filing section text is effectively immutable.Preserve the honest fields
Whatever your UI shows, carry these through from the response so the answer stays truthful:freshness,as_of,market_status, andcache_age_seconds.reasonandavailable: falseon unsupported families,fields_omitted_by_symbolfrom compare, andsummary_90d.noticesfrom insider rollups.- Units and signs:
_usd,_pct, share counts, drawdowns, cash-flow signs, insider net values.
stale, degraded, or unsupported state makes the number look more certain than it is. Surface it.
Batch workflows
Run many symbols with bounded concurrency and no bulk endpoint.
Plans and limits
Per-plan minute limits, monthly quotas, and route gating.