> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avert.ldeo.columbia.edu/llms.txt
> Use this file to discover all available pages before exploring further.

# Query Imagery

> Unified endpoint for querying infrared and visible volcano imagery

## Overview

The unified imagery query endpoint provides access to both infrared and visible images from AVERT volcano monitoring cameras. Query and download images with flexible filtering options including site, date range, atmospheric conditions, and temporal sampling.

<Tip>
  All parameters are optional. Without parameters, the API returns the 100 most recent infrared images.
</Tip>

## Parameters

### Image Type

<ParamField query="imageType" type="string" default="infrared">
  Type of imagery to query

  **Options:**

  * `infrared` - Thermal/infrared imagery (default)
  * `visible` - Standard visible light imagery

  ```bash theme={null}
  # Infrared images (default)
  ?imageType=infrared

  # Visible images
  ?imageType=visible
  ```
</ParamField>

### Pagination

<ParamField query="limit" type="integer" default="100">
  Number of results per page

  **Range:** 1-200\
  **Default:** 100

  ```bash theme={null}
  # Get 20 results
  ?limit=20

  # Get maximum 200 results
  ?limit=200
  ```
</ParamField>

<ParamField query="page" type="integer" default="1">
  Page number for pagination

  **Minimum:** 1\
  **Default:** 1

  ```bash theme={null}
  # First page
  ?page=1

  # Second page
  ?page=2
  ```
</ParamField>

### Location Filters

