Skip to content
worldgovdata
Documentation / Endpoints

Endpoints

Data

Raw observations — one country, one indicator, one year, one value — as JSON or as a streaming CSV. This is the endpoint that costs credits, and the only one whose price varies.

GET/v1/data
Auth
Bearer key required
Cost
max(1, ceil(rows / 100))
Returns
Observation rows, JSON or CSV
Order
iso3, then year
Row cap
100,000 per request
Paging
None — narrow the query instead

Base https://api.worldgovdata.com/v1

No pagination, a hard row cap, and a price set by what actually came back. Read the two notes on the cap and on CSV before you build against it.

Parameters

Query parameters

Query parameters
ParameterDescription
indicatorstring · requiredThe indicator id. Exact and case-sensitive. Resolve it with /v1/indicators?q=…, which is free.example: wgi.control_of_corruption
countriescomma-separated ISO3Restrict to these countries. Codes are trimmed and upper-cased for you, and empty entries are ignored, so a trailing comma is harmless. Omit it and you get every country that has the series — which is usually what trips the row cap.default: all countries · example: IND,CHN,USA
frominteger yearEarliest year, inclusive.default: the start of the series · example: 2000
tointeger yearLatest year, inclusive.default: the end of the series · example: 2024
formatjson | csvResponse format. csv streams a five-column file with no envelope — and therefore no licence or attribution.default: json · example: csv

indicator is the only required parameter. Everything else narrows the result, which is how you control both the row count and the cost.

JSON

Request

curl -sS "https://api.worldgovdata.com/v1/data?indicator=wgi.control_of_corruption&countries=IND,CHN,USA,DEU,JPN&from=2000&to=2024" \
  -H "Authorization: Bearer $WGD_API_KEY"

Response · 200

{
  "data": [
    { "iso3": "CHN", "year": 2000, "value": 47.6, "ci_lo": 38.1, "ci_hi": 57.0 },
    { "iso3": "CHN", "year": 2001, "value": 48.2, "ci_lo": 38.9, "ci_hi": 57.4 },
    // … CHN through 2024, then DEU, IND, JPN, USA — ordered by iso3, then year
    { "iso3": "USA", "year": 2024, "value": 87.1, "ci_lo": 78.4, "ci_hi": 94.2 }
  ],
  "meta": {
    "generated_at": "2026-07-28T09:14:02.481932+00:00",
    "indicator": "wgi.control_of_corruption",
    "count": 125,
    "licence": "CC BY 4.0",
    "attribution": "World Bank — Worldwide Governance Indicators",
    "from": 2000,
    "to": 2024
  }
}

Row fields

Fields of an observation row
FieldTypeNotes
iso3stringISO 3166-1 alpha-3 country code.
yearintegerThe year the observation is for.
valuenumber | nullThe measurement. Null where the provider published a row with no value.
ci_lonumber | nullLower bound of the confidence interval, where the provider gives one.
ci_hinumber | nullUpper bound. Null for the many series that carry no interval.

meta

  • indicator — echoed back.
  • count — rows in this response. This is the number the credit charge was computed from, so you can always reconcile a bill against a response.
  • licence and attribution — the terms for this series. Carry them with the rows; see Licensing.
  • from and to — echoed only if you sent them.

CSV

format=csv streams rather than buffering, which usually matters more than the size of the response itself. The header row is fixed and the columns are in this order:

text/csv

iso3,year,value,ci_lo,ci_hi
CHN,2000,3679.4,,
CHN,2001,4017.9,,
CHN,2002,4402.1,,
IND,2000,2358.7,,
IND,2001,2441.0,,
USA,2000,49951.3,,
USA,2001,50628.8,,
  • Sent as text/csv with Content-Disposition: attachment; filename="<indicator>.csv", so a browser downloads it rather than rendering it.
  • Nulls are empty fields, not the string NULL and not 0. Configure your reader accordingly — the trailing empty columns in the sample above are a series with no confidence interval.
  • Identical cost, identical row cap, identical ordering.

