Welcome to the official Python client library for the Massive REST and WebSocket API. To get started, please see the Getting Started section in our documentation, view the examples directory for code snippets.
Note: Polygon.io has rebranded as Massive.com on Oct 30, 2025. Existing API keys, accounts, and integrations continue to work exactly as before. The only change in this SDK is that it now defaults to the new API base at api.massive.com, while api.polygon.io remains supported for an extended period.
For details, see our rebrand announcement blog post or open an issue / contact support@massive.com if you have questions.
Before installing the Massive Python client, ensure your environment has Python 3.10 or higher.
Please use pip to install or update to the latest stable version.
pip install -U massive
To get started, please see the Getting Started section in our docs, view the examples directory for code snippets.
The free tier of our API comes with usage limitations, potentially leading to rate limit errors if these are exceeded. For uninterrupted access and to support larger data requirements, we recommend reviewing our subscription plans, which are tailored for a wide range of needs from development to high-demand applications. Refer to our pricing page for detailed information on limits and features to ensure a seamless experience, especially for real-time data processing.
Coding with Claude, Cursor, ChatGPT, or another AI assistant? Give it direct access to Massive data and docs:
- MCP server: connect your MCP client to the hosted server at
https://mcp.massive.com/. See the AI Tools quickstart for setup in each client. - llms.txt: point your agent at
https://massive.com/docs/llms.txtfor an LLM-friendly index of the Massive docs. - Skills plugin: install the Massive plugin from massive-com/ai to teach your assistant how to use the API correctly: hosts, auth, entitlements, and accurate historical data. In Claude Code, run
/plugin marketplace add massive-com/ai, then/plugin install massive@massive-plugins.
Import the RESTClient.
from massive import RESTClientCreate a new client with your API key
client = RESTClient(api_key="<API_KEY>")Request data using client methods.
ticker = "AAPL"
# List Aggregates (Bars)
aggs = []
for a in client.list_aggs(ticker=ticker, multiplier=1, timespan="minute", from_="2023-01-01", to="2023-06-13", limit=50000):
aggs.append(a)
print(aggs)
# Get Last Trade
trade = client.get_last_trade(ticker=ticker)
print(trade)
# List Trades
trades = client.list_trades(ticker=ticker, timestamp="2022-01-04")
for trade in trades:
print(trade)
# Get Last Quote
quote = client.get_last_quote(ticker=ticker)
print(quote)
# List Quotes
quotes = client.list_quotes(ticker=ticker, timestamp="2022-01-04")
for quote in quotes:
print(quote)By default, the client paginates results for endpoints like list_trades and list_quotes behind the scenes for you. Understanding how pagination interacts with the limit parameter is key.
Pagination is enabled by default (pagination=True):
limitcontrols the page size, not the total number of results.- The client automatically fetches all pages, yielding results until none remain.
Here's an example:
client = RESTClient(api_key="<API_KEY>")
trades = [t for t in client.list_trades(ticker="TSLA", limit=100)]This fetches all TSLA trades, 100 per page.
To return a fixed number of results and stop, disable pagination:
client = RESTClient(api_key="<API_KEY>", pagination=False)
trades = [t for t in client.list_trades(ticker="TSLA", limit=100)]This returns at most 100 total trades, no additional pages.
If you're fetching large datasets, always use the maximum supported limit for the API endpoint. This reduces the number of API calls and improves overall performance.
Many of the APIs in this client library support the use of additional filter parameters to refine your queries. Please refer to the specific API documentation for details on which filter parameters are supported for each endpoint. These filters can be applied using the following operators:
.gt: greater than.gte: greater than or equal to.lt: less than.lte: less than or equal to
Here's a sample code snippet that demonstrates how to use these filter parameters when requesting an Options Chain using the list_snapshot_options_chain method. In this example, the filter parameters ensure that the returned options chain data will only include options with an expiration date that is greater than or equal to "2024-03-16" and a strike price that falls between 29 and 30 (inclusive).
options_chain = []
for o in client.list_snapshot_options_chain(
"HCP",
params={
"expiration_date.gte": "2024-03-16",
"strike_price.gte": 29,
"strike_price.lte": 30,
},
):
options_chain.append(o)
print(options_chain)
print(len(options_chain))Also, please refer to the API documentation to get a full understanding of how the API works and the supported arguments. All required arguments are annotated with red asterisks " * " and argument examples are set.
Options for corporate networks, proxies, and multithreaded use. They apply to both RESTClient and WebSocketClient unless noted.
If you're behind a corporate firewall that inspects TLS traffic, requests can fail with SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed. Point the client at your company's CA bundle.
With environment variables (no code changes):
export SSL_CERT_FILE=/path/to/corporate-ca-bundle.pem
# or, if you already set this for the requests library:
export REQUESTS_CA_BUNDLE=/path/to/corporate-ca-bundle.pemIn code:
from massive import RESTClient, WebSocketClient
client = RESTClient(ca_certs="/path/to/corporate-ca-bundle.pem")
ws_client = WebSocketClient(ca_certs="/path/to/corporate-ca-bundle.pem")The client uses the first one it finds: ca_certs, then SSL_CERT_FILE, then REQUESTS_CA_BUNDLE, then the bundled certifi certificates. If an environment variable points to a file that doesn't exist, it's skipped and a warning is logged.
The bundle replaces certifi, so it must also include the public root certificates. If you only have your company's root certificate, add it to certifi's bundle:
cat "$(python -m certifi)" corporate-root.pem > corporate-ca-bundle.pemREST: pass the proxy URL. RESTClient doesn't read HTTPS_PROXY on its own, so existing setups keep connecting the same way.
import os
from massive import RESTClient
client = RESTClient(proxy="http://proxy.example.com:8080")
# with a username and password
client = RESTClient(proxy="http://user:password@proxy.example.com:8080")
# use the proxy from your environment, like the requests library does
client = RESTClient(proxy=os.environ.get("HTTPS_PROXY"))WebSocket: WebSocketClient already uses the proxy from HTTPS_PROXY / HTTP_PROXY (and respects NO_PROXY). To set one explicitly, or to turn it off, pass proxy to run() or connect():
ws_client = WebSocketClient(subscriptions=["T.AAPL"])
ws_client.run(handle_msg, proxy="http://user:password@proxy.example.com:8080")
# ws_client.run(handle_msg, proxy=None) # connect directly, ignoring HTTPS_PROXYBehind a proxy that inspects TLS, you'll usually need both:
client = RESTClient(
proxy="http://proxy.example.com:8080",
ca_certs="/path/to/corporate-ca-bundle.pem",
)RESTClient can be shared across threads. It keeps up to maxsize connections (default 10) open for reuse, so set it to at least the number of threads making requests at the same time. Otherwise, extra threads open new connections and redo the TLS handshake on most requests.
from concurrent.futures import ThreadPoolExecutor
from massive import RESTClient
client = RESTClient(maxsize=20)
tickers = ["AAPL", "MSFT", "NVDA", "AMZN"]
with ThreadPoolExecutor(max_workers=20) as pool:
results = list(pool.map(lambda t: client.get_previous_close_agg(t), tickers))Note that num_pools doesn't help here: it's the number of different hosts the client keeps connections for, and the client only talks to one.
Any other urllib3 pool option can be passed with connection_pool_kw. These take precedence over the client's defaults, so avoid overriding headers, which carries your API key.
# wait for a free connection instead of opening extra ones beyond maxsize
client = RESTClient(maxsize=4, connection_pool_kw={"block": True})Sometimes you may find it useful to see the actual request and response details while working with the API. The RESTClient allows for this through its trace=True option.
You can activate the debug mode as follows:
client = RESTClient(trace=True, verbose=True)When debug mode is enabled, the client will print out useful debugging information for each API request. You need to enable verbose=True too so that you log the trace or already have your own logger configured. This includes: the request URL, the headers sent in the request, and the headers received in the response.
For instance, if you made a request for TSLA data for the date 2023-08-01, you would see debug output similar to the following:
Request URL: https://api.massive.com/v2/aggs/ticker/TSLA/range/1/minute/2023-08-01/2023-08-01?limit=50000
Request Headers: {'Authorization': 'Bearer REDACTED', 'Accept-Encoding': 'gzip', 'User-Agent': 'Massive.com PythonClient/1.12.4'}
Response Headers: {'Server': 'nginx/1.19.2', 'Date': 'Tue, 05 Sep 2023 23:07:02 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'X-Request-Id': '727c82feed3790b44084c3f4cae1d7d4', 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains'}
This can be an invaluable tool for debugging issues or understanding how the client interacts with the API.
Import classes
from massive import WebSocketClient
from massive.websocket.models import WebSocketMessage
from typing import ListCreate a new client with your API key and subscription options.
# Note: Multiple subscriptions can be added to the array
# For example, if you want to subscribe to AAPL and META,
# you can do so by adding "T.META" to the subscriptions array. ["T.AAPL", "T.META"]
# If you want to subscribe to all tickers, place an asterisk in place of the symbol. ["T.*"]
ws = WebSocketClient(api_key=<API_KEY>, subscriptions=["T.AAPL"])Create a handler function and run the WebSocket.
def handle_msg(msg: List[WebSocketMessage]):
for m in msg:
print(m)
ws.run(handle_msg=handle_msg)Check out more detailed examples here.
If you found a bug or have an idea for a new feature, please first discuss it with us by submitting a new issue. We will respond to issues within at most 3 weeks. We're also open to volunteers if you want to submit a PR for any open issues but please discuss it with us beforehand. PRs that aren't linked to an existing issue or discussed with us ahead of time will generally be declined.
If you use an AI coding agent to work on this repo, it should read AGENTS.md, which covers the project layout, build and test commands, and which files are generated.
If you plan to contribute by developing new features then you will need to install certain dependencies.
Poetry is a packaging and dependency manager for Python.
Installation instructions can be found on their website.
Once installed run poetry install to install the required dependencies. This step should be run after incorporating new upstream changes.
Our Makefile has the common operations needed when developing on this repo. Running tests and linting can both be
run through our Makefile. Just run make help to see the list of available commands.
If you're using pyenv to manage active Python versions then you might need to launch a Poetry shell before running
Make commands in order to actually use your chosen Python version. This is because Poetry uses the system Python version
by default.
poetry shell # start shell
poetry install # install deps
make test # run your make commandsThis client will attempt to follow the release cadence of our API.
When endpoints are deprecated and newer versions are added, the client will
maintain two methods in a backwards compatible way
(e.g. list_trades and list_trades_v4(...)).
When deprecated endpoints are removed from the API, we'll rename the versioned
method (e.g. list_trades_v4(...) -> list_trades(...)), remove the old method,
and release a new major version of the client.
The goal is to give users ample time to upgrade to newer versions of our API before we bump the major version of the client, and in general, we'll try to bundle breaking changes like this to avoid frequent major version bumps.
Exceptions to this are:
- Methods under
client.vx. These are expiremental. - Methods that start with
_*. We use these internally. - Type annotations. We may modify these based on our JSON responses.
- We may add model fields.