Skip to content

Querying impact data

Graph Cast has two ways to read, matching the two questions a portfolio holder asks: “show me the evidence across everyone” (table reads) and “show me this organization’s model” (tree reads). All the graph joins happen server-side — you receive ready-to-render JSON.

GET /rows/indicator-reports
?theme=<IRI> repeatable — OR within the parameter
&fund=<funder name> repeatable — OR within the parameter
&org=<org id>
&period[from]=2025-01-01
&period[to]=2025-12-31
&limit=50&cursor=<opaque>

Different parameters AND together; repeating one parameter ORs its values. Each row is fully denormalized:

{
"data": [{
"object": "indicator_report", "id": "",
"value": 94, "value_text": "94", "unit": "%",
"period_start": "2025-01-01", "period_end": "2025-12-31",
"themes": ["https://metadata.un.org/sdg/1"],
"indicator": {"id": "", "name": "Tenant retention rate (%)"},
"outcome": {"id": "", "name": "Housing stability"},
"org": {"id": "", "name": "Greenfield Housing Co-op"}
}],
"meta": {"row_count": 1, "cursor": null, "cids_version": "cids-3.0", "generated_at": ""}
}

Pagination: pass meta.cursor back as ?cursor=; null means done.

Period semantics — overlap. period[from]/period[to] match any report whose period intersects the range, so an annual report spanning a year-end appears in both years’ queries. That matches the “what happened during FY2025” intuition.

Theme semantics. A row’s themes collects themes attached to the indicator’s outcome and directly to the indicator (both are legal CIDS). Three consequences worth knowing before building a dashboard:

  • An outcome with two themes puts its reports under both — theme-group totals can legitimately exceed the report total.
  • Reports whose indicator links to no outcome (and carries no theme of its own) have themes: [] and are unreachable by any theme= filter. The themes facet tells you how many exist.
  • Theme filter values are IRIs, URL-encoded. Nobody types them — copy them from the facet endpoint.

Fund semantics. fund= matches reports from organizations that have an sff:FundingStatus naming this funder. See modelling funds.

Small endpoints that answer “what can I filter by?” — the feed for any dashboard’s filter pickers, with values copy-paste ready for the corresponding filter:

Terminal window
GET /rows/themes
# → {"data": [{"theme": "https://metadata.un.org/sdg/11", "report_count": 39}, …],
# "meta": {"total_reports": 2030, "unthemed_reports": 12}}
GET /rows/funds
# → {"data": [{"fund": "Horizon Impact Fund", "org_count": 11, "report_count": 143}, …]}

meta.unthemed_reports counts the reports no theme filter can reach.

One organization’s full theory of change as a nested document:

GET /orgs/{id}?expand=outcomes.indicators.reports
GET /orgs/{id}/everything # shorthand for the full expansion

expand takes comma-separated dotted paths (a path implies its prefixes): outcomes, outcomes.indicators, outcomes.indicators.reports, indicators, indicators.reports. Without expand, GET /orgs/{id} returns the plain organization.

{
"object": "organization", "id": "", "uri": "",
"name": "Greenfield Housing Co-op",
"outcomes": [{
"object": "outcome", "name": "Housing stability",
"themes": ["https://metadata.un.org/sdg/1"],
"indicators": [{
"object": "indicator", "name": "Tenant retention rate (%)", "unit": "%",
"reports": [{"value": 94, "value_text": "94",
"period_start": "2025-01-01", "period_end": "2025-12-31"}]
}]
}],
"indicators": []
}

An indicator appears exactly once per tree. Indicators linked to an outcome nest under it; indicators with no outcome link (optional in CIDS) surface in the organization-level indicators array rather than being dropped. Every object self-identifies via "object".

Rows are small and fully denormalized on purpose: group-by is a few lines client-side, and qualitative reports (value: null) stay visible instead of skewing sums. For example, numeric totals per theme:

import collections, requests
rows = requests.get(f"{BASE}/rows/indicator-reports?limit=500",
headers=headers).json()["data"]
totals = collections.defaultdict(float)
for r in rows:
if r["value"] is not None:
for theme in r["themes"]:
totals[theme] += r["value"]

Server-side aggregation is planned.

GET /export?format=jsonld|nquads # everything in the namespace
GET /orgs/{id}/export?format=… # one organization's subgraph

The organization-scoped export selects the org, its outcomes and indicators, their reports and measure values, plus anything pointing at the org via forOrganization (profiles, funding statuses). Exports return the exact identifiers that were submitted.