Elections Data API
Read-only access to the same verified record readers see on Strata-AF™ Elections: contests, candidates, parties, results, published projections, signals and corrections. Version 1 is stable: new fields may be added, nothing is removed or renamed.
Quickstart
Ask for a key (below), then:
curl -s https://api.elections.strata-af.com/v1/contests/ng-2027-pres \ -H "Authorization: Bearer $STRATA_API_KEY"
const res = await fetch('https://api.elections.strata-af.com/v1/contests/ng-2027-pres/projections?kind=friday&limit=1', {
headers: { Authorization: `Bearer ${process.env.STRATA_API_KEY}` },
})
const { data, meta } = await res.json()
// Always show meta.attribution, and label projections with meta.model_status.The full contract is the OpenAPI 3.1 spec: https://api.elections.strata-af.com/v1/openapi.yaml.
Authentication
Send your key in the Authorization: Bearer <key> header. Keys in the URL are refused (400 key_in_query) because URLs end up in logs; if you ever put a key in a URL, ask us to replace it. Keys are stored only as a one-way hash, so we can replace a lost key but never show it again.
Call the API from your server where you can. If you need to call it from a browser, tell us your site’s origins (e.g. https://www.example.com); requests from any other origin are refused.
Keys are issued to publishing partners. Ask your Strata-AF™ Elections contact, with your organisation, a contact, and how you plan to use the data.
Responses
Every response has the same envelope:
{
"data": { … } | [ … ],
"meta": {
"generated_at": "2026-10-21T08:00:00.000Z",
"revision": 18342,
"attribution": { "text": "Source: Strata-AF™ Elections, a Strata Publishing Co product", "url": "https://elections.strata-af.com" },
"model_status": "experimental"
},
"links": { "self": "…", "next": "…" }
}Times are ISO-8601 in UTC, dates are YYYY-MM-DD, countries are ISO 3166-1 alpha-2 codes, and slugs never change. Every record carries revision (goes up by one each time it changes) and updated_at.
model_status is experimental until a contest’s projection model passes its backtest against past elections, then validated. null means the contest has no projection model. projection_stage (structural, calibrating, live) shows how far the model has come.
Endpoints
Base URL https://api.elections.strata-af.com/v1
| GET | What it returns |
|---|---|
/contests | Published contests. Filters: country (NG), year, status, since. |
/contests/{slug} | One contest, e.g. ng-2027-pres. Includes model_status, projection_stage and the winning rule. |
/contests/{slug}/candidates | The ballot list. |
/contests/{slug}/parties | Parties fielding candidates, with colours, logos (and credits) and sources. |
/contests/{slug}/results | Rounds and results carried by an approved source. kind=historical gives past results by state/county/circle. |
/contests/{slug}/projections | Published Monday (support picture) and Friday (forecast) runs, newest first. kind=monday|friday. |
/contests/{slug}/projections/{run_id} | One published run. |
/contests/{slug}/signals | Approved campaign events and the evidence used in published runs. type=campaign_event|model_evidence. |
/contests/{slug}/timeline | Milestones, official updates, campaign events, observer reports and integrity events. |
/parties/{slug} | One party, e.g. ng-apc. |
/corrections | Every published correction, newest first. Filters: contest, scope, since. |
/meta | Versions, the model_status map, attribution rules and rate limits. |
Only published data is available. Drafts, unreviewed signals and projection runs that are not published do not exist for the API: asking for one returns 404.
Pagination
Lists take limit (1–100, default 25) and return links.next while there is more. Follow it as is; the cursor inside is opaque. Many lists also take since to fetch only what changed.
Caching and freshness
Responses carry an ETag; send it back as If-None-Match and you get a 304 when nothing changed. Corrections and newly published runs reach the API within seconds. During voting and counting, contest data is cached for at most 15 seconds; otherwise for up to 5 minutes.
Rate limits
| Tier | Requests per minute |
|---|---|
| Standard | 120 |
| Partner | 600 |
| Broadcast | 3,000 |
Every response reports X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with Retry-After. Cache on your side and use ETags; election-night traffic is what limits are for.
Attribution rules
- Wherever you show the data, show “Source: Strata-AF™ Elections, a Strata Publishing Co product” linked to elections.strata-af.com.
- Show
model_statusnext to any projection, and the word “experimental” where it applies. Never present a projection as a result. - Keep each figure’s confidence (official, verified, expected, disputed, claimed) and its source when you show it. Do not alter figures.
- Show the logo credit with any party logo you display.
- Check local rules before publishing results or projections around voting day; some countries restrict when they can be published.
- When we publish a correction (
/corrections), update your copy.
Errors
Errors use the same envelope with data: null and an error object: { "code": "rate_limited", "message": "…", "status": 429 }.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request, key_in_query | A parameter is wrong, or the key was sent in the URL. |
| 401 | unauthorized | No key, or the key is not valid. |
| 403 | forbidden, origin_not_allowed | The key is suspended or revoked, or this website is not on its list. |
| 404 | not_found | No such endpoint, or nothing published with that slug or id. |
| 429 | rate_limited | Slow down; see Retry-After. |
| 5xx | internal, unavailable | Our side. Retry with backoff. |
Changelog
- 2026-10-21 — v1 released: contests, candidates, parties, results (official and historical), published projections, signals, timeline, corrections, meta.
See also the methodology and the corrections log.