diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bd99cb..e4fa5ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## v.Next
+
+### Packaging
+
+- Ship a `py.typed` marker (PEP 561) so downstream type checkers consume the package's type hints (#110)
+
## 0.16.0 - July 7, 2026
### New Features
diff --git a/README.md b/README.md
index 746c727..7896ef1 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,34 @@
-# GridStatus.io Hosted API
+# GridStatus.io Hosted API — Python Client
-* Python client for accessing the [GridStatus.io Hosted API](https://www.gridstatus.io/api).
-* Browse all available datasets in our [Data Catalog](https://www.gridstatus.io/datasets).
+`gridstatusio` is a Python client for the [GridStatus.io Hosted API](https://www.gridstatus.io/api), which provides historical and real-time electricity market data from North American ISOs through a single REST API.
+Available datasets cover load and demand, fuel and generation mix, forecasts, locational marginal prices (LMPs), interchange, ancillary services, and more. Browse 500+ datasets in the [Data Catalog](https://www.gridstatus.io/datasets).
+
+## Contents
+
+- [Why the hosted API?](#why-the-hosted-api)
+- [Installation](#installation)
+- [Getting started](#getting-started)
+- [Return formats](#return-formats)
+- [API usage](#checking-your-api-usage)
+- [More examples](#more-examples)
+
+## Why the hosted API?
+
+The hosted API differs from the open-source [`gridstatus`](https://github.com/gridstatus/gridstatus) library in several ways:
+
+| Hosted API | Open-source `gridstatus` |
+|------------|--------------------------|
+| Consistent column names, timestamp formats, and DST handling | Raw data directly from ISO sources |
+| Single REST API | Source-specific integrations |
+| Historical data queryable immediately | Historical availability depends on each source's retention policy |
+| Consistent server-side filtering by time, columns, and row values | Filtering capabilities vary by source |
+| Support included with paid subscriptions | Community support |
+
+Use the open-source library when you want raw data directly from the ISOs with no account. We recommend this client when you want normalized, hosted data through a single API.
## Installation
@@ -34,22 +57,31 @@ uv pip install gridstatusio[notebooks]
uv pip install gridstatusio[all]
```
-## Getting Started
+## Getting started
-* Sign up for a Grid Status account and get your API key from the [Settings page](https://www.gridstatus.io/settings/api)
-* Set your API key as an environment variable: `export GRIDSTATUS_API_KEY=your_api_key` or pass to the client with `client = GridStatusClient(api_key="")`
-* You're now ready to start querying. List datasets with:
+1. **Get an API key.** Sign up for a Grid Status account and copy your key from the [Settings page](https://www.gridstatus.io/settings/api).
+2. **Provide the key.** Set `export GRIDSTATUS_API_KEY=your_api_key`, or pass it directly: `GridStatusClient(api_key="")`.
+3. **Find a dataset.** Call `client.list_datasets()` or browse the [Data Catalog](https://www.gridstatus.io/datasets).
+4. **Query the dataset.** Call `client.get_dataset(...)` with the dataset ID and desired time range.
```python
from gridstatusio import GridStatusClient
-client = GridStatusClient()
-data = client.get_dataset('ercot_fuel_mix', limit=100, start='2025-01-01', end='2025-01-02')
+client = GridStatusClient() # reads GRIDSTATUS_API_KEY from the environment
+
+client.list_datasets()
+
+df = client.get_dataset(
+ "ercot_fuel_mix",
+ start="2024-06-01",
+ end="2024-06-02",
+ limit=1000,
+)
```
-* To see all available datasets, use `client.list_datasets()` or check out the complete Grid Status catalog at https://www.gridstatus.io/datasets
+### Dataset metadata
-* To get metadata for a single dataset (description, available time range, columns, and more), use `client.get_dataset_metadata(dataset_id)`. It always returns a dictionary, with timestamp fields parsed into timezone-aware datetimes:
+Use `client.get_dataset_metadata(dataset_id)` to get a dataset's description, available time range, columns, and more. It always returns a dictionary, with timestamp fields parsed into timezone-aware datetimes:
```python
metadata = client.get_dataset_metadata("ercot_fuel_mix")
@@ -63,21 +95,9 @@ metadata = client.get_dataset_metadata("ercot_fuel_mix")
# }
```
-* **NOTE**: the Grid Status API has a 500,000 rows per month limit on the free plan. This limit is _very_ easy to exceed when querying data, especially real time prices.
- * Make sure to add `limit` to all of your `get_dataset` calls to avoid quickly exceeding the limit.
+## Return formats
-
-* For more detailed examples, check out this notebook: [Getting Started](Examples/1.%20Getting%20Started.ipynb)
-* Other notebooks in the [Examples](Examples) directory:
- - [Finding Hubs and Zones in Pricing Data](Examples/2.%20ISO%20Hubs.ipynb)
- - [ERCOT Pricing Data](Examples/3.%20ERCOT%20Pricing%20Data.ipynb)
- - [CAISO April Net Load Analysis](Examples/4.%20CAISO%20April%20Net%20Load.ipynb)
- - [Stacked Net Load Visualization](Examples/5.%20Stacked%20Net%20Load%20Visualization.ipynb)
- - [Resample Data to Different Frequencies](Examples/6.%20Resampling%20Data.ipynb)
-
-## Return Formats
-
-The client supports three return formats for data: **pandas DataFrames**, **polars DataFrames**, and **Python objects** (list of dictionaries). You can specify the format at the client level or per-call.
+`get_dataset(...)` supports pandas, polars, or Python objects. Set the return format at the client level or per-call. Dataset metadata is always returned as a dictionary.
```python
from gridstatusio import GridStatusClient
@@ -86,74 +106,43 @@ from gridstatusio import GridStatusClient
client = GridStatusClient(return_format="pandas") # or "polars" or "python"
# Override format for a specific call
-data = client.get_dataset('ercot_fuel_mix', limit=100, return_format="python")
+data = client.get_dataset("ercot_fuel_mix", limit=100, return_format="python")
```
-### Format Options
-
| Format | Return Type | Description |
|--------|------------|-------------|
| `"pandas"` | `pd.DataFrame` | Pandas DataFrame with parsed datetime columns |
| `"polars"` | `pl.DataFrame` | Polars DataFrame with parsed datetime columns |
| `"python"` | `list[dict]` | List of dictionaries with parsed datetime columns |
-### Default Behavior
-
If `return_format` is not specified, the client returns **pandas DataFrames** by default.
-### Example: Python Format
-
```python
-from gridstatusio import GridStatusClient
-
-client = GridStatusClient(return_format="python")
-data = client.get_dataset('ercot_fuel_mix', limit=5)
-
-# Returns a list of dictionaries
+# Python format → list of dicts:
# [
# {"interval_start_utc": "2025-01-01T00:00:00+00:00", "coal": 1234.5, ...},
# {"interval_start_utc": "2025-01-01T00:05:00+00:00", "coal": 1235.2, ...},
-# ...
# ]
```
-### Example: Polars Format
+### Using without pandas (advanced)
-```python
-from gridstatusio import GridStatusClient
-
-client = GridStatusClient(return_format="polars")
-df = client.get_dataset('ercot_fuel_mix', limit=100)
-
-# Returns a polars DataFrame
-print(type(df)) #
-```
-
-### Using Without Pandas (Advanced)
-
-While pandas is a required dependency, the library uses lazy loading so pandas is only imported when actually needed. This allows advanced users to use the library without pandas in minimal environments:
+Pandas is installed by default, but the library uses lazy loading so it's only imported when needed. In minimal environments, you can install without dependencies and use `return_format="python"` to avoid pandas entirely:
```bash
-# Install without dependencies (advanced usage only)
uv pip install gridstatusio --no-deps
-
-# Then manually install only the required non-pandas dependencies
uv pip install requests termcolor tabulate
```
-When using the library without pandas:
-
```python
from gridstatusio import GridStatusClient
-# Must explicitly set return_format="python" to avoid pandas import
+# Must explicitly set return_format="python" to avoid the pandas import
client = GridStatusClient(api_key="your_key", return_format="python")
-data = client.get_dataset('ercot_fuel_mix', limit=100)
-
-# Returns list of dicts - no pandas required
+data = client.get_dataset("ercot_fuel_mix", limit=100)
```
-Note: If you don't specify `return_format="python"`, the client will attempt to use pandas and raise an error if it's not installed.
+If you don't set `return_format="python"`, the client attempts to use pandas and raises an error if it isn't installed.
## Checking your API usage
@@ -161,12 +150,13 @@ Note: If you don't specify `return_format="python"`, the client will attempt to
usage = client.get_api_usage()
```
-* This shows the limits for your API key, the start and end of the current usage period, and the API usage in the current period. Note a limit of -1 means no limit.
+Shows the limits for your API key, the start/end of the current usage period, and usage in the current period. A limit of `-1` means no limit.
-## Retry Configuration
+The free plan allows 500,000 rows per month. You can view detailed usage stats in [Settings](https://www.gridstatus.io/settings/usage).
-* The Grid Status API has rate limits that restrict the number of requests that are allowed each second, minute and hour. If rate limits are hit the client will automatically retry the request after a delay. You can configure the maximum number of retries using the `max_retries` parameter when initializing the client. If you find yourself hitting rate limits, you may need to add a delay between your requests. The [Grid Status Pricing Page](https://www.gridstatus.io/pricing) contains more details on specific rate limits.
-* The client retries failed requests due to rate limits (429), server errors (5xx), and network issues using exponential backoff. You can customize retry behavior:
+## Retry configuration
+
+The API enforces per-second/minute/hour rate limits. The client retries rate limits (429), server errors (5xx), and network issues with exponential backoff. See the [Pricing Page](https://www.gridstatus.io/pricing) for specific limits.
```python
client = GridStatusClient(
@@ -176,28 +166,31 @@ client = GridStatusClient(
)
```
-The retry delay follows the formula `delay = base_delay * (exponential_base ** retry_count)`.
-
-Retries are useful when:
+Set `max_retries=0` to disable retries.
-* You're making pagination-heavy requests and risk hitting short-term rate limits
-* A request fails due to a temporary server error
-* A network issue or timeout interrupts the request
+## Version check
-To disable retries entirely, set `max_retries=0`.
-
-## Version Check
-
-The client checks for updates to the library when it is imported. It does this by making a call to the [library on PyPI](https://pypi.org/project/gridstatusio/). For certain applications, this call can be problematic, and can be disabled by setting the environment variable `GSIO_SKIP_VERSION_CHECK` to `true`.
+The client checks PyPI for library updates on import. To disable this (e.g. in restricted environments), set:
```bash
export GSIO_SKIP_VERSION_CHECK=true
```
-## Open Source
+## More examples
+
+- [Getting Started](Examples/1.%20Getting%20Started.ipynb)
+- [Finding Hubs and Zones in Pricing Data](Examples/2.%20ISO%20Hubs.ipynb)
+- [ERCOT Pricing Data](Examples/3.%20ERCOT%20Pricing%20Data.ipynb)
+- [CAISO April Net Load Analysis](Examples/4.%20CAISO%20April%20Net%20Load.ipynb)
+- [Stacked Net Load Visualization](Examples/5.%20Stacked%20Net%20Load%20Visualization.ipynb)
+- [Resample Data to Different Frequencies](Examples/6.%20Resampling%20Data.ipynb)
+
+## Resources
-If you prefer to use an open source library that fetches data directly from the source, you can check out this [github repo](https://github.com/gridstatus/gridstatus).
+- [OpenAPI spec](https://api.gridstatus.io/openapi.json)
+- [LLMs manifest](https://gridstatus.io/llms.txt)
+- [Docs assistant](https://docs.gridstatus.io/)
-## Get Help
+## Get help
-We'd love to answer any usage or data access questions! Please let us know by emailing us at contact@gridstatus.io
+For usage or data-access questions, email contact@gridstatus.io.
diff --git a/gridstatus-header.png b/gridstatus-header.png
index 7eabb95..aa49770 100644
Binary files a/gridstatus-header.png and b/gridstatus-header.png differ
diff --git a/gridstatusio/py.typed b/gridstatusio/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/pyproject.toml b/pyproject.toml
index 2b087e1..9960d11 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "gridstatusio"
version = "0.16.0"
-description = "Python Client for GridStatus.io API"
+description = "Python client for the GridStatus.io API: normalized electricity market data (load, fuel mix, LMPs, forecasts) from every North American ISO through a single REST API."
authors = [{ name = "Max Kanter", email = "kmax12@gmail.com" }]
requires-python = ">=3.10, <4"
readme = "README.md"
@@ -9,9 +9,36 @@ license.file = "LICENSE"
maintainers = [{ name = "Max Kanter", email = "kmax12@gmail.com" }]
keywords = [
"energy",
+ "electricity",
+ "power grid",
"independent system operator",
+ "ISO",
+ "RTO",
+ "CAISO",
+ "ERCOT",
+ "PJM",
+ "MISO",
+ "SPP",
+ "NYISO",
+ "ISO-NE",
+ "IESO",
+ "AESO",
+ "EIA",
+ "LMP",
+ "electricity prices",
+ "energy markets",
+ "time series",
+ "grid data",
+ "API client",
]
classifiers = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Science/Research",
+ "Intended Audience :: Financial and Insurance Industry",
+ "Topic :: Scientific/Engineering :: Information Analysis",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
@@ -48,8 +75,12 @@ all = [
]
[project.urls]
-"Source Code" = "https://github.com/kmax12/gridstatusio/"
-"Issue Tracker" = "https://github.com/kmax12/gridstatusio/issues"
+"Homepage" = "https://www.gridstatus.io/"
+"Documentation" = "https://www.gridstatus.io/api"
+"Data Catalog" = "https://www.gridstatus.io/datasets"
+"Source Code" = "https://github.com/gridstatus/gridstatusio/"
+"Changes" = "https://github.com/gridstatus/gridstatusio/blob/main/CHANGELOG.md"
+"Issue Tracker" = "https://github.com/gridstatus/gridstatusio/issues"
[tool.uv]
dev-dependencies = [