Query layer
Cypher
Data LiveAPI PlannedAsk the graph questions we never anticipated, writing the traversal yourself while we enforce the guardrails, which is something no other election data provider offers.
When to reach for it
REST answers the questions we chose in advance, whereas Cypher answers yours. It earns its keep on multi-hop questions, the sort that would take three joins and a subquery in SQL and cannot be expressed in a flat export at all.
- Every Democratic candidate for county office in Arizona who has filed but not yet qualified for the ballot.
- Contests where an incumbent is unopposed, grouped by jurisdiction tier.
- People who have run for more than one office across cycles, and which offices.
- Which authority administers elections for each municipality inside a given county.
Your first query
Post a statement together with any parameters it needs, and the response comes back as row-oriented JSON.
MATCH (c:Candidacy)-[:RUNS_IN]->(:Contest)-[:FILLS]->(o:Office)
WHERE o.level = 'congressional-district'
MATCH (c)-[:IS_CANDIDACY_OF]->(p:Person)
MATCH (o)-[:SCOPED_TO]->(r:Region)
WHERE r.state_fips = $state_fips
OPTIONAL MATCH (c)-[:NOMINATED_BY]->(party:Party)
RETURN p.name AS person, party.name AS party,
r.name AS district, c.filing_status AS filing_status,
c.confidence AS confidence
ORDER BY district LIMIT 25{
"columns": ["person", "party", "district", "filing_status", "confidence"],
"rows": [
["Amish Shah", "Democratic Party", "Arizona's 1st Congressional District", null, 0.85],
["Monica Alponte", "Libertarian Party", "Arizona's 1st Congressional District", null, 0.85],
["Oren Davis", "Independent", "Arizona's 1st Congressional District", "pending", 0.85],
["Andres Barraza", "Democratic Party", "Arizona's 1st Congressional District", "pending", 0.85]
],
"stats": { "rows": 25, "elapsed_ms": 184, "db_hits": 9412 }
}Guardrails
The endpoint is read-only by construction rather than by convention. Statements are checked and rejected before any database session opens, and the session itself is opened in read-only mode against a replica, so anything that somehow cleared the first check is still refused at the database.
| Rule | Behaviour |
|---|---|
| Write clauses | CREATE, MERGE, SET, DELETE, REMOVE, DROP, DETACH, FOREACH and LOAD CSV are rejected before execution |
| Introspection | CALL db.*, CALL dbms.*, CALL apoc.* and keys() are rejected, since the documented schema is the supported way to learn a node's shape |
| Entitlement check | A statement naming a field your key cannot read is rejected rather than silently emptied |
| Wall clock | Queries exceeding your plan's budget are cancelled and return a timeout |
| Row cap | Results are capped at your plan's limit whatever LIMIT you wrote. Use bulk export for volume |
| Parameters | Pass values via parameters, which lets the database reuse its query plan and is therefore both safer and faster than string concatenation |
| Cardinality | Statements whose estimated cost exceeds your plan's ceiling are refused before any rows are produced |
{
"error": "entitlement_required",
"message": "Statement references 'campaign_emails', which requires the 'contact' entitlement.",
"field": "campaign_emails",
"entitlement": "contact"
}Why Cypher rather than only GraphQL. GraphQL selects fields from a shape we defined, whereas Cypher lets you define the shape yourself. For a dataset whose value lies in its relationships that difference is the whole product, and because it is the language the graph is natively stored in there is no translation layer to lose fidelity in.