Cross-voting analysis REST API for programmatic access to election data.
The Right Steps Victory Election Analytics API provides programmatic access to cross-voting analysis data. Use it to query how voters who supported a specific candidate in one race voted in other races on the same ballot.
The API is read-only and returns JSON responses. All endpoints require authentication via an API key.
All API requests must include a valid API key in the X-API-Key HTTP header:
X-API-Key: your_api_key_here
API keys are SHA-256 hashed on the server. Only the first 8 characters (prefix) are stored in plaintext for identification. Keep your full key secure—it cannot be recovered if lost.
Keys can be deactivated or expired by an administrator at any time. Usage is tracked per request.
https://victory.hardingps.com/api/v1/election
All endpoint paths below are relative to this base URL.
/datasets
Returns all completed election datasets available for analysis.
Response:
{
"datasets": [
{
"id": 1,
"name": "2024 Primary Election",
"description": "Primary election CVR data",
"election_date": "2024-03-19",
"election_type": "primary",
"total_records": 150000,
"status": "completed"
}
]
}
/datasets/{dataset_id}/contests
Returns all contests and their choices for a specific dataset. Use this to discover valid contest and choice IDs for cross-voting queries.
| Parameter | Type | In | Description |
|---|---|---|---|
dataset_id | integer | path | The ID of the dataset |
Response:
{
"dataset_id": 1,
"dataset_name": "2024 Primary Election",
"contests": [
{
"id": 1,
"name": "Presidential Race",
"position": 0,
"choices": [
{"id": 1, "name": "Candidate A", "vote_count": 50000},
{"id": 2, "name": "Candidate B", "vote_count": 45000}
]
}
]
}
/datasets/{dataset_id}/analysis/cross-voting
Analyzes how voters who chose specific candidates in source races voted in target comparison races. Supports querying multiple source contest/choice pairs in a single request.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
dataset_id | integer | path | Yes | The ID of the dataset |
source_contest_ids | string | query | Yes* | Comma-separated source contest IDs (e.g., 1,2,3) |
source_choice_ids | string | query | Yes* | Comma-separated source choice IDs matching each contest (e.g., 10,20,30) |
compare_contest_ids | string | query | Yes | Comma-separated target contest IDs to compare against (e.g., 4,5) |
*Legacy parameters primary_contest_id and primary_choice_id are also accepted for single-source requests.
Single-source response:
{
"dataset_id": 1,
"dataset_name": "2024 Primary Election",
"primary_contest": {"id": 1, "name": "Presidential Race"},
"primary_choice": {"id": 10, "name": "Candidate A"},
"total_matching_voters": 50000,
"comparisons": [
{
"contest_id": 4,
"contest_name": "Senate Race",
"distribution": [
{"choice": "Senator X", "count": 30000, "percentage": 60.0},
{"choice": "Senator Y", "count": 18000, "percentage": 36.0},
{"choice": "UNDERVOTE", "count": 2000, "percentage": 4.0}
]
}
]
}
Multi-source response:
{
"dataset_id": 1,
"dataset_name": "2024 Primary Election",
"results": [
{
"source_contest": {"id": 1, "name": "Presidential Race"},
"source_choice": {"id": 10, "name": "Candidate A"},
"total_matching_voters": 50000,
"comparisons": [
{
"contest_id": 4,
"contest_name": "Senate Race",
"distribution": [
{"choice": "Senator X", "count": 30000, "percentage": 60.0}
]
}
]
}
]
}
# List all datasets
curl -H "X-API-Key: your_api_key" \
https://victory.hardingps.com/api/v1/election/datasets
# Get contests for a dataset
curl -H "X-API-Key: your_api_key" \
https://victory.hardingps.com/api/v1/election/datasets/1/contests
# Cross-voting analysis (single source)
curl -H "X-API-Key: your_api_key" \
"https://victory.hardingps.com/api/v1/election/datasets/1/analysis/cross-voting?\
source_contest_ids=1&source_choice_ids=10&compare_contest_ids=4,5"
# Cross-voting analysis (multiple sources)
curl -H "X-API-Key: your_api_key" \
"https://victory.hardingps.com/api/v1/election/datasets/1/analysis/cross-voting?\
source_contest_ids=1,2&source_choice_ids=10,20&compare_contest_ids=4,5"
import requests
API_KEY = "your_api_key"
BASE_URL = "https://victory.hardingps.com/api/v1/election"
headers = {"X-API-Key": API_KEY}
# List datasets
response = requests.get(f"{BASE_URL}/datasets", headers=headers)
datasets = response.json()
# Get contests for a dataset
dataset_id = 1
response = requests.get(
f"{BASE_URL}/datasets/{dataset_id}/contests",
headers=headers
)
contests = response.json()
# Cross-voting analysis
params = {
"source_contest_ids": "1",
"source_choice_ids": "10",
"compare_contest_ids": "4,5"
}
response = requests.get(
f"{BASE_URL}/datasets/{dataset_id}/analysis/cross-voting",
headers=headers,
params=params
)
analysis = response.json()
print(analysis)
const API_KEY = "your_api_key";
const BASE_URL = "https://victory.hardingps.com/api/v1/election";
const headers = { "X-API-Key": API_KEY };
// List datasets
const datasets = await fetch(`${BASE_URL}/datasets`, { headers })
.then(res => res.json());
// Get contests
const datasetId = 1;
const contests = await fetch(
`${BASE_URL}/datasets/${datasetId}/contests`, { headers }
).then(res => res.json());
// Cross-voting analysis
const params = new URLSearchParams({
source_contest_ids: "1",
source_choice_ids: "10",
compare_contest_ids: "4,5"
});
const analysis = await fetch(
`${BASE_URL}/datasets/${datasetId}/analysis/cross-voting?${params}`,
{ headers }
).then(res => res.json());
console.log(analysis);
The API uses standard HTTP status codes. Errors return a JSON body with error and message fields.
| Code | Meaning | Common Cause |
|---|---|---|
| 200 | Success | Request completed successfully |
| 400 | Bad Request | Missing or invalid query parameters |
| 401 | Unauthorized | Missing, invalid, or expired API key |
| 404 | Not Found | Dataset or contest ID does not exist |
| 500 | Server Error | Unexpected internal error |
Error response format:
{
"error": "Bad Request",
"message": "Missing required parameter: source_contest_ids"
}