A unified API and event database that collects, normalizes, and serves solar event data from multiple sources (HEK, CCMC, WSA, RHESSI) for use in Helioviewer.org and related tools.
- Multi-source Event Collection: Collects and normalizes solar event data from CCMC DONKI and other sources
- Coordinate Transformation: High-performance batch coordinate transformation from HGS to HPC with caching
- RESTful API: Modern REST API with v1 endpoints for event querying
- Real-time Processing: Event collection with configurable chunk intervals for efficient data processing
- Event Distribution Aggregation: Pre-aggregated event counts by time buckets (30m, hourly, daily, weekly, monthly, yearly) for fast distribution queries
- Redis Caching: Comprehensive caching layer for HTTP requests and coordinate transformations
- PostgreSQL Storage: Robust data storage with Eloquent ORM and database migrations
This project uses Docker Compose for local development with PHP 8.4, Nginx, and PostgreSQL.
- Docker
- Docker Compose
- Make (optional, for convenience commands)
-
Start the development environment:
make up
Or manually:
docker compose -f docker/docker-compose.yml up
-
Install PHP dependencies:
make composer-install
-
Run database migrations:
make migrate-run
-
Seed the database with sample data:
make seed-run
-
Access the application:
- Open http://localhost:8082 in your browser
- You should see the PHP info page
For a complete list of available commands, run:
makemake up- Start the Docker containersmake down- Stop the Docker containersmake build- Build the Docker images (with --no-cache)make shell- Open a bash shell in the PHP container (user 1000:1000)make shell-root- Open a root bash shell in the PHP container
make composer-install- Install PHP dependencies via Composermake composer-require PACKAGE=package-name- Add a new packagemake composer-dump- Regenerate Composer autoloader
make nginx-reload- Reload nginx configuration
make migrate-status- Check migration statusmake migrate-create NAME=MigrationName- Create a new migrationmake migrate-run- Run pending migrationsmake migrate-rollback- Rollback the last migrationmake seed-run- Run database seeders
make collect- Collect events for today (daily chunks)make collect 2024-01-01- Collect events for single daymake collect 2024-01-01 2024-01-31- Collect events for date range (daily chunks)make collect 2024-01-01 2024-01-31 5- Collect date range in 5-day chunksmake collect SOURCES="NAME1,NAME2" 2024-01-01- Collect only from the named sourcesmake sources- List every registered source and the path it writes tomake recents- Show the most recent events from the databasemake recents 10- Show last 10 eventsmake stats- Show database statistics grouped by source and pathmake logs- Follow application logs (tail -f storage/logs/*.log)make reset- Reset database (rollback all migrations, migrate, and seed)make distribution-build- Build distribution aggregations from all events
Base URL: https://events.helioviewer.org
| Source | Description |
|---|---|
| CCMC | Community Coordinated Modeling Center (DONKI, FlareScoreboard) |
| HEK | Heliophysics Event Knowledgebase |
| WSA | Wang-Sheeley-Arge model forecasts (CCMC WSA dashboard): coronal-hole maps and magnetic-connectivity footpoints |
| RHESSI | Reuven Ramaty High Energy Solar Spectroscopic Imager |
Source names are case-insensitive in all endpoints.
| Format | Example |
|---|---|
| Unix timestamp | 1705314645 |
| ISO 8601 with microseconds & timezone | 2024-01-15T10:30:45.123456+00:00 |
| ISO 8601 with milliseconds & Z | 2024-01-15T10:30:45.123Z |
| ISO 8601 with timezone | 2024-01-15T10:30:45+00:00 |
| ISO 8601 with Z | 2024-01-15T10:30:45Z |
| ISO 8601 without timezone | 2024-01-15T10:30:45 |
| Space-separated | 2024-01-15 10:30:45 |
| PHP strtotime fallback | 2024-01-15, now, etc. |
These endpoints return data in a format tailored for the Helioviewer.org client.
Get events active at a specific observation time, in Helioviewer legacy format. Returns events grouped by event type with nested groups by detection method.
WSA is not implemented on this endpoint. It is the legacy tree format, which stops at three path levels; WSA paths go deeper. Requesting source=WSA returns 400 — use /api/v1/events/WSA/observation/{timestamp} instead.
| Parameter | Type | Description |
|---|---|---|
source |
path | Event source (CCMC, HEK, RHESSI) — WSA not implemented, see above |
timestamp |
path | Observation time (any supported timestamp format) |
import requests
response = requests.get(
"https://events.helioviewer.org/helioviewer/events/HEK/observation/2025-03-15T12:00:00Z"
)
data = response.json()Example response:
[
{
"name": "Active Region",
"pin": "AR",
"groups": [
{
"name": "HMI SHARP",
"contact": "turmon@jpl.nasa.gov",
"url": "http://sun.stanford.edu/~mbobra/spaceweather/sharps.htm",
"data": [
{
"id": "019c3d8f-0932-715c-b107-7599a5d9a5a9",
"path": "HEK>>Active Region>>HMI SHARP",
"start": "2025-03-15T08:00:00",
"end": "2025-03-15T12:00:00",
"hv_hpc_x": -806.97,
"hv_hpc_y": 440.01,
"visible": true,
"label": "HMI SHARP 12923",
"..."
},
"..."
]
}
]
},
"... (31 event type groups)"
]Get events matching path prefixes within a time range. Returns a flat list with Helioviewer-specific fields (x, x2 as millisecond timestamps, event_type, frm_name, hv_labels_formatted — tooltip rows, built from the HEK source fields or the WSA view). hv_hpc_x/hv_hpc_y are the event's arcsec snapshot at its own coordinate_time, whatever system the row is stored in.
| Parameter | Type | Description |
|---|---|---|
from |
path | Start time (Unix timestamp) |
to |
path | End time (Unix timestamp) |
paths |
body (JSON) | Array of event path prefixes; an entry ending in >><uuid> selects that single event by id |
import requests
response = requests.post(
"https://events.helioviewer.org/helioviewer/events/from/1741996800/to/1742083200",
json={"paths": ["HEK>>Flare"]}
)
data = response.json()Example response:
{
"paths": ["HEK>>Flare"],
"from": 1741996800,
"to": 1742083200,
"count": 33,
"events": [
{
"x": 1741999352000,
"x2": 1741999592000,
"y": 1,
"event_starttime": "2025-03-15 00:42:32",
"event_endtime": "2025-03-15 00:46:32",
"event_peaktime": "2025-03-15 00:44:20",
"hv_hpc_x": 345.6,
"hv_hpc_y": 192,
"url": "https://events.helioviewer.org/api/v1/events/019c3d8f-07d6-...",
"event_type": "FL",
"frm_name": "Flare Detective - Trigger Module",
"concept": "Flare",
"hv_labels_formatted": {"Peak Flux": "37.4 DN/sec/pixel"}
},
"..."
]
}Get event count distributions aggregated into time buckets.
| Parameter | Type | Description |
|---|---|---|
size |
path | Bucket size: 30m, h, D, W, M, Y |
from |
path | Start time (Unix timestamp) |
to |
path | End time (Unix timestamp) |
paths |
body (JSON) | Array of event path prefixes |
import requests
response = requests.post(
"https://events.helioviewer.org/helioviewer/distributions/size/D/from/1741996800/to/1742256000",
json={"paths": ["HEK>>Flare", "CCMC>>DONKI>>CME"]}
)
data = response.json()Example response:
{
"paths": ["HEK>>Flare", "CCMC>>DONKI>>CME"],
"size": "D",
"from": 1741996800,
"to": 1742256000,
"event_types": ["CE", "FL"],
"buckets": [
{"start": 1741996800, "counts": {"CE": 9, "FL": 33}},
{"start": 1742083200, "counts": {"CE": 13, "FL": 73}},
{"start": 1742169600, "counts": {"CE": 16, "FL": 79}},
{"start": 1742256000, "counts": {"CE": 16, "FL": 49}}
]
}Get deduplicated events + rotated coordinates for multiple timestamps, filtered by a flexible selections array of path prefixes — designed for movie rendering (static event data sent once, per-timestamp rotated coordinates sent separately). A selection matches every event whose path equals the prefix or starts with that prefix.
| Parameter | Type | Description |
|---|---|---|
timestamps |
body (JSON) | Array of datetime strings (max 150 per request) |
selections |
body (JSON) | Array of path-prefix strings (max 200) |
import requests
response = requests.post(
"https://events.helioviewer.org/helioviewer/events/frames_with_selections",
json={
"timestamps": ["2025-03-15 11:00:00", "2025-03-15 12:00:00"],
"selections": ["HEK>>Flare", "CCMC>>DONKI>>CME"]
}
)
data = response.json()Example response:
{
"events": {
"019c3d8f-...": {
"path": "HEK>>Flare>>SSW Latest Events",
"label": "...", "start": "2025-03-15T11:50:00", "end": "2025-03-15T12:10:00",
"hv_hpc_x": -123.4, "hv_hpc_y": 567.8, "footprint": [[...]],
"type": "FL", "pin": "FL"
}
},
"timestamps": {
"2025-03-15 11:00:00": {"019c3d8f-...": {"dx": 4.4, "dy": 2.4, "visible": true}},
"2025-03-15 12:00:00": {"019c3d8f-...": {"dx": 9.7, "dy": 5.1, "visible": true}}
}
}timestamps[ts] is {} (object) if no event in the selection is active at that moment.
For movies > 150 frames, split timestamps into chunks and merge timestamps across responses.
hv_hpc_x/y and footprint in events are the arcsec base at the event's own coordinate time; each timestamp carries {"dx", "dy"}, that frame's arcsec offset from the base. Render pin = center + (dx, dy), and shift every polygon point by the same delta. footprint is a list of polygons ([[{x,y},…],…] — multi-contour events like WSA coronal-hole maps carry several).
visible is per frame: whether the event's center faces the observer at that timestamp, so a movie can fade a region as it rotates over the limb without re-fetching its footprint. Far-side vertices in the events footprint base carry their own "visible": false.
Get the last 100 updated events with enhanced data.
import requests
response = requests.get(
"https://events.helioviewer.org/api/v1/events/recents"
)
events = response.json()Example response:
[
{
"url": "https://events.helioviewer.org/api/v1/events/019d0c00-1985-...",
"path": "CCMC>>Solar Flare Predictions>>ASSA",
"start": "2026-03-20 16:00:00",
"end": "2026-03-21 04:00:00",
"hv_hpc_x": -1,
"hv_hpc_y": -1,
"label": "ASSA \nC: 25%\nM: 4%\nX: 0%",
"coordinate_system": "stonyhurst",
"regions": [{"organization": "MODEL", "external_id": "4", "..."}],
"source_url": "https://events.helioviewer.org/api/v1/events/019d0c00-.../source",
"views": [{"name": "Flare Prediction", "content": {"C": 0.25, "M": 0.04, "..."}}],
"link": {"url": "...", "text": "Helioviewer Events API JSON"}
},
"... (100 events)"
]Get events from a specific source active at a given observation time.
| Parameter | Type | Description |
|---|---|---|
source |
path | Event source (CCMC, HEK, RHESSI, WSA) |
timestamp |
path | Observation time (any supported timestamp format) |
import requests
response = requests.get(
"https://events.helioviewer.org/api/v1/events/HEK/observation/2025-03-15T12:00:00Z"
)
events = response.json()Example response:
[
{
"url": "https://events.helioviewer.org/api/v1/events/019c3d8f-0932-...",
"path": "HEK>>Active Region>>HMI SHARP",
"start": "2025-03-15 08:00:00",
"end": "2025-03-15 12:00:00",
"hv_hpc_x": -806.97,
"hv_hpc_y": 440.01,
"visible": true,
"label": "HMI SHARP 12923",
"coordinate_system": "helioprojective",
"regions": [{"organization": "NOAA", "external_id": "14033", "..."}],
"..."
},
"... (49 events)"
]visible says whether the event's center faces the observer at the requested time — an event that has rotated behind the limb is still returned, with visible: false, and the client decides how to draw it. Far-side footprint vertices carry their own "visible": false next to x/y; front-side vertices have no such key. Both fields are additive, and a missing key always means visible.
Get a single event by its UUID with full details.
| Parameter | Type | Description |
|---|---|---|
uuid |
path | Event UUID |
import requests
uuid = "019d0c00-1985-7131-84eb-24bc34a750ad"
response = requests.get(
f"https://events.helioviewer.org/api/v1/events/{uuid}"
)
event = response.json()Example response:
{
"url": "https://events.helioviewer.org/api/v1/events/019d0c00-1985-...",
"path": "CCMC>>Solar Flare Predictions>>ASSA",
"start": "2026-03-20 16:00:00",
"peak": "2026-03-21 04:00:00",
"end": "2026-03-21 04:00:00",
"coordinate_time": "2026-03-20 17:24:36",
"hv_hpc_x": -1,
"hv_hpc_y": -1,
"label": "ASSA \nC: 25%\nM: 4%\nX: 0%",
"coordinate_system": "stonyhurst",
"regions": [{"organization": "MODEL", "external_id": "4", "..."}],
"source_url": "https://events.helioviewer.org/api/v1/events/019d0c00-.../source",
"views": [{"name": "Flare Prediction", "content": {"C": 0.25, "M": 0.04, "X": 0, "..."}}],
"link": {"url": "...", "text": "Helioviewer Events API JSON"}
}Get the raw source data for an event (original data from the provider before normalization).
| Parameter | Type | Description |
|---|---|---|
uuid |
path | Event UUID |
import requests
uuid = "019d0c00-1985-7131-84eb-24bc34a750ad"
response = requests.get(
f"https://events.helioviewer.org/api/v1/events/{uuid}/source"
)
source_data = response.json()Example response (CCMC FlareScoreboard source):
{
"start_window": "2026-03-20T16:00:00.0Z",
"end_window": "2026-03-21T04:00:00.0Z",
"issue_time": "2026-03-20T16:00:00.0Z",
"C": 0.25,
"M": 0.04,
"X": 0,
"NOAALocationTime": "-1",
"NOAALatitude": "-1",
"NOAALongitude": "-1",
"ModelRegionId": 4,
"ModelLocationTime": "2026-03-20T16:00:00.0Z",
"ModelLatitude": -69,
"ModelLongitude": 25
}Get all active regions across all organizations.
import requests
response = requests.get(
"https://events.helioviewer.org/api/v1/regions"
)
data = response.json()
regions = data["regions"]Example response:
{
"regions": [
{
"id": 1032,
"organization": "CATANIA",
"external_id": "1",
"event_count": 147,
"first_seen": "2025-09-24 23:38:31",
"last_updated": "2025-09-24 23:38:31",
"latest_event_start": "2026-03-19 12:30:00"
},
"... (11310 total regions)"
]
}Get events associated with a specific active region.
| Parameter | Type | Description |
|---|---|---|
organization |
path | Region organization (e.g., NOAA, CATANIA, HARP) |
external_id |
path | Region identifier (e.g., 14188) |
import requests
response = requests.get(
"https://events.helioviewer.org/api/v1/regions/NOAA/14188"
)
data = response.json()
region = data["region"]
events = data["events"]Example response:
{
"region": {
"organization": "NOAA",
"external_id": "14188",
"event_count": 100
},
"events": [
{
"url": "https://events.helioviewer.org/api/v1/events/019981d6-c0db-...",
"path": "CCMC>>Solar Flare Predictions>>DAFFS",
"start": "2025-08-31 23:54:00",
"end": "2025-09-01 23:54:00",
"hv_hpc_x": -10.97,
"hv_hpc_y": 60.73,
"label": "DAFFS \nC+: 0.3%\nM+: 0.03%\nX: 0.06%",
"coordinate_system": "stonyhurst",
"..."
},
"... (100 events total)"
]
}| Path | Description |
|---|---|
GET / |
Home page with API documentation |
GET /stats |
Statistics dashboard |
GET /active-regions |
Active regions search tool |
- Nginx (
eventsapi-nginx) - Web server listening on port 8082 - PHP-FPM (
eventsapi-phpfpm) - PHP 8.4 application server - PostgreSQL (
eventsapi-postgres) - Event database with Eloquent ORM - Redis (
eventsapi-redis) - Caching layer for HTTP and coordinate transformations
The system includes dual HTTP-based coordinate transformation services with automatic failover:
- Primary - Remote coordinator service (
HV_COORDINATOR_URL) - Backup - Local Docker coordinator container (
http://coordinator)
Both coordinators support batch processing and Redis caching for optimal performance.
PostgreSQL database configuration:
- Database:
eventsapi - User:
eventsapi - Password:
eventsapi_pass - Host:
postgres(container name) - Port:
5432
Redis configuration:
- Host:
redis(container name) - Port:
6379 - Database:
10
Database migrations are managed using Phinx with environment variable configuration.