' + 'Account & API keys' + 'Sign out'; panel.querySelector('#seoSignOut').addEventListener('click', function (e) { e.preventDefault(); signOut(); }); } else { label.hidden = true; var redir = encodeURIComponent(location.href); panel.innerHTML = '
' + (mode === 'login' ? 'Sign in' : 'Create a free account') + '
' + '
' + 'Continue with Google' + 'Facebook' + '
' + '
or
' + '' + '
' + '' + '' + '' + '
' + '

' + (mode === 'login' ? 'New here? Create a free account · Forgot password?' : 'Have an account? Sign in') + '

'; panel.querySelector('#seoAuthToggle').addEventListener('click', function () { mode = mode === 'login' ? 'register' : 'login'; render(); var f = panel.querySelector('input'); if (f) f.focus(); }); panel.querySelector('#seoAuthForm').addEventListener('submit', submit); } } function submit(e) { e.preventDefault(); var form = e.target, email = form.email.value.trim(), password = form.password.value; var err = panel.querySelector('#seoAuthErr'), sbtn = form.querySelector('.seo-auth-btn'); var labelTxt = mode === 'login' ? 'Sign in' : 'Create account'; err.style.display = 'none'; sbtn.disabled = true; sbtn.textContent = '…'; fetch(apiBase + (mode === 'login' ? '/api/auth/login' : '/api/auth/register'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: password }), }) .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); }) .then(function (res) { if (!res.ok || !res.d.token) { err.textContent = res.d.error || res.d.message || 'Something went wrong. Try again.'; err.style.display = 'block'; sbtn.disabled = false; sbtn.textContent = labelTxt; return; } setToken(res.d.token); location.reload(); }) .catch(function () { err.textContent = 'Network error. Try again.'; err.style.display = 'block'; sbtn.disabled = false; sbtn.textContent = labelTxt; }); } function signOut() { var t = token(); setToken(null); if (t) fetch(apiBase + '/api/auth/logout', { method: 'POST', headers: { 'Authorization': 'Bearer ' + t } }).catch(function () {}); location.reload(); } window.erstatAuth = { open: openPanel, close: closePanel }; // Consume an OAuth return (?token / ?error) on any SEO page. try { var p = new URLSearchParams(location.search), tk = p.get('token'), er = p.get('error'); if (tk) { setToken(tk); history.replaceState({}, '', location.pathname + location.hash); } else if (er) { window.__erstatOAuthError = 'Sign-in failed: ' + er; history.replaceState({}, '', location.pathname + location.hash); } } catch (e) {} var t = token(); if (t) { fetch(apiBase + '/api/auth/me', { headers: { 'Authorization': 'Bearer ' + t } }) .then(function (r) { if (!r.ok) throw 0; return r.json(); }) .then(function (d) { user = d.user || d; if (!panel.hidden) render(); else { label.hidden = false; label.textContent = user.display_name || user.email || 'Account'; } }) .catch(function () { setToken(null); user = null; }); } })();

ERstat API v1

Live Canadian emergency-room data as JSON: closures, reopenings and service disruptions, per-province coverage, and live per-hospital wait times. Free for non-commercial use with attribution; commercial licensing adds wait feeds, history and an SLA.

Base URL https://erstat.ca

All endpoints live under /api/v1, are read-only GET, and are CORS-enabled (Access-Control-Allow-Origin: *) so you can call them straight from a browser. Responses are JSON and carry an attribution string you must surface on the free tier.

Authentication

Every data endpoint requires an API key. Keys are free, start with esk_, and are tied to your ERstat account. Send the key as an X-API-Key header (preferred) or a ?key= query parameter. Requests without a valid key return 401.

curl -H "X-API-Key: YOUR_KEY" https://erstat.ca/api/v1/closures

# or, as a query parameter
curl "https://erstat.ca/api/v1/closures?key=YOUR_KEY"

Create a free API key →

The free tier is for non-commercial use with attribution (a visible link back to erstat.ca). The /waits and /history endpoints require a commercial key — see licensing.

Rate limits & caching

The free tier is rate-limited and served as-is with no SLA; exceeding the limit returns 429. Higher limits, an uptime SLA and webhook/push delivery are part of a commercial licence. Responses are edge-cached, so effective freshness is bounded by the cache window per endpoint:

EndpointCache
GET /api/v1/closures~60 seconds
GET /api/v1/coverage~5 minutes
GET /api/v1/waits~30 seconds
GET /api/v1/history~5 minutes

Endpoints

GET /api/v1/closures Free key

Every Canadian emergency room currently closed or under a service disruption. Edge-cached ~60s.

Query parameters

ParameterTypeRequiredDescription
provincestringnoTwo-letter province/territory code (e.g. ON, NS, AB). Omit for all of Canada.

Request

curl -H "X-API-Key: YOUR_KEY" "https://erstat.ca/api/v1/closures?province=NS"

Response 200 OK

