Quickstart
#1. Make a request (no key needed)
curl https://api.sentinara.org/v1/countries/IDNEvery response has the same envelope:
{
"data": { "iso3": "IDN", "name": "Indonesia", "...": "..." },
"meta": {
"request_id": "8d3f…",
"tier": "anonymous",
"data_updated_at": "2026-10-31T06:03:50.174Z",
"attribution": "SENTINARA (sentinara.org) analysis of WHO …",
"license": "CC BY 4.0",
"docs": "https://docs.sentinara.org/reference#country"
}
}#2. List, filter and paginate
curl "https://api.sentinara.org/v1/countries?region=SEAR&sort=-cases&limit=10"List responses add meta.total and links.next. Follow links.next until it is null. See Pagination & filtering.
#3. Get a key for higher limits
Without a key you can make 30 requests per minute. Create a key in a minute, then send it in a header:
curl https://api.sentinara.org/v1/warnings \
-H "Authorization: Bearer $SENTINARA_KEY"Never put the key in the URL; the API rejects that with api_key_in_query.
#4. Use it from your language
// Node.js 18+ (server side: use a secret key)
const res = await fetch('https://api.sentinara.org/v1/indonesia/forecast', {
headers: { Authorization: `Bearer ${process.env.SENTINARA_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).detail);
const { data } = await res.json();
console.log(data.months[0]);import os, requests
r = requests.get(
"https://api.sentinara.org/v1/countries/BRA/series",
params={"from": "2025-01"},
headers={"Authorization": f"Bearer {os.environ['SENTINARA_KEY']}"},
timeout=30,
)
r.raise_for_status()
for row in r.json()["data"]:
print(row["month"], row["cases"])library(httr2)
resp <- request("https://api.sentinara.org/v1/indonesia/annual") |>
req_url_query(format = "csv") |>
req_perform()
annual <- read.csv(text = resp_body_string(resp))#5. Download CSV
Every list endpoint returns CSV with ?format=csv or Accept: text/csv:
curl -o indonesia-series.csv "https://api.sentinara.org/v1/indonesia/series?format=csv"In Google Sheets: =IMPORTDATA("https://api.sentinara.org/v1/indonesia/annual?format=csv").
#6. Cache politely
The data changes once a week. Send the ETag back as If-None-Match and you get 304 Not Modified with no body. Conditional requests still count towards limits, but they are fast and light.