# Error Handling

## Overview

The GridStatus API uses standard HTTP status codes to indicate the success or failure of requests. Error responses include a JSON body with details about what went wrong.

## Error Response Format

All error responses follow this structure:

```json
{
    "detail": "Human-readable error message describing what went wrong."
}
```

## HTTP Status Codes

### Success Codes

| Code | Description       |
| ---- | ----------------- |
| 200  | Request succeeded |

### Client Error Codes (4xx)

| Code | Name                 | Description                                |
| ---- | -------------------- | ------------------------------------------ |
| 400  | Bad Request          | Invalid parameters or malformed request    |
| 401  | Unauthorized         | Missing or invalid API key                 |
| 403  | Forbidden            | Valid API key but insufficient permissions |
| 404  | Not Found            | Resource (dataset, column, etc.) not found |
| 422  | Unprocessable Entity | Query timeout or validation error          |
| 429  | Too Many Requests    | Rate limit or usage limit exceeded         |

### Server Error Codes (5xx)

| Code | Name                  | Description                    |
| ---- | --------------------- | ------------------------------ |
| 500  | Internal Server Error | Unexpected server error        |
| 502  | Bad Gateway           | Upstream service unavailable   |
| 503  | Service Unavailable   | Server temporarily unavailable |
| 504  | Gateway Timeout       | Request took too long          |

***

## Common Errors and Solutions

### 401 Unauthorized

**Cause:** Missing or invalid API key.

```json
{
    "detail": "API key is required."
}
```

**Solutions:**

* Verify your API key is correct
* Check that the API key is being passed correctly (header or query parameter)
* Ensure the API key hasn't been revoked

{% tabs %}
{% tab title="Shell" %}

```shell
# Correct: API key as query parameter
curl "https://api.gridstatus.io/v1/datasets?api_key=YOUR_API_KEY"

# Correct: API key as header
curl -H "x-api-key: YOUR_API_KEY" "https://api.gridstatus.io/v1/datasets"
```

{% endtab %}

{% tab title="Python" %}

```python
import gridstatusio as gs

# Ensure API key is set
client = gs.GridStatusClient(api_key="YOUR_API_KEY")

# Or via environment variable

# export GRIDSTATUS_API_KEY=your_api_key
client = gs.GridStatusClient()
```

{% endtab %}
{% endtabs %}

***

### 403 Forbidden

**Cause:** Your subscription doesn't include access to the requested resource or feature.

```json
{
    "detail": "Access to this dataset requires a paid subscription."
}
```

**Common scenarios:**

* Accessing a premium dataset on a free plan
* Using resampling without a paid subscription
* Accessing audit data without proper entitlements

**Solutions:**

* Upgrade your subscription plan
* Check which datasets are available on your plan
* Contact support if you believe you should have access

***

### 404 Not Found

**Cause:** The requested resource doesn't exist.

**Dataset not found:**

```json
{
    "detail": "Dataset invalid_dataset_id not found."
}
```

**Column not found:**

```json
{
    "detail": "Column not found."
}
```

**Solutions:**

* Verify the dataset ID is spelled correctly
* Use the list datasets endpoint to see available datasets
* Check that the column name exists using the dataset metadata endpoint

{% tabs %}
{% tab title="Shell" %}

```shell
# List available datasets
curl "https://api.gridstatus.io/v1/datasets?api_key=YOUR_API_KEY" | jq '.data[].id'

# Check columns in a dataset
curl "https://api.gridstatus.io/v1/datasets/ercot_fuel_mix?api_key=YOUR_API_KEY" | jq '.all_columns[].name'
```

{% endtab %}

{% tab title="Python" %}

```python
# List available datasets
datasets = client.list_datasets(return_list=True)
print([d['id'] for d in datasets])

# Get metadata for a specific dataset to see columns
metadata = client.get(
    f"{client.host}/datasets/ercot_fuel_mix",
    return_raw_response_json=True
)
columns = [col['name'] for col in metadata['all_columns']]
print(f"Available columns: {columns}")
```

{% endtab %}
{% endtabs %}

***

### 422 Unprocessable Entity - Query Timeout

**Cause:** The query took too long to execute and was cancelled.

```json
{
    "detail": "Query timed out. The following may allow the query to complete within the timeout: Reduce the time range of the query. Remove resampling. Use '=' instead of other comparison operators in filters. Set a limit on the number of rows returned. If none of these options work for you our Snowflake offering may meet your needs. Please contact us at contact@gridstatus.io for more information."
}
```

**Solutions:**

{% stepper %}
{% step %}

### Reduce time range

Example: Instead of querying all locations, filter to the specific location you need.

{% tabs %}
{% tab title="Shell" %}

