ecolod.org › GraphQL

GraphQL endpoint

A read-only GraphQL facade over the ecolod economic-statistics graph (3,544,395 triples of country-level indicators). Queries are translated to SPARQL and run against the vetted /sparql path — no SPARQL knowledge required. No auth, CORS open to every origin, introspection enabled.

Licence & attribution

The data is published under CC BY 4.0. Attribute Team KitaSan / ecolod.org. Questions, corrections and bulk-access requests: info@ecolod.org.

Endpoint

POST https://q.ecolod.org/graphql        Content-Type: application/json   {"query": "...", "variables": {...}}
POST https://q.ecolod.org/graphql        Content-Type: application/graphql (raw query in the body)
GET  https://q.ecolod.org/graphql?query={GraphQL}

Visiting with no query shows this page.

Cookbook

Each line is a transcript of the command above it, run against the store this endpoint serves. Country codes are ISO-3166-1 alpha-3; indicator ids are the slug (gdp-nominal-usd) or the source's own code (NGDPD).

1. A point series — Japan's nominal GDP, 2020-2024, every source

curl -s https://q.ecolod.org/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ series(country:\"JPN\", indicator:\"gdp-nominal-usd\", from:2020, to:2024){ observations { year value status dataset } } }"}'

→ 15 observations — 5 years x 3 datasets. 2024 reads 4190.01 (ecodb-imf-weo), 4190.008 (imf-weo/2026-04) and 4190.00818835857 (wb-wdi/2026-07-13): same number, three precisions. Add dataset:"ecodb-imf-weo" to keep one

2. Top 5 economies (latest actual year, portal dataset)

curl -s https://q.ecolod.org/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ ranking(indicator:\"gdp-nominal-usd\", limit:5){ country { iso3 } value year } }"}'

→ USA 30767.08 / CHN 19626.25 / DEU 5048.06 / JPN 4435.16 / GBR 4003.02, all year 2025 (USD billions). ranking defaults to the ecodb-imf-weo portal dataset — see the Query.ranking description in the SDL for why

3. Multilingual labels — look an indicator up by its IMF code

curl -s https://q.ecolod.org/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ indicator(id:\"NGDPD\"){ slug notation unit label(lang:\"ja\") } }"}'

→ slug = gdp-nominal-usd, notation = NGDPD, unit = usd-billion-current, label = 名目GDP (USドル). indicator(id:) takes either the slug or the skos:notation; every indicator carries prefLabels in all ten portal languages

4. Search the labels in any language

curl -s https://q.ecolod.org/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ search(text:\"失業\"){ label lang indicator { slug } } }"}'

→ 2 hits: 失業率@ja -> unemployment-rate, 若年失業率 (15-24歳)@ja -> youth-unemployment

5. What is in the store? (datasets and their licences)

curl -s https://q.ecolod.org/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ datasets { uri title license issued } }"}'

→ 3 datasets. ecodb-imf-weo (the portal series, 127245 observations) is referenced by its observations but carries no dcterms metadata, so its title/license come back null; imf-weo/2026-04 = "IMF World Economic Outlook (April 2026 edition)" and wb-wdi/2026-07-13 = "World Bank World Development Indicators" (CC BY 4.0)

Three datasets, one graph

The same (country, indicator, year) can carry up to three observations, because the store unions the portal's own series with the IMF and World Bank feeds it is built from:

series and observations return all three, each labelled with its dataset; pass dataset: to scope to one. Short forms ("wb-wdi/2026-07-13") and full IRIs are both accepted, and an unresolvable value is an error rather than an empty list.

Limits

Lists are batched per resolution level, so countries(limit:200){ indicators { label } } costs four upstream queries, not four hundred. A query that still exceeds the call budget fails loudly rather than truncating.

Schema (SDL)