{
  "version": "v1",
  "generated_at": "2026-08-15T14:32:00Z",
  "attribution": "Data from ERstat (https://erstat.ca). Free for non-commercial use with attribution.",
  "count": 1,
  "closures": [
    {
      "id": "ns_rosewayhospital",
      "name": "Roseway Hospital",
      "city": "Shelburne",
      "province": "NS",
      "status": "closed",
      "message": "ER closed until 8:00 a.m. due to physician unavailability.",
      "updated_at": "2026-08-15T02:05:00Z",
      "expected_reopen": "2026-08-15T12:00:00Z"
    }
  ]
}
GET /api/v1/coverage Free key

How many ERs each province has and how many publish a live wait time. Edge-cached ~5 min.

Request

curl -H "X-API-Key: YOUR_KEY" "https://erstat.ca/api/v1/coverage"

Response 200 OK

{
  "version": "v1",
  "generated_at": "2026-08-15T14:32:00Z",
  "attribution": "Data from ERstat (https://erstat.ca). Free for non-commercial use with attribution.",
  "national": { "ers": 812, "reporting_live": 216 },
  "provinces": [
    { "province": "AB", "ers": 104, "reporting_live": 104, "pct": 100 },
    { "province": "ON", "ers": 168, "reporting_live": 61,  "pct": 36 }
  ]
}
GET /api/v1/waits Commercial

Current wait for every tracked ER: a live published reading where a province exposes one, a model prediction elsewhere. Each row carries a basis flag (live, predicted, closed, none) so a consumer degrades gracefully instead of showing a wait it cannot stand behind. Edge-cached ~30s.

Query parameters

ParameterTypeRequiredDescription
provincestringnoTwo-letter province/territory code. Omit for all of Canada.

Request

curl -H "X-API-Key: YOUR_COMMERCIAL_KEY" "https://erstat.ca/api/v1/waits?province=ON"

Response 200 OK

{
  "version": "v1",
  "licensed_to": "Acme Health",
  "generated_at": "2026-08-15T14:32:00Z",
  "attribution": "Data from ERstat (https://erstat.ca).",
  "count": 1,
  "hospitals": [
    {
      "id": "on_uhntoronto",
      "name": "Toronto General Hospital",
      "city": "Toronto",
      "province": "ON",
      "lat": 43.6579, "lng": -79.3873,
      "status": "open",
      "status_message": null,
      "basis": "live",
      "wait_minutes": 142,
      "wait_lower": 120, "wait_upper": 190,
      "live_wait_minutes": 142,
      "predicted_wait_minutes": 155,
      "predicted_confidence": "high",
      "updated_at": "2026-08-15T14:28:00Z",
      "predicted_at": "2026-08-15T14:00:00Z"
    }
  ]
}
GET /api/v1/history Commercial

Historical and bulk wait-time and closure data. Response schema and terms ship with the licence. Without a commercial key the endpoint returns 403.

Query parameters

ParameterTypeRequiredDescription
periodstringnoLook-back window, e.g. 30d, 90d. Defaults to 30d.
provincestringnoTwo-letter province/territory code.

Request

curl -H "X-API-Key: YOUR_COMMERCIAL_KEY" "https://erstat.ca/api/v1/history?period=90d&province=ON"

Response 403 Forbidden (without a commercial key)

{
  "error": "commercial_license_required",
  "message": "Historical and bulk data require a commercial API key. See https://erstat.ca/licensing",
  "licensing": "https://erstat.ca/licensing"
}

Errors

Errors return the HTTP status plus a JSON body of the form { "error": "…", "message": "…" }.

StatusCodeMeaning
200OKRequest succeeded.
401api_key_requiredMissing or invalid API key. Create a free key on your account and send it as X-API-Key.
403commercial_license_requiredEndpoint requires a commercial licence (/waits, /history).
429rate_limitedFree-tier rate limit exceeded. Back off, or move to a commercial key.
500server_errorUnexpected server error.
503history_unavailableThe history service is temporarily unavailable.

FAQ

Is there a Canadian ER closures API?

Yes. The ERstat API returns live emergency-room closures, reopenings and service disruptions across Canada as JSON, plus per-province coverage. Every request needs a free API key tied to your ERstat account.

Is the ERstat API free?

Yes — the JSON API and RSS feed are free for personal, journalistic, academic and other non-commercial use, with attribution (a visible link back to erstat.ca). Commercial use, history and bulk export are available under a paid licence.

How do I authenticate?

Send your key on every request as an X-API-Key header (preferred) or a ?key= query parameter. Requests without a valid key are rejected with HTTP 401.

Is there a rate limit?

Yes, the free tier is rate-limited and provided as-is with no SLA. Higher limits, an uptime SLA and webhooks/push delivery are part of a commercial licence.

Can I use the data commercially?

Not on the free tier. A commercial licence grants commercial-use rights along with historical and bulk data, higher limits, an SLA and support. See the licensing page or get in touch.

Do you have historical or bulk data?

Yes — historical and bulk export are available via the /api/v1/history endpoint under a commercial licence.

Is there a no-code option?

Yes — a free embeddable widget shows live ER status for Canada or a single province with one line of HTML. It needs no key and includes attribution automatically.