The stockrow API
Up to ten years of financial statements, metrics and ratios, daily prices, dividends, insider trades and screens for US-listed companies — the figures on stockrow.com — as JSON or as CSV a spreadsheet can read directly. It is read-only, and part of Powerpack.
Using a spreadsheet? Google Sheets & Excel recipes are copy-and-paste. Using Claude, ChatGPT or Cursor? Connect the MCP server.
Quick start
curl -H "Authorization: Bearer $STOCKROW_KEY" \
https://stockrow.com/api/v1/companies/AAPL/statements/income-statement.csv?period=annual
code,label,unit,2025-09-27,2024-09-28,2023-09-30,…
REVENUE,Revenue,cash,416161000000,391035000000,383285000000,…
NETINC,Net Income,cash,112010000000,93736000000,96995000000,…
Authentication
Make a key on your account’s API page. Send it as a header,
Authorization: Bearer sr_live_…, or — where a header cannot be sent, as in Google Sheets’
IMPORTDATA — as the key parameter: ?key=sr_live_…. A key can read your
data feed, your watchlist and your saved screens, and nothing else: never your account, your billing or your
email. If one is shared by mistake, roll it on the API page and the old one stops at once.
Every request checks the key’s account. If Powerpack lapses, requests answer 402 until it is
renewed, and then the same key works again; keys are never deleted with a subscription.
Formats and units
End a path with .json or .csv; with neither, the answer is JSON unless the request
asks for text/csv. JSON is {"data": …, "meta": …}, where meta says where the
figures come from, the page on stockrow.com with the same figures (url) and how current they are
(as_of). CSV is UTF-8 with a header row.
- Cash is in US dollars as raw figures — not thousands, not millions.
- Percentages and growth rates are fractions:
0.05is 5%. - Booleans are
1or0. A missing value isnull, an empty cell in CSV. - Dates are
YYYY-MM-DD. Periods run newest first unless you ask fororder=asc. - Statements go back up to ten years, annual, quarterly and trailing twelve months.
- Tickers are accepted in any case,
BRK.Bincluded.
Errors
Anything that is not a success is JSON {"error": {"code", "message", "docs"}} — or, for a CSV
request, two lines, error,message and the two values.
| Status | Code | Means |
|---|---|---|
| 400 | invalid_parameter | A parameter is missing or out of range; the message names it. |
| 401 | missing_key, invalid_key | No key, or one that was revoked or rolled. |
| 402 | subscription_required | The key’s account has no active Powerpack. |
| 404 | not_found | No such ticker, metric, statement or screen of yours. |
| 406 | unsupported_format | A suffix other than .json or .csv. |
| 429 | rate_limited | Over the limits below; Retry-After says when to try again. |
| 500 | internal | Our fault; it is logged. |
Limits
60 requests a minute and
5,000 a day for each key, the API and the
MCP server together. Every response says what is left: RateLimit-Limit,
RateLimit-Remaining and RateLimit-Reset (seconds) for the minute, and
X-RateLimit-Remaining-Day. A spreadsheet of fifty formulas refreshing every hour uses about
1,200 a day. To fetch many companies at once, use /metrics/latest: up to 100 tickers and 25 metrics
in one request. /status shows a key’s allowance without costing much of it.
Statements and the latest-values grid send an ETag; send it back as If-None-Match and an
unchanged answer is a 304 with no body.
Versioning
/api/v1 only grows: new fields, new CSV columns added on the right, new endpoints. A field is never
renamed, removed or reordered, and a unit or a code never changes. A change that would break that is
/api/v2, and v1 then keeps working for at least twelve months, with Deprecation and
Sunset headers and an email to everyone with an active key.
Reference
Base URL https://stockrow.com/api/v1. The same reference as OpenAPI 3.1:
openapi.yaml.
Companies
GET /companies/search — Find companies by ticker or name
| Parameter | In | Takes | Notes |
|---|---|---|---|
q (required) |
query | string | A ticker or part of a company's name. |
limit |
query |
1 to 20
— default 10
|
GET /companies/{ticker}/profile — A company's name, exchange, sector and industry, with its latest price and market cap
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
GET /companies/{ticker}/statements/{statement} — One financial statement, up to ten years
Wide by default: a row per line item with its stable code, label and unit, then a column per period end, newest first (column D in a spreadsheet is always the latest period). layout=long is a row per cell instead.
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
statement (required) |
path |
income-statement, balance-sheet, cash-flow, metrics-ratios
|
|
period |
query |
annual, quarterly, ttm
— default annual
|
The balance sheet has no ttm. |
limit |
query | 1 to 40 | Periods; 10 by default for annual, 12 otherwise. |
order |
query |
desc, asc
— default desc
|
|
layout |
query |
wide, long
— default wide
|
GET /companies/{ticker}/prices — Daily open, high, low, close and volume, newest first
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
from |
query | date | |
to |
query | date |
GET /companies/{ticker}/dividends — Dividends over the last ten years, newest first
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
limit |
query | 1 to 200 |
GET /companies/{ticker}/insider-transactions — Insider buys and sells, newest first
value is the trade in dollars, owned_after the shares the insider holds after it.
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
type |
query |
buy, sell
|
|
limit |
query |
1 to 200
— default 50
|
Metrics
GET /companies/{ticker}/metrics/{slug} — One metric's history for a company
slug is a metric slug such as pe-ratio, revenue or free-cash-flow, or any indicator's lower-cased code; /indicators lists them.
| Parameter | In | Takes | Notes |
|---|---|---|---|
ticker (required) |
path | string | The ticker, in any case (aapl, BRK.B). |
slug (required) |
path | string | |
period |
query |
annual, quarterly, ttm, daily, latest
|
Defaults to the metric's own; only the periods it comes in are accepted. |
limit |
query | 1 to 120 | |
from |
query | date | |
to |
query | date |
GET /metrics/latest — The latest value of up to 25 metrics for up to 100 companies, as one grid
A row per ticker in the order asked for. A ticker stockrow does not cover gets a row of empty cells rather than an error, and is named in meta.unknown_tickers.
| Parameter | In | Takes | Notes |
|---|---|---|---|
tickers (required) |
query | string | Comma-separated, at most 100. |
metrics (required) |
query | string | Comma-separated metric slugs, at most 25. |
Your account
GET /watchlist — The companies you follow, with the day's price and change
| Parameter | In | Takes | Notes |
|---|---|---|---|
metrics |
query | string | Optional comma-separated metric slugs to add as columns, at most 25. |
GET /screens — Your saved screens
GET /screens/{id}/results — The companies one of your saved screens matches today, with its columns
| Parameter | In | Takes | Notes |
|---|---|---|---|
id (required) |
path | uuid | |
limit |
query |
1 to 1000
— default 500
|
|
offset |
query |
integer
— default 0
|
Reference
GET /indicators — The data dictionary — every metric and line item, with its slug, unit, periods and meaning
| Parameter | In | Takes | Notes |
|---|---|---|---|
tier |
query |
core, advanced
|
|
q |
query | string |
GET /status — Your key, your plan, and the requests you have left