"""
GraphQL facade over the ecolod.org economic-statistics graph (read-only).

Country URIs are https://ecolod.org/country/{ISO-3166-1 alpha-3}; indicator
URIs are https://ecolod.org/concept/indicator/{slug}. Every field is translated
into a bounded SPARQL SELECT and run against the same vetted /sparql path.

The store is a UNION of three qb:DataSets, so one (country, indicator, year)
triple of coordinates can carry up to three observations that differ in
precision and vintage:
  * https://ecolod.org/data/dataset/ecodb-imf-weo      (the portal's own series)
  * https://ecolod.org/data/dataset/imf-weo/2026-04    (IMF WEO, vintaged)
  * https://ecolod.org/data/dataset/wb-wdi/2026-07-13  (World Bank WDI)
Series observations are returned from ALL of them, each labelled with its
dataset; pass `dataset:` to scope to one. Short forms are accepted
("ecodb-imf-weo", "imf-weo/2026-04", "wb-wdi/2026-07-13") as well as full IRIs;
an unresolvable value is an error, never a silently empty result.
"""
type Query {
  """
  One country by ISO-3166-1 alpha-3 code; null when the graph holds no observation for it.
  """
  country(iso3: String!): Country

  """Every country that has at least one observation, ordered by URI."""
  countries(limit: Int = 50, offset: Int = 0): [Country!]!

  """
  One indicator by slug (e.g. "gdp-nominal-usd"), by skos:notation (e.g. "NGDPD"), or by its full concept URI; null if unknown.
  """
  indicator(id: String!): Indicator

  """Every indicator concept, ordered by URI."""
  indicators: [Indicator!]!

  """
  The time series for one (country, indicator) pair. Null only when the country
  or the indicator itself is unknown — a known pair with no data returns a
  Series whose observations list is empty, so \"bad arguments\" stays
  distinguishable from \"no data\". `from`/`to` are inclusive year bounds and
  become the defaults for the nested `observations` field.
  """
  series(country: String!, indicator: String!, dataset: String, from: Int, to: Int): Series

  """
  Countries ranked by an indicator, highest value first.
  
  Semantics: within ONE dataset (default: the ecodb-imf-weo portal dataset),
  take the latest year that has an actual — non-forecast, eco:obsStatus "A" —
  observation, then order that year's values descending. Scoping to a single
  dataset is not a nicety: without it every economy would appear once per
  source, and the three sources disagree in the last digits. The default
  reproduces exactly what the ecolod.org portal pages show.
  """
  ranking(indicator: String!, limit: Int = 20, dataset: String): [RankingEntry!]!

  """
  Case-insensitive substring search over indicator prefLabel/altLabel in any of the 10 portal languages.
  """
  search(text: String!, lang: String, limit: Int = 20): [SearchHit!]!

  """
  Every dataset present in the store, ordered by URI. Metadata fields are null for datasets the store describes only by reference.
  """
  datasets: [Dataset!]!
}

type Country {
  """ISO-3166-1 alpha-3 code (e.g. "JPN")."""
  iso3: String!

  """Full country URI."""
  uri: String!

  """
  Country name in a language. Resolution order: the asked language, then
  English, then the ISO3 code itself. The current bake asserts no triples at all
  about country resources, so this returns the code in practice; the label rungs
  activate automatically if a future bake adds them.
  """
  name(lang: String = "en"): String

  """
  All country labels with their language tags (empty in the current bake — see `name`).
  """
  names: [Label!]!

  """
  Indicators that have at least one observation for this country, ordered by URI.
  """
  indicators(limit: Int = 30): [Indicator!]!
}

type Indicator {
  """Local slug (e.g. "gdp-nominal-usd")."""
  slug: String!

  """Full indicator concept URI."""
  uri: String!

  """skos:notation — the source's own code (e.g. "NGDPD")."""
  notation: String

  """
  Local name of the eco:defaultUnit concept (e.g. "usd-billion-current").
  """
  unit: String

  """skos:prefLabel for a language (default en), falling back to English."""
  label(lang: String = "en"): String

  """
  All skos:prefLabel values with their language tags (10 portal languages).
  """
  labels: [Label!]!

  """skos:definition for a language (default en), falling back to English."""
  definition(lang: String = "en"): String
}

type Label {
  value: String!

  """BCP-47 language tag, or "" for an untagged literal."""
  lang: String!
}

type Series {
  country: Country!
  indicator: Indicator!

  """
  Observations, ordered by (year, dataset). Arguments override the defaults
  inherited from the enclosing `series(...)` call: `from`/`to` are inclusive
  year bounds, `status` is "A" (outturn) or "F" (forecast), `dataset` scopes to
  one source.
  """
  observations(from: Int, to: Int, status: String, dataset: String): [Observation!]!
}

type Observation {
  """Calendar year (eco:timePeriod, an xsd:gYear, parsed to an Int)."""
  year: Int!

  """eco:obsValue."""
  value: Float!

  """eco:obsStatus — "A" = actual/outturn, "F" = forecast."""
  status: String!

  """IRI of the qb:DataSet this number came from."""
  dataset: String!
}

type RankingEntry {
  country: Country!
  value: Float!
  year: Int!
  status: String!
}

type SearchHit {
  """The matched label value."""
  label: String!

  """Language tag of the matched label ("" if untagged)."""
  lang: String!
  indicator: Indicator!
}

type Dataset {
  uri: String!
  title: String
  description: String
  license: String
  issued: String
}

See also