Identifiers — bring your own URIs
Graph Cast is URI-agnostic: it never assigns, rewrites, or normalizes entity identifiers. Every entity — organization, outcome, indicator, indicator report, and anything else a capsule carries — is identified by an IRI you supply, stored verbatim, and returned verbatim in every read and export.
This will likely surprise you if you’re used to server-assigned IDs. The rules:
The contract
Section titled “The contract”- Every entity carries its own IRI. In a capsule that’s the
@idon each node; in the CRUD lane it’s the requiredurifield on every create. Creating without one is an error (422), and a capsule containing a blank-node core entity (no@id) is rejected with a message saying so. - Identity is the IRI, exactly. Same IRI, either lane → one entity; re-submitting it replaces its properties (an upsert). A different IRI — even with an identical legal name — is a different entity. IRIs are compared byte-for-byte: case, trailing slashes, and whitespace all matter.
- Graph Cast will not guess that two IRIs refer to the same thing. Reconciliation is the data owner’s call, never the platform’s.
- IRIs never change. Updates (
PUT) modify properties — a rename is a label change. There is no operation that moves an entity to a new IRI.
What this means in practice
Section titled “What this means in practice”Use the organization’s own IRI everywhere. If a social purpose
organization publishes capsules, its capsule @id
(e.g. https://greenfield.example/Organization/ghc) is its
identity. When a funder pre-creates that organization via CRUD — for
example to attach form-based data before the first capsule arrives — it
should create it under that same IRI, so everything converges on one
entity:
curl -X POST $BASE/orgs \ -H "X-API-Key: $KEY" -H "X-Namespace: $NS" \ -H "Content-Type: application/json" \ -d '{"uri": "https://greenfield.example/Organization/ghc", "name": "Greenfield Housing Co-op"}'Pick stable, boring URIs for data you originate. For form or
spreadsheet intake where no upstream IRI exists, mint your own under a
domain you control and never change them —
https://yourplatform.example/org/{slug},
…/report/{indicator}-{year}. Stable report URIs make re-imports and
corrections free: same URI, new value, old value replaced.
The failure mode to design against: the same real-world organization loaded under two IRIs becomes two organizations, silently splitting its data across portfolio rollups. Agree on one IRI per entity across your intake paths before loading from multiple sources.
Where REST ids come from
Section titled “Where REST ids come from”URLs like /orgs/{id} use an opaque id rather than the full IRI. The id
is derived from the IRI — the first 128 bits of SHA-256 over the exact
IRI string, base32-encoded (26 characters) — so it is stable and
computable client-side without asking the server:
import base64, hashlibdef graph_cast_id(iri: str) -> str: digest = hashlib.sha256(iri.encode()).digest()[:16] return base64.b32encode(digest).decode().lower().rstrip("=")Every API response includes both id and uri, so in practice you read
ids from responses; the formula matters only when you want to address an
entity knowing nothing but its IRI.
Valid entity URIs
Section titled “Valid entity URIs”An entity URI must be an absolute http(s) IRI without characters that are
illegal in IRIs (spaces, quotes, angle brackets). Anything else is rejected
with a 400 explaining the problem. External codelist references — SDG
IRIs, population-served codes, units — are stored verbatim as opaque
identifiers, exactly as CIDS intends.