<Note>You can find available site names and vnum codes [here](/api-reference/v2/introduction#available-volcanoes-and-sites).</Note>

<ParamField query="site" type="string">
  Camera site code

  **Common sites:**

  * `CLNE` - Cleveland volcano
  * `VPMI` - Poás volcano

  ```bash theme={null}
  # Images from Cleveland
  ?site=CLNE

  # Images from Poás
  ?site=VPMI
  ```
</ParamField>

<ParamField query="vnum" type="integer">
  Volcano ID number

  **Common volcano IDs:**

  * `311240` - Cleveland
  * `345040` - Poás
  * `383010` - Cumbre Vieja
  * `311290` - Okmok

  ```bash theme={null}
  # Cleveland volcano
  ?vnum=311240

  # Poás volcano
  ?vnum=345040
  ```
</ParamField>

### Date Range Filters

<ParamField query="datefrom" type="string">
  Start date/time for query range

  **Format:** `yyyymmddhhmmss`\
  **Timezone:** Coordinated Universal Time (UTC)
  **Example:** `20250321110000` = March 21, 2025 at 11:00:00 AM UTC

  ```bash theme={null}
  # Images after January 1, 2025
  ?datefrom=20250101000000

  # Specific date and time
  ?datefrom=20250321110000
  ```
</ParamField>

<ParamField query="dateto" type="string">
  End date/time for query range

  **Format:** `yyyymmddhhmmss`\
  **Timezone:** Coordinated Universal Time (UTC)

  **Time Handling:**

  * `freq=all` with single site: Time portion ignored, dates interpreted as local dates
  * `freq=daily`: Time portion ignored, dates interpreted as local dates
  * `freq=hourly/minutely`: Time portion creates daily recurring UTC time window
  * `freq=all` without site: Time portion used, dates interpreted as exact UTC range

  ```bash theme={null}
  # Images before December 31, 2025
  ?dateto=20251231235959

  # Complete date range (November 2025)
  ?datefrom=20251101000000&dateto=20251130235959

  # Hourly window: 12PM-2PM UTC each day (Dec 1-5)
  ?site=VPMI&freq=hourly&datefrom=20251201120000&dateto=20251205140000
  ```
</ParamField>

### Condition Filters

<ParamField query="is_empty" type="boolean">
  Filter by empty/corrupted files

  **Applies to:** Infrared and Visible

  ```bash theme={null}
  # Non-empty images only
  ?is_empty=false
  ```
</ParamField>

<ParamField query="is_night" type="boolean">
  Filter by time of day

  **Applies to:** Visible only (returns 400 error for infrared)

  **Options:**

  * `true` - Nighttime images
  * `false` - Daytime images

  ```bash theme={null}
  # Daytime visible images only
  ?imageType=visible&is_night=false

  # Nighttime visible images
  ?imageType=visible&is_night=true
  ```
</ParamField>

<ParamField query="is_degraded" type="boolean">
  Filter by degraded quality (dynamic range artifacts)

  **Applies to:** Infrared only (returns 400 error for visible)

  ```bash theme={null}
  # Non-degraded infrared images
  ?imageType=infrared&is_degraded=false
  ```
</ParamField>

<ParamField query="low_visibility" type="boolean">
  Filter by fog/clouds obstruction

  **Applies to:** Infrared and Visible

  ```bash theme={null}
  # Clear images (good visibility)
  ?low_visibility=false

  # Clear, non-degraded infrared images
  ?imageType=infrared&low_visibility=false&is_degraded=false&is_empty=false

  # Clear daytime visible images
  ?imageType=visible&low_visibility=false&is_night=false&is_empty=false
  ```
</ParamField>

### Temporal Sampling

<ParamField query="freq" type="string" default="all">
  Frequency/sampling filter for time-lapse and data reduction

  **Options:**

  * `all` - Return all captured images (default, \~1-2 min intervals)
    * Single site: Returns all images for full local days, timestamps in local time (no Z)
    * No site/multi-site: Returns images in exact UTC time range, timestamps in UTC (with Z)
  * `minutely` - Middle image from each minute within daily UTC time window (requires `site`)
  * `hourly` - Middle image from each hour within daily UTC time window (requires `site`)
  * `daily` - Image closest to 10:00 AM local time for each day (requires `site`)

  **How it works:**

  AVERT cameras capture images approximately every 1-2 minutes (irregular intervals). The `freq` parameter groups images by time period and returns a representative image from each period.

  | freq value          | Data Reduction          | Timestamp Format  | Requires Site? |
  | ------------------- | ----------------------- | ----------------- | -------------- |
  | `all` (single site) | None (100% of images)   | Local time (no Z) | No             |
  | `all` (no site)     | None                    | UTC (with Z)      | No             |
  | `minutely`          | \~0-5%                  | UTC (with Z)      | Yes            |
  | `hourly`            | \~98% (60x smaller)     | UTC (with Z)      | Yes            |
  | `daily`             | \~99.9% (1440x smaller) | UTC (with Z)      | Yes            |

  **Time Handling:**

  * `freq=all` with single site: Time portion ignored, returns full local days
  * `freq=daily`: Time portion ignored, returns one image per local day at 10 AM
  * `freq=hourly/minutely`: Time portion creates daily recurring UTC time window

  ```bash theme={null}
  # All images (default)
  ?freq=all

  # One image per hour (requires site)
  ?site=VPMI&freq=hourly

  # Daily summaries (requires site)
  ?site=CLNE&freq=daily&datefrom=20250101000000&dateto=20251231235959

  # Hourly window: 12PM-2PM UTC each day
  ?site=VPMI&freq=hourly&datefrom=20251201120000&dateto=20251205140000
  ```

  <Tip>
    Use `freq=hourly` or `freq=daily` when downloading large date ranges to dramatically reduce dataset size while maintaining temporal coverage. Note that `freq=daily`, `hourly`, and `minutely` require a `site` parameter.
  </Tip>
</ParamField>

<ParamField query="order" type="string" default="desc">
  Sort order of results by timestamp

  **Options:**

  * `desc` - Newest images first (default)
  * `asc` - Oldest images first

  **Timezone-aware ordering:**

  * `freq=all` with single site: Sorts by local site time
  * All other queries: Sorts by UTC time

  ```bash theme={null}
  # Default: newest first
  ?order=desc

  # Oldest first (useful for time-lapse)
  ?order=asc

  # Combined with other parameters
  ?site=VPMI&freq=hourly&order=asc
  ```

  <Note>
    For `freq=hourly` and `freq=minutely`, the same images are returned regardless of `order` - only the display order changes.
  </Note>
</ParamField>

### Download Options

<ParamField query="download" type="string">
  Download mode for retrieving images

  **Options:**

  * `estimate` - Get download estimate (size, time, warnings) WITHOUT downloading
  * `true` - Download all matching images as ZIP file
  * `selected` - Download specific images by ID (requires `image_ids`)

  **Frontend workflow (recommended):**

  <Steps>
    <Step title="Get estimate">
      ```bash theme={null}
      ?site=VPMI&datefrom=20251201000000&dateto=20251201235959&download=estimate
      ```

      Returns JSON with:

      ```json theme={null}
      {
        "total_images": 24,
        "estimated_size_mb": 4.1,
        "estimated_time_seconds": 5,
        "warning": null,
        "suggestion": null
      }
      ```
    </Step>

    <Step title="Show confirmation">
      Display estimate to user and get confirmation before downloading
    </Step>

    <Step title="Trigger download">
      ```bash theme={null}
      ?site=VPMI&datefrom=20251201000000&dateto=20251201235959&download=true
      ```

      Returns ZIP file with all 24 images
    </Step>
  </Steps>

  **API testing:**

  ```bash theme={null}
  # Test with small batch
  ?limit=10&download=true
  ```
</ParamField>

<ParamField query="image_ids" type="string">
  Comma-separated image IDs for selective download

  **Required when:** `download=selected`\
  **Format:** Comma-separated list of image IDs

  ```bash theme={null}
  # Download specific images
  ?download=selected&image_ids=311240.CLNE.2025.329_120000-0000,311240.CLNE.2025.329_130000-0000
  ```
</ParamField>

## Response Format

### JSON Response (default)

<ResponseExample>
  ```json Success theme={null}
  {
    "results": [
      {
        "image_id": "311240.CLNE.2025.329_120000-0000",
        "image_url": "https://avert-legacy.ldeo.columbia.edu/archive/imagery/infrared/311240/2025/CLNE/still/329/311240.CLNE.2025.329_120000-0000.jpg",
        "vnum": 311240,
        "site": "CLNE",
        "frame": 0,
        "file_format": "jpg",
        "timestamp": "2025-11-25T12:00:00Z",
        "image_type": "infrared",
        "created_at": "2025-11-25T12:05:00Z",
        "updated_at": "2025-11-25T12:05:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 100,
      "total_count": 1500,
      "total_pages": 15,
      "has_prev": false,
      "has_next": true,
      "prev_page": null,
      "next_page": 2,
      "count": 100
    },
    "query": {
      "imageType": "infrared",
      "site": "CLNE",
      "vnum": 311240,
      "datefrom": null,
      "dateto": null,
      "is_empty": null,
      "is_night": null,
      "is_degraded": null,
      "low_visibility": null,
      "freq": "all",
      "order": "desc"
    }
  }
  ```
</ResponseExample>

### Response Fields

<ResponseField name="results" type="array" required>
  Array of image objects matching your query

  <Expandable title="Image object properties">
    <ResponseField name="image_id" type="string" required>
      Unique identifier for the image
    </ResponseField>

    <ResponseField name="image_url" type="string" required>
      Public HTTPS URL to the image file - ready to display
    </ResponseField>

    <ResponseField name="vnum" type="integer" required>
      Volcano ID number
    </ResponseField>

    <ResponseField name="site" type="string" required>
      Camera site code
    </ResponseField>

    <ResponseField name="timestamp" type="string" required>
      ISO 8601 timestamp when image was captured. Format depends on query:

      * `freq=all` with single site: Local time (no Z suffix)
      * All other queries: UTC time (with Z suffix)
    </ResponseField>

    <ResponseField name="image_type" type="string" required>
      Type of image: `infrared` or `visible`
    </ResponseField>

    <ResponseField name="frame" type="integer">
      Frame number (typically 0)
    </ResponseField>

    <ResponseField name="file_format" type="string">
      Image file format (typically `jpg`)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="pagination" type="object" required>
  Pagination metadata for navigating results

  <Expandable title="Pagination properties">
    <ResponseField name="page" type="integer">
      Current page number
    </ResponseField>

    <ResponseField name="limit" type="integer">
      Results per page
    </ResponseField>

    <ResponseField name="total_count" type="integer">
      Total number of matching results
    </ResponseField>

    <ResponseField name="total_pages" type="integer">
      Total number of pages available
    </ResponseField>

    <ResponseField name="has_prev" type="boolean">
      Whether a previous page exists
    </ResponseField>

    <ResponseField name="has_next" type="boolean">
      Whether a next page exists
    </ResponseField>

    <ResponseField name="prev_page" type="integer | null">
      Previous page number (null if on first page)
    </ResponseField>

    <ResponseField name="next_page" type="integer | null">
      Next page number (null if on last page)
    </ResponseField>

    <ResponseField name="count" type="integer">
      Number of results in current page
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="query" type="object" required>
  Echo of your query parameters for debugging
</ResponseField>

### Download Estimate Response

When using `download=estimate`:

<ResponseExample>
  ```json Estimate Response theme={null}
  {
    "total_images": 24,
    "estimated_size_mb": 4.1,
    "estimated_time_seconds": 5,
    "warning": null,
    "suggestion": null
  }
  ```

  ```json Large Dataset Warning theme={null}
  {
    "total_images": 525600,
    "estimated_size_mb": 87363.0,
    "estimated_time_seconds": 5256,
    "warning": "large_dataset",
    "suggestion": "Consider using freq=hourly or freq=daily to reduce dataset size"
  }
  ```
</ResponseExample>

### ZIP Download Response

When using `download=true` or `download=selected`, returns a ZIP file containing JPG images.

**ZIP Structure:**

```
avert_infrared_CLNE_20251126_120000.zip
└── avert_imagery/
    └── infrared/
        └── 311240/
            └── CLNE/
                ├── 311240.CLNE.2025.329_120000-0000.jpg
                ├── 311240.CLNE.2025.329_130000-0000.jpg
                └── 311240.CLNE.2025.329_140000-0000.jpg
```

## Example Requests

### Basic Queries

<CodeGroup>
  ```bash Default Query theme={null}
  # 100 most recent infrared images
  curl "https://avert.ldeo.columbia.edu/api/imagery/q"
  ```

  ```bash Visible Images theme={null}
  # 50 visible images
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?imageType=visible&limit=50"
  ```

  ```bash Small Sample theme={null}
  # 5 images for quick testing
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?limit=5"
  ```
</CodeGroup>

### Filter by Location

<CodeGroup>
  ```bash By Site theme={null}
  # Images from Cleveland site
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?site=CLNE&limit=50"
  ```

  ```bash By Volcano theme={null}
  # Images from Poás volcano
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?vnum=345040&limit=100"
  ```
</CodeGroup>

### Filter by Date

<CodeGroup>
  ```bash Specific Month theme={null}
  # November 2025
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  datefrom=20251101000000&\
  dateto=20251130235959&\
  limit=100"
  ```

  ```bash Single Day theme={null}
  # March 21, 2025
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  datefrom=20250321000000&\
  dateto=20250321235959&\
  limit=50"
  ```

  ```bash Recent Images theme={null}
  # After November 1, 2025
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  datefrom=20251101000000&\
  limit=100"
  ```
</CodeGroup>

### Filter by Conditions

<CodeGroup>
  ```bash Clear Visible Daytime theme={null}
  # Clear daytime visible images
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  imageType=visible&\
  is_night=false&\
  low_visibility=false&\
  is_empty=false&\
  limit=50"
  ```

  ```bash Clear Infrared theme={null}
  # Clear, non-degraded infrared images
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  imageType=infrared&\
  is_degraded=false&\
  low_visibility=false&\
  is_empty=false&\
  limit=50"
  ```
</CodeGroup>

### Temporal Sampling

<CodeGroup>
  ```bash Hourly Time-lapse theme={null}
  # One image per hour from November
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  site=VPMI&\
  freq=hourly&\
  datefrom=20251101000000&\
  dateto=20251130235959&\
  limit=200"
  ```

  ```bash Daily Summary theme={null}
  # Daily snapshots for entire year
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  site=CLNE&\
  freq=daily&\
  datefrom=20250101000000&\
  dateto=20251231235959"
  ```
</CodeGroup>

### Downloads

<CodeGroup>
  ```bash Get Estimate First theme={null}
  # Step 1: Get download estimate
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  site=VPMI&\
  datefrom=20251201000000&\
  dateto=20251201235959&\
  download=estimate"

  # Response shows: 1440 images, 239 MB, 24 seconds
  ```

  ```bash Download with Filters theme={null}
  # Step 2: Download the images
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  site=VPMI&\
  datefrom=20251201000000&\
  dateto=20251201235959&\
  download=true" \
  -o images.zip
  ```

  ```bash Test Download theme={null}
  # Download 10 images for testing
  curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
  limit=10&\
  download=true" \
  -o test.zip
  ```
</CodeGroup>

## Code Examples

### JavaScript/TypeScript

<CodeGroup>
  ```javascript Basic Query theme={null}
  // Query recent infrared images
  const response = await fetch(
    'https://avert.ldeo.columbia.edu/api/imagery/q?imageType=infrared&limit=20'
  );
  const data = await response.json();

  console.log(`Total results: ${data.results.length}`);
  console.log(`Page ${data.pagination.page} of ${data.pagination.total_pages}`);

  // Display images
  data.results.forEach(img => {
    const imgElement = document.createElement('img');
    imgElement.src = img.image_url;
    imgElement.alt = img.image_id;
    document.body.appendChild(imgElement);
  });
  ```

  ```javascript With Filters theme={null}
  // Query with multiple filters
  const params = new URLSearchParams({
    imageType: 'infrared',
    site: 'CLNE',
    is_degraded: 'false',
    low_visibility: 'false',
    is_empty: 'false',
    datefrom: '20251101000000',
    dateto: '20251130235959',
    limit: '50'
  });

  const response = await fetch(
    `https://avert.ldeo.columbia.edu/api/imagery/q?${params}`
  );
  const data = await response.json();
  ```

  ```javascript Pagination theme={null}
  // Load specific page
  async function loadPage(pageNum) {
    const response = await fetch(
      `https://avert.ldeo.columbia.edu/api/imagery/q?limit=50&page=${pageNum}`
    );
    const data = await response.json();
    
    console.log(`Page ${data.pagination.page} of ${data.pagination.total_pages}`);
    console.log(`Has next: ${data.pagination.has_next}`);
    
    return data;
  }

  // Load next page
  if (data.pagination.has_next) {
    const nextPage = await loadPage(data.pagination.next_page);
  }
  ```

  ```javascript Download with Estimate theme={null}
  // Frontend: Download with user confirmation
  async function downloadImagesWithEstimate(filters) {
    // Step 1: Get estimate
    const estimateUrl = `https://avert.ldeo.columbia.edu/api/imagery/q?${filters}&download=estimate`;
    const estimate = await fetch(estimateUrl).then(r => r.json());
    
    const {total_images, estimated_size_mb, estimated_time_seconds, warning, suggestion} = estimate;
    
    // Step 2: Show confirmation
    let message = `Download ${total_images} images (${estimated_size_mb} MB)?\nEstimated time: ${estimated_time_seconds} seconds`;
    
    if (warning === 'large_dataset') {
      message += `\n\n⚠️ Large download!\n${suggestion}`;
    }
    
    if (confirm(message)) {
      // Step 3: Trigger download
      showLoadingSpinner(`Preparing ${total_images} images...`);
      window.location.href = `https://avert.ldeo.columbia.edu/api/imagery/q?${filters}&download=true`;
    }
  }

  // Usage
  const filters = 'site=VPMI&datefrom=20251101000000&dateto=20251130235959&freq=hourly';
  downloadImagesWithEstimate(filters);
  ```
</CodeGroup>

### Python

<CodeGroup>
  ```python Basic Query theme={null}
  import requests

  # Query recent infrared images
  response = requests.get(
      'https://avert.ldeo.columbia.edu/api/imagery/q',
      params={
          'imageType': 'infrared',
          'site': 'CLNE',
          'limit': 50
      }
  )
  data = response.json()

  print(f"Total results: {len(data['results'])}")
  print(f"Page {data['pagination']['page']} of {data['pagination']['total_pages']}")

  for img in data['results']:
      print(f"{img['image_id']}: {img['image_url']}")
  ```

  ```python With Date Range theme={null}
  # Query with date range and conditions
  response = requests.get(
      'https://avert.ldeo.columbia.edu/api/imagery/q',
      params={
          'imageType': 'infrared',
          'site': 'CLNE',
          'datefrom': '20251101000000',
          'dateto': '20251130235959',
          'is_degraded': False,
          'low_visibility': False,
          'is_empty': False,
          'limit': 100
      }
  )
  data = response.json()
  ```

  ```python Pagination theme={null}
  def get_all_pages(base_params, max_pages=None):
      """Fetch all pages of results"""
      all_results = []
      page = 1
      
      while True:
          params = {**base_params, 'page': page}
          response = requests.get(
              'https://avert.ldeo.columbia.edu/api/imagery/q',
              params=params
          )
          data = response.json()
          
          all_results.extend(data['results'])
          
          if not data['pagination']['has_next']:
              break
          
          if max_pages and page >= max_pages:
              break
              
          page += 1
      
      return all_results

  # Usage
  all_images = get_all_pages({
      'site': 'CLNE',
      'limit': 100
  }, max_pages=5)

  print(f"Fetched {len(all_images)} total images")
  ```

  ```python Download with Estimate theme={null}
  def download_with_estimate(filters):
      """Download images with estimate confirmation"""
      # Step 1: Get estimate
      estimate_response = requests.get(
          'https://avert.ldeo.columbia.edu/api/imagery/q',
          params={**filters, 'download': 'estimate'}
      )
      estimate = estimate_response.json()
      
      total = estimate['total_images']
      size_mb = estimate['estimated_size_mb']
      time_sec = estimate['estimated_time_seconds']
      warning = estimate.get('warning')
      suggestion = estimate.get('suggestion')
      
      # Step 2: Confirm with user
      print(f"Download {total} images ({size_mb} MB)?")
      print(f"Estimated time: {time_sec} seconds")
      
      if warning:
          print(f"⚠️ Warning: {suggestion}")
      
      confirm = input("Continue? (y/n): ")
      
      if confirm.lower() == 'y':
          # Step 3: Download
          print(f"Downloading {total} images...")
          download_response = requests.get(
              'https://avert.ldeo.columbia.edu/api/imagery/q',
              params={**filters, 'download': 'true'}
          )
          
          with open('images.zip', 'wb') as f:
              f.write(download_response.content)
          
          print(f"✓ Downloaded {total} images to images.zip")

  # Usage
  filters = {
      'site': 'VPMI',
      'freq': 'hourly',
      'datefrom': '20251101000000',
      'dateto': '20251130235959'
  }
  download_with_estimate(filters)
  ```

  ```python Frequency Filtering theme={null}
  # Get hourly snapshots for time-lapse
  response = requests.get(
      'https://avert.ldeo.columbia.edu/api/imagery/q',
      params={
          'site': 'VPMI',
          'freq': 'hourly',
          'datefrom': '20251101000000',
          'dateto': '20251130235959',
          'limit': 100
      }
  )
  hourly_images = response.json()

  print(f"Hourly images: {len(hourly_images['results'])}")
  ```
</CodeGroup>

## Handling Rate Limits

The `/api/imagery/q` endpoint limits requests to 100 per minute per IP address. Implement retry logic to handle rate limit errors:

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time

  def query_with_retry(url, params, max_retries=3):
      """Query with exponential backoff on rate limits"""
      for attempt in range(max_retries):
          response = requests.get(url, params=params)
          
          if response.status_code == 200:
              return response.json()
          elif response.status_code == 429:
              # Rate limited - wait and retry
              wait_time = 2 ** attempt  # Exponential backoff
              print(f"Rate limited. Waiting {wait_time} seconds...")
              time.sleep(wait_time)
          else:
              response.raise_for_status()
      
      raise Exception("Max retries exceeded")

  # Usage
  data = query_with_retry(
      'https://avert.ldeo.columbia.edu/api/imagery/q',
      params={'limit': 50, 'site': 'CLNE'}
  )
  ```

  ```javascript JavaScript theme={null}
  async function queryWithRetry(url, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url);
      
      if (response.ok) {
        return await response.json();
      } else if (response.status === 429) {
        // Rate limited - wait and retry
        const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
        console.log(`Rate limited. Waiting ${waitTime/1000} seconds...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      } else {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
    }
    
    throw new Error('Max retries exceeded');
  }

  // Usage
  const data = await queryWithRetry(
    'https://avert.ldeo.columbia.edu/api/imagery/q?limit=50&site=CLNE'
  );
  ```
</CodeGroup>

## Error Responses

<ResponseExample>
  ```json 400 - Invalid Parameters theme={null}
  {
    "error": true,
    "status_code": 400,
    "detail": "Invalid imageType 'thermal'. Must be 'infrared' or 'visible'."
  }
  ```

  ```json 400 - Invalid Filter Combination theme={null}
  {
    "error": true,
    "status_code": 400,
    "detail": "is_night filter is not applicable to infrared images..."
  }
  ```

  ```json 400 - Missing Required Site theme={null}
  {
    "error": true,
    "status_code": 400,
    "detail": "freq=daily requires a 'site' parameter. Example: ?site=VPMI&freq=daily"
  }
  ```

  ```json 422 - Validation Error theme={null}
  {
    "error": true,
    "status_code": 422,
    "detail": "Validation error in request parameters",
    "errors": [
      {
        "type": "int_parsing",
        "loc": ["query", "limit"],
        "msg": "Input should be a valid integer"
      }
    ],
    "path": "/api/imagery/q"
  }
  ```

  ```json 429 - Rate Limit Exceeded theme={null}
  {
    "error": "Rate limit exceeded: 100 per 1 minute"
  }
  ```
</ResponseExample>

## Best Practices

<AccordionGroup>
  <Accordion title="Use estimates before downloading" icon="chart-line">
    Always call `download=estimate` before triggering large downloads to inform users about size and time requirements.

    ```python theme={null}
    # Good: Get estimate first
    estimate = get_download_estimate(filters)
    if estimate['total_images'] > 1000:
        print(f"Warning: Large download ({estimate['estimated_size_mb']} MB)")
        
    # Then download if confirmed
    download_images(filters)
    ```
  </Accordion>

  <Accordion title="Use freq parameter for large datasets" icon="clock">
    When querying long time periods, use `freq=hourly` or `freq=daily` to reduce dataset size while maintaining temporal coverage.

    ```bash theme={null}
    # Bad: Download 60,000+ images from November
    ?datefrom=20251101000000&dateto=20251130235959&download=true

    # Good: Download ~720 hourly snapshots instead
    ?datefrom=20251101000000&dateto=20251130235959&freq=hourly&download=true
    ```
  </Accordion>

  <Accordion title="Implement pagination for large result sets" icon="book">
    Don't try to fetch all results at once. Use pagination to load results incrementally.

    ```python theme={null}
    # Good: Paginated loading
    def load_results(params):
        page = 1
        while True:
            data = fetch_page(params, page)
            yield data['results']
            
            if not data['pagination']['has_next']:
                break
            page += 1
    ```
  </Accordion>

  <Accordion title="Cache results when appropriate" icon="database">
    Store frequently accessed data locally to reduce API calls and improve performance.

    ```javascript theme={null}
    // Good: Cache results
    const cache = new Map();

    async function getCachedResults(cacheKey, params) {
      if (cache.has(cacheKey)) {
        return cache.get(cacheKey);
      }
      
      const data = await fetchResults(params);
      cache.set(cacheKey, data);
      return data;
    }
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully" icon="shield">
    Implement proper error handling and retry logic for rate limits and network errors.

    ```python theme={null}
    # Good: Comprehensive error handling
    try:
        data = query_with_retry(url, params)
    except requests.HTTPError as e:
        if e.response.status_code == 429:
            log.warning("Rate limited - try again later")
        else:
            log.error(f"API error: {e}")
    except requests.RequestException as e:
        log.error(f"Network error: {e}")
    ```
  </Accordion>
</AccordionGroup>

## Need Help?

<CardGroup cols={2}>
  <Card title="Migration Guide" icon="arrow-right-arrow-left" href="/api-reference/v2/migration-guide">
    Upgrading from API v1? Check our migration guide
  </Card>

  <Card title="Contact Support" icon="envelope" href="mailto:avert-system@proton.me">
    Questions or issues? Reach out to our team
  </Card>
</CardGroup>
