Loading today's issue...
Developers

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

GETWhat it returns
/contestsPublished 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}/candidatesThe ballot list.
/contests/{slug}/partiesParties fielding candidates, with colours, logos (and credits) and sources.
/contests/{slug}/resultsRounds and results carried by an approved source. kind=historical gives past results by state/county/circle.
/contests/{slug}/projectionsPublished Monday (support picture) and Friday (forecast) runs, newest first. kind=monday|friday.
/contests/{slug}/projections/{run_id}One published run.
/contests/{slug}/signalsApproved campaign events and the evidence used in published runs. type=campaign_event|model_evidence.
/contests/{slug}/timelineMilestones, official updates, campaign events, observer reports and integrity events.
/parties/{slug}One party, e.g. ng-apc.
/correctionsEvery published correction, newest first. Filters: contest, scope, since.
/metaVersions, 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

TierRequests per minute
Standard120
Partner600
Broadcast3,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_status next 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 }.

StatusCodeMeaning
400bad_request, key_in_queryA parameter is wrong, or the key was sent in the URL.
401unauthorizedNo key, or the key is not valid.
403forbidden, origin_not_allowedThe key is suspended or revoked, or this website is not on its list.
404not_foundNo such endpoint, or nothing published with that slug or id.
429rate_limitedSlow down; see Retry-After.
5xxinternal, unavailableOur 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.