Query Imagery
curl --request GET \
--url https://avert.ldeo.columbia.edu/api/imagery/qimport requests
url = "https://avert.ldeo.columbia.edu/api/imagery/q"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://avert.ldeo.columbia.edu/api/imagery/q', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"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"
}
}
Endpoints
Query Imagery
Unified endpoint for querying infrared and visible volcano imagery
GET
/
api
/
imagery
/
q
Query Imagery
curl --request GET \
--url https://avert.ldeo.columbia.edu/api/imagery/qimport requests
url = "https://avert.ldeo.columbia.edu/api/imagery/q"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://avert.ldeo.columbia.edu/api/imagery/q', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"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"
}
}
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.All parameters are optional. Without parameters, the API returns the 100 most recent infrared images.
Parameters
Image Type
string
default:"infrared"
Type of imagery to queryOptions:
infrared- Thermal/infrared imagery (default)visible- Standard visible light imagery
# Infrared images (default)
?imageType=infrared
# Visible images
?imageType=visible
Pagination
integer
default:"100"
Number of results per pageRange: 1-200
Default: 100
Default: 100
# Get 20 results
?limit=20
# Get maximum 200 results
?limit=200
integer
default:"1"
Page number for paginationMinimum: 1
Default: 1
Default: 1
# First page
?page=1
# Second page
?page=2
Location Filters
You can find available site names and vnum codes here.
string
Camera site codeCommon sites:
CLNE- Cleveland volcanoVPMI- Poás volcano
# Images from Cleveland
?site=CLNE
# Images from Poás
?site=VPMI
integer
Volcano ID numberCommon volcano IDs:
311240- Cleveland345040- Poás383010- Cumbre Vieja311290- Okmok
# Cleveland volcano
?vnum=311240
# Poás volcano
?vnum=345040
Date Range Filters
string
Start date/time for query rangeFormat:
Timezone: Coordinated Universal Time (UTC) Example:
yyyymmddhhmmssTimezone: Coordinated Universal Time (UTC) Example:
20250321110000 = March 21, 2025 at 11:00:00 AM UTC# Images after January 1, 2025
?datefrom=20250101000000
# Specific date and time
?datefrom=20250321110000
string
End date/time for query rangeFormat:
Timezone: Coordinated Universal Time (UTC)Time Handling:
yyyymmddhhmmssTimezone: Coordinated Universal Time (UTC)Time Handling:
freq=allwith single site: Time portion ignored, dates interpreted as local datesfreq=daily: Time portion ignored, dates interpreted as local datesfreq=hourly/minutely: Time portion creates daily recurring UTC time windowfreq=allwithout site: Time portion used, dates interpreted as exact UTC range
# 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
Condition Filters
boolean
Filter by empty/corrupted filesApplies to: Infrared and Visible
# Non-empty images only
?is_empty=false
boolean
Filter by time of dayApplies to: Visible only (returns 400 error for infrared)Options:
true- Nighttime imagesfalse- Daytime images
# Daytime visible images only
?imageType=visible&is_night=false
# Nighttime visible images
?imageType=visible&is_night=true
boolean
Filter by degraded quality (dynamic range artifacts)Applies to: Infrared only (returns 400 error for visible)
# Non-degraded infrared images
?imageType=infrared&is_degraded=false
boolean
Filter by fog/clouds obstructionApplies to: Infrared and Visible
# 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
Temporal Sampling
string
default:"all"
Frequency/sampling filter for time-lapse and data reductionOptions:
Time Handling:
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 (requiressite)hourly- Middle image from each hour within daily UTC time window (requiressite)daily- Image closest to 10:00 AM local time for each day (requiressite)
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 |
freq=allwith single site: Time portion ignored, returns full local daysfreq=daily: Time portion ignored, returns one image per local day at 10 AMfreq=hourly/minutely: Time portion creates daily recurring UTC time window
# 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
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.string
default:"desc"
Sort order of results by timestampOptions:
desc- Newest images first (default)asc- Oldest images first
freq=allwith single site: Sorts by local site time- All other queries: Sorts by UTC time
# Default: newest first
?order=desc
# Oldest first (useful for time-lapse)
?order=asc
# Combined with other parameters
?site=VPMI&freq=hourly&order=asc
For
freq=hourly and freq=minutely, the same images are returned regardless of order - only the display order changes.Download Options
string
Download mode for retrieving imagesOptions:Returns JSON with:Returns ZIP file with all 24 imagesAPI testing:
estimate- Get download estimate (size, time, warnings) WITHOUT downloadingtrue- Download all matching images as ZIP fileselected- Download specific images by ID (requiresimage_ids)
1
Get estimate
?site=VPMI&datefrom=20251201000000&dateto=20251201235959&download=estimate
{
"total_images": 24,
"estimated_size_mb": 4.1,
"estimated_time_seconds": 5,
"warning": null,
"suggestion": null
}
2
Show confirmation
Display estimate to user and get confirmation before downloading
3
Trigger download
?site=VPMI&datefrom=20251201000000&dateto=20251201235959&download=true
# Test with small batch
?limit=10&download=true
string
Comma-separated image IDs for selective downloadRequired when:
Format: Comma-separated list of image IDs
download=selectedFormat: Comma-separated list of image IDs
# Download specific images
?download=selected&image_ids=311240.CLNE.2025.329_120000-0000,311240.CLNE.2025.329_130000-0000
Response Format
JSON Response (default)
{
"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"
}
}
Response Fields
array
required
Array of image objects matching your query
Show Image object properties
Show Image object properties
string
required
Unique identifier for the image
string
required
Public HTTPS URL to the image file - ready to display
integer
required
Volcano ID number
string
required
Camera site code
string
required
ISO 8601 timestamp when image was captured. Format depends on query:
freq=allwith single site: Local time (no Z suffix)- All other queries: UTC time (with Z suffix)
string
required
Type of image:
infrared or visibleinteger
Frame number (typically 0)
string
Image file format (typically
jpg)object
required
Pagination metadata for navigating results
Show Pagination properties
Show Pagination properties
integer
Current page number
integer
Results per page
integer
Total number of matching results
integer
Total number of pages available
boolean
Whether a previous page exists
boolean
Whether a next page exists
integer | null
Previous page number (null if on first page)
integer | null
Next page number (null if on last page)
integer
Number of results in current page
object
required
Echo of your query parameters for debugging
Download Estimate Response
When usingdownload=estimate:
{
"total_images": 24,
"estimated_size_mb": 4.1,
"estimated_time_seconds": 5,
"warning": null,
"suggestion": 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"
}
ZIP Download Response
When usingdownload=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
# 100 most recent infrared images
curl "https://avert.ldeo.columbia.edu/api/imagery/q"
# 50 visible images
curl "https://avert.ldeo.columbia.edu/api/imagery/q?imageType=visible&limit=50"
# 5 images for quick testing
curl "https://avert.ldeo.columbia.edu/api/imagery/q?limit=5"
Filter by Location
# Images from Cleveland site
curl "https://avert.ldeo.columbia.edu/api/imagery/q?site=CLNE&limit=50"
# Images from Poás volcano
curl "https://avert.ldeo.columbia.edu/api/imagery/q?vnum=345040&limit=100"
Filter by Date
# November 2025
curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
datefrom=20251101000000&\
dateto=20251130235959&\
limit=100"
# March 21, 2025
curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
datefrom=20250321000000&\
dateto=20250321235959&\
limit=50"
# After November 1, 2025
curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
datefrom=20251101000000&\
limit=100"
Filter by Conditions
# 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"
# 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"
Temporal Sampling
# 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"
# Daily snapshots for entire year
curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
site=CLNE&\
freq=daily&\
datefrom=20250101000000&\
dateto=20251231235959"
Downloads
# 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
# 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
# Download 10 images for testing
curl "https://avert.ldeo.columbia.edu/api/imagery/q?\
limit=10&\
download=true" \
-o test.zip
Code Examples
JavaScript/TypeScript
// 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);
});
// 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();
// 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);
}
// 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);
Python
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']}")
# 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()
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")
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)
# 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'])}")
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:
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'}
)
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'
);
Error Responses
{
"error": true,
"status_code": 400,
"detail": "Invalid imageType 'thermal'. Must be 'infrared' or 'visible'."
}
{
"error": true,
"status_code": 400,
"detail": "is_night filter is not applicable to infrared images..."
}
{
"error": true,
"status_code": 400,
"detail": "freq=daily requires a 'site' parameter. Example: ?site=VPMI&freq=daily"
}
{
"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"
}
{
"error": "Rate limit exceeded: 100 per 1 minute"
}
Best Practices
Use estimates before downloading
Use estimates before downloading
Always call
download=estimate before triggering large downloads to inform users about size and time requirements.# 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)
Use freq parameter for large datasets
Use freq parameter for large datasets
When querying long time periods, use
freq=hourly or freq=daily to reduce dataset size while maintaining temporal coverage.# 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
Implement pagination for large result sets
Implement pagination for large result sets
Don’t try to fetch all results at once. Use pagination to load results incrementally.
# 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
Cache results when appropriate
Cache results when appropriate
Store frequently accessed data locally to reduce API calls and improve performance.
// 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;
}
Handle errors gracefully
Handle errors gracefully
Implement proper error handling and retry logic for rate limits and network errors.
# 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}")
Need Help?
Migration Guide
Upgrading from API v1? Check our migration guide
Contact Support
Questions or issues? Reach out to our team