```shell
curl "https://api.gridstatus.io/v1/datasets/caiso_lmp_real_time_5_min/query?\
start_time=2026-01-01&\
end_time=2026-01-02&\
filter_column=location&\
filter_value=TH_SP15_GEN-APND&\
api_key=YOUR_API_KEY"
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add filters

Filter to specific locations instead of all locations.

{% tabs %}
{% tab title="Shell" %}

```shell
curl "https://api.gridstatus.io/v1/datasets/ercot_lmp_by_settlement_point/query?\
start_time=2026-01-01&\
end_time=2026-01-02&\
filter_column=location&\
filter_value=HB_HOUSTON&\
api_key=YOUR_API_KEY"
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Set a limit

Limit the number of rows returned.

{% tabs %}
{% tab title="Shell" %}

```shell
curl "https://api.gridstatus.io/v1/datasets/pjm_lmp_real_time_5_min/query?\
start_time=2026-01-01&\
limit=100&\
api_key=YOUR_API_KEY"
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Remove resampling

Server-side resampling can be expensive. Consider downloading raw data and resampling client-side.
{% endstep %}
{% endstepper %}

***

### 429 Too Many Requests

**Cause:** You've exceeded rate limits or monthly usage limits.

**Rate limit exceeded:**

```json
{
    "detail": "Rate limit exceeded. Try again in 60 seconds."
}
```

**Monthly limit exceeded:**

```json
{
    "detail": "Monthly row limit exceeded. Limit resets on 2026-02-01."
}
```

**Solutions:**

* Implement exponential backoff

{% tabs %}
{% tab title="Python" %}

```python
# Configure the client with retry settings
retry_client = gs.GridStatusClient(
    api_key="YOUR_API_KEY",
    max_retries=5,      # Retry up to 5 times
    base_delay=2.0      # Start with 2 second delay
)

# Queries will automatically retry on 429 errors
df = retry_client.get_dataset("ercot_fuel_mix", start="2026-01-01", end="2026-01-02")
```

{% endtab %}
{% endtabs %}

* Check usage before large queries

{% tabs %}
{% tab title="Python" %}

```python

usage = client.get_api_usage()

rows_used = usage['current_period_usage']['total_api_rows_returned']
rows_limit = usage['limits']['api_rows_returned_limit']
rows_remaining = rows_limit - rows_used

if rows_remaining < 10000:
    print("Warning: Low quota remaining!")
```

{% endtab %}
{% endtabs %}

* Upgrade your plan if you consistently hit limits.

***

### 400 Bad Request

**Cause:** Invalid parameter values or missing required parameters.

**Invalid column:**

```json
{
    "detail": "Column invalid_column not found in dataset. Possible columns: ['interval_start_utc', 'location', 'lmp']."
}
```

**Invalid filter type:**

```json
{
    "detail": "Invalid type for filter_value, expected <class 'float'>."
}
```

**Invalid resample frequency:**

```json
{
    "detail": "Invalid resample_frequency: '2h'. Must be one of: 5min, 15min, 30min, 1h, 1d, 1w, 1M."
}
```

**Solutions:**

* Check the error message for valid options
* Use the dataset metadata endpoint to verify column names
* Review the query parameters documentation

***

## Error Handling Best Practices

{% stepper %}
{% step %}

### Use Try/Except with the Client

{% tabs %}
{% tab title="Python" %}

```python
try:
    df = client.get_dataset("ercot_fuel_mix", limit=10)
    print(f"Success: {len(df)} rows")
except Exception as e:
    print(f"Error: {e}")
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Implement Retry Logic

{% tabs %}
{% tab title="Python" %}

```python
# Configure the client with built-in retry logic
retry_client = gs.GridStatusClient(
    api_key="YOUR_API_KEY",
    max_retries=3,       # Number of retries
    base_delay=2.0,      # Initial delay in seconds
    exponential_base=2   # Multiply delay by this each retry
)

# All queries will automatically retry on transient errors (429, 500, 502, 503, 504)
df = retry_client.get_dataset("ercot_fuel_mix", limit=10)
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Log Errors for Debugging

{% tabs %}
{% tab title="Python" %}

```python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def query_with_logging(dataset: str, **kwargs) -> pd.DataFrame | None:
    try:
        df = client.get_dataset(dataset, **kwargs)
        logger.info(f"Query successful: {dataset}, {len(df)} rows returned")
        return df
    except Exception as e:
        logger.error(f"Query failed: {dataset}\nParams: {kwargs}\nError: {e}")
        return None

# Usage
df = query_with_logging("ercot_fuel_mix", start="2026-01-01", end="2026-01-02")
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Handle Pagination Automatically

{% tabs %}
{% tab title="Python" %}

```python
# The client handles pagination automatically

# Just specify your query parameters and it fetches all pages
df = client.get_dataset(
    "ercot_fuel_mix",
    start="2026-01-01",
    end="2026-01-08"  # Large date range - client paginates automatically
)

print(f"Retrieved {len(df)} total rows across all pages")
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Related Documentation

* [Utility Endpoints](file:///docs/api/utility-endpoints) - Monitor your usage to avoid limits
* [Advanced Query Features](file:///docs/api/advanced-query-features) - Valid parameter values
* [Best Practices](file:///docs/api/best-practices) - Optimize queries to avoid timeouts


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.gridstatus.io/developers/concepts/error-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
