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.
Table reads
Section titled “Table reads”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 anytheme=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.
Facets
Section titled “Facets”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:
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.
Tree reads
Section titled “Tree reads”One organization’s full theory of change as a nested document:
GET /orgs/{id}?expand=outcomes.indicators.reportsGET /orgs/{id}/everything # shorthand for the full expansionexpand 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".
Aggregation
Section titled “Aggregation”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.
Export
Section titled “Export”GET /export?format=jsonld|nquads # everything in the namespaceGET /orgs/{id}/export?format=… # one organization's subgraphThe 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.
