Skip to content

Requests and responses

View as Markdown
https://belfort-data-api.vercel.app

Every route is a GET. Send your key in the X-API-Key header on every route except /v1/status, which needs none:

Terminal window
curl -H "X-API-Key: $BELFORT_API_KEY" https://belfort-data-api.vercel.app/v1/companies/MSFT

Company routes take an {id} in the path. It can be:

  • a ticker, in any case. BRK.B and BRK-B reach the same company.
  • a CIK, the SEC’s company number, with or without leading zeros.

A filer with no ticker still works by CIK. A ticker reaches exactly one CIK; for the few companies that reorganized under a new CIK, see Company.

Every successful answer has the same two parts:

{
"data": "...",
"meta": { "snapshot_id": "...", "as_of": "...", "next_cursor": null }
}
  • data is the answer: one object, or a list.
  • meta.snapshot_id names the load of data that answered you.
  • meta.as_of is when the source data was published. Quote it with any figure you use.
  • meta.next_cursor is the key to the next page, or null on the last page and on routes that don’t page.

Values are as filed. A blank in a filing is null, never 0.

Errors come back as { "error": { "code": "...", "message": "..." } }. The message says what to fix.

HTTP code What happened
400 bad_request The request is malformed, or has a query parameter the route doesn’t take.
401 unauthorized The key is missing, unknown or revoked.
404 not_found No company matches this ticker or CIK.
429 rate_limited Your key is over its per-minute limit. Wait the seconds in Retry-After.
500 internal Something failed on our side.
503 internal The database can’t be reached, or this dataset isn’t loaded yet. Wait the seconds in Retry-After.

Unknown query parameters are refused on purpose, so a typo like frequncy=annual fails loudly instead of quietly returning everything.

Each key has a per-minute limit. The limit is counted by each server instance on its own, so it isn’t a hard global cap: a 429 means the instance you reached is over your limit, and another instance may still answer. Please treat your agreed limit as the rate you send. Usage is counted across all instances.

Routes that return long lists page them. Pass limit to set the page size and, for the next page, pass meta.next_cursor back as cursor with the same filters. Stop when next_cursor is null.

Terminal window
curl -H "X-API-Key: $BELFORT_API_KEY" \
"https://belfort-data-api.vercel.app/v1/companies/AAPL/statements?statement=IS&limit=4"
# then, with the cursor from that answer:
curl -H "X-API-Key: $BELFORT_API_KEY" \
"https://belfort-data-api.vercel.app/v1/companies/AAPL/statements?statement=IS&limit=4&cursor=NEXT_CURSOR"

A page can hold fewer items than limit even when more remain, so always follow next_cursor rather than counting items. A cursor only works with the filters it was issued for.

Route limit Default Pages with cursor
/v1/companies/{id}/statements 1–20 4 Yes
/v1/companies/{id}/financials 1–100 40 Yes
/v1/companies/{id}/segments 1–50 20 Yes
/v1/companies/{id}/holders 1–100 25 Yes
/v1/companies/{id}/insider-trades 1–50 20 Yes
/v1/companies/{id}/form13f 1–50 20 Yes
/v1/companies/{id}/filings 1–50 20 Yes
/v1/companies/{id}/earnings-releases 1–100 20 Yes
/v1/companies/{id}/ownership 1–100 20 No

Earnings releases also take from and to, inclusive filed-date bounds in YYYY-MM-DD form.

/v1/status, /v1/companies/{id}, /v1/companies/{id}/metrics and /v1/macro/{series} return everything in one answer.

/v1/status reports, for each dataset, when its source was published (as_of), when we loaded it (loaded_at) and its age in hours. A dataset that isn’t loaded yet reads null, and its route answers 503 until it is. How often each source updates is on Where the data comes from.