What it costs

The formula

cost = max(1, ceil(rows_returned / 100))

  40 rows   → max(1, ceil(0.4))  = 1
 125 rows   → max(1, ceil(1.25)) = 2
5325 rows   → max(1, ceil(53.25)) = 54
  • Charged on rows returned, not rows requested. A generous query that happens to match little is cheap.
  • Every error costs 0 — including too_many_rows, because the count runs before any data is read.
  • Each request rounds up independently, so many small requests cost slightly more than one large one. Credits works this through.

The row cap

A single request will not return more than 100,000 rows. Over that, the request is refused with the real count in the message, so you know exactly how far over you are.

too_many_rows · 400

HTTP/1.1 400 Bad Request

{
  "error": {
    "code": "too_many_rows",
    "message": "Query matches 412903 rows, over the 100000 limit. Narrow by countries or year range."
  }
}

There is no page 2. You narrow the query — fewer countries, or a shorter year range — and issue it again. Pagination has a complete chunking recipe that splits on countries first and years second, and halves adaptively if the estimate was wrong.

In practice the cap is generous: one indicator across every country and every year it holds is usually well under it. It bites on the deep-history series, some of which carry centuries.

Straight to a file

Download as CSV

curl -sS "https://api.worldgovdata.com/v1/data?indicator=wdi.gdp_per_capita_ppp&countries=IND,CHN,USA&from=2000&to=2024&format=csv" \
  -H "Authorization: Bearer $WGD_API_KEY" \
  -o gdp_per_capita.csv

Errors

  • 400 bad_requestindicator missing. The details array names it.
  • 400 bad_formatformat was neither json nor csv. Case is normalised first, so CSV is accepted.
  • 400 bad_rangefrom is greater than to. Only checked when you send both.
  • 400 too_many_rows — over the cap. Costs nothing.
  • 401 unauthorized — no key.
  • 402 insufficient_credits — balance below the minimum. Nothing was served and nothing was charged.
  • 403 display_only — the indicator’s licence permits display but not export. Permanent, and not fixable by topping up.
  • 404 indicator_not_found — no such id, or a series we may not redistribute at all.

display_only · 403

HTTP/1.1 403 Forbidden

{
  "error": {
    "code": "display_only",
    "message": "Indicator 'oecd.example_series' is display-only: viewable on the portal with attribution, but not available for CSV/JSON export."
  }
}

An unknown country code is not an error. Codes that match nothing simply contribute no rows, so countries=IND,XXX returns India’s rows with a 200. If a response is smaller than you expected, check your codes against /v1/countries. A year range outside the series behaves the same way: an empty array, a count of 0 — and, because there is a minimum, one credit.

Notes and gotchas

  • Ordering is stable. Always iso3 then year, ascending. There is no sort parameter, and none is needed — the result is deterministic.
  • No pagination, on purpose. Offsets over an unbounded observation table are slow and fragile; a bounded query with a hard cap is neither. Chunk on the axes the data has.
  • Only the open tier is exportable. Rankings and country detail will serve you a display-tier value; this endpoint will not. That is the difference between displaying and redistributing.
  • Cache it yourself. Because this route needs a key, the response comes back private, no-store — it carries your balance, so no shared cache may hold it. Cache it inside your own application instead: the pipeline behind it runs daily at fastest, and a response you did not re-request costs no credit.
  • Prefer latest_year to yr_max. A few series carry projection years. Bounding to with coverage.latest_year keeps forecasts out of a dataset that is meant to be observed.
  • Reconcile from meta.count. It is exactly the number the charge was computed from, so a surprising bill is one division away from being explained.
  • /v1/rankings — if what you want is every country for one year, this is a flat 1 credit and arrives already ordered.
  • Pagination — the full-panel chunking recipe.
  • Credits — the cost formula, worked through.
  • Licensing — what you owe the provider.