Working with capsules
An impact data capsule is a complete CIDS/SFF JSON-LD document — the interchange unit of the Common Approach ecosystem. Graph Cast ingests capsules whole: validated, stored, and removed as one unit.
Upload and poll
Section titled “Upload and poll”Processing is asynchronous — POST returns immediately with 202 and a
capsule id; poll until the status is terminal:
curl -X POST $BASE/capsules \ -H "X-API-Key: $KEY" -H "X-Namespace: $NS" \ -H "Content-Type: application/json" \ --data-binary @capsule.jsonld# → {"capsule_id": "…", "status": "pending"}
curl -H "X-API-Key: $KEY" -H "X-Namespace: $NS" \ $BASE/capsules/{capsule_id}# → {"status": "accepted", "triple_count": 315, "validation": {…}}| Status | Meaning |
|---|---|
pending |
queued or processing |
accepted |
validated and stored; entities are queryable |
rejected |
validation failed; nothing was stored |
deleted |
rolled back via DELETE /capsules/{id} |
Capsules up to 10 MiB are accepted. Both common shapes parse: a
top-level {"@context": …, "@graph": […]} document, or a bare JSON array
of node objects each carrying its own @context. The official Common
Approach context URLs are resolved from bundled copies — ingest never
fetches over the network.
Validation
Section titled “Validation”Capsules are validated against a focused subset of the official CIDS Basic Tier SHACL shapes. Violations reject the whole capsule and store nothing; warnings are accepted and reported. The validation report is written in plain language, intentionally suitable for forwarding verbatim to the organization that produced the file:
{"severity": "violation", "message": "An organization in the capsule has no legal name…", "focus": "https://vendor.example/Organization/acme"}What rejects a capsule (severity: violation):
- An organization without exactly one non-empty legal name
(
org:hasLegalName). - An outcome or indicator without exactly one non-empty name
(
org:hasName). - An outcome whose
cids:forThemevalues are not IRIs — themes must be codelist IRIs (e.g. an SDG URL), not text values or blank nodes. - An indicator report missing
cids:forIndicator, missing a value (i72:value→i72:Measure→i72:hasNumericalValue), or missing its reporting period (prov:startedAtTime/prov:endedAtTime).
What’s reported as a warning (capsule still accepted):
- An organization without a description.
- An indicator that doesn’t reference the outcome it measures
(
cids:forOutcome).
Three structural rules beyond the shapes:
- Every core entity must carry its own
@id(organization, outcome, indicator, indicator report). Blank nodes are rejected — Graph Cast never assigns identifiers. - Every outcome and indicator must be linked to an organization within
the capsule — via
hasOutcome/hasIndicatoron the organization, orforOrganizationon the entity. Unlinked entities could never be attributed to an organization in portfolio reads. - Every identifier must be a valid absolute IRI. Spaces, backticks,
quotes, angle brackets, or stray newlines inside an
@idor reference — common artifacts of spreadsheet exports — reject the capsule with each offending identifier named verbatim, so the producer knows exactly what to fix. Graph Cast never repairs identifiers on your behalf.
Graph Cast does not enforce every constraint in the Basic Tier file:
cids:unitDescription on indicators, org:hasName and
cids:forOrganization on indicator reports, the required Address fields,
and the xsd:dateTime typing of report periods are accepted as-is today.
A capsule that conforms to the official Basic Tier shapes passes Graph
Cast validation, with one deliberate exception: entities and themes
carried as blank nodes are rejected, because every entity must have
its own IRI.
Everything else the capsule carries — organization profiles, addresses, people, team and EDG profiles, funding statuses, characteristics — is stored verbatim, included in exports, and preserved even though it has no dedicated CRUD endpoints yet.
Report values: numeric and qualitative
Section titled “Report values: numeric and qualitative”CIDS carries report values as strings (i72:hasNumericalValue is
xsd:string in the official shapes), and real-world capsules routinely
contain qualitative values — “Yes”, narrative text. Graph Cast keeps both
readings, everywhere a report appears:
"value": 94, // parsed decimal — null when the string isn't numeric"value_text": "94" // the verbatim CIDS string — always presentQualitative reports are never dropped from table reads; they surface
with value: null. When aggregating, sum value and skip nulls.
Rollback
Section titled “Rollback”curl -X DELETE $BASE/capsules/{capsule_id} \ -H "X-API-Key: $KEY" -H "X-Namespace: $NS" # → 204Everything that capsule asserted is removed in one atomic operation. Entities asserted by multiple capsules survive until the last asserting capsule is deleted. Entities created via CRUD are untouched by capsule rollback (and vice versa: CRUD deletes don’t remove capsule-sourced data — capsules are removed only by rollback).
Modelling funds
Section titled “Modelling funds”To make an organization filterable by fund, submit a small capsule of
sff:FundingStatus nodes whose forOrganization points at the
organization’s IRI:
[{"@context": ["https://ontology.commonapproach.org/contexts/cidsContext.jsonld", "https://ontology.commonapproach.org/contexts/sffContext.jsonld"], "@type": "sff:FundingStatus", "@id": "https://yourfund.example/funding-status/ghc-2026", "forOrganization": "https://greenfield.example/Organization/ghc", "forFunder": "Horizon Impact Fund", "hasFundingState": "https://codelist.commonapproach.org/FundingState#Invested"}]The fund= filter and the /rows/funds facet read these directly. Note
forFunder is a string in the official SFF context, so fund identity is
exact-string matching — spell funder names consistently.
