DCI Overview
Contents
DCI Overview#
For: developers
Understand the Digital Convergence Initiative (DCI) architecture and how OpenSPP implements it — the three-part message envelope, HTTP Signature authentication, registry types, and interaction patterns (sync/async search, subscribe/notify).
Prerequisites#
Familiarity with REST APIs and JSON
Basic understanding of OAuth 2.0 client credentials flow
Optional: familiarity with HTTP Message Signatures (RFC 9421 / draft-cavage)
What is DCI?#
The Digital Convergence Initiative (DCI) is a set of open API standards designed for interoperability between social protection systems. DCI enables governments and organizations to:
Exchange beneficiary data between different registries
Avoid duplication across programs
Enable data portability and consent-based sharing
Standardize communication between CRVS, social registries, and program management systems
Why DCI Matters for OpenSPP#
OpenSPP is often deployed as part of a larger ecosystem of government systems:
System Type |
Example |
Integration Need |
|---|---|---|
Civil Registration |
National CRVS |
Import birth/death records to update registry |
Beneficiary Registries |
National IBR |
Check if person is enrolled elsewhere to prevent duplication |
MIS/Dashboards |
Ministry dashboard |
Query OpenSPP registry for reporting and analytics |
Disability Registries |
National DR |
Query disability status for eligibility targeting |
Other Social Registries |
Provincial registries |
Federated beneficiary lookups |
Without DCI, each integration requires custom API development. With DCI, OpenSPP uses standardized protocols that work across different systems.
DCI Architecture#
graph TB
subgraph "OpenSPP as DCI Server"
A[External System] -->|DCI Search Request| B[DCI Server Module]
B -->|Query| C[OpenSPP Registry]
C -->|Results| B
B -->|DCI Response| A
end
subgraph "OpenSPP as DCI Client"
D[DCI Client Module] -->|DCI Search Request| E[External Registry]
E -->|Results| D
D -->|Import| F[OpenSPP Registry]
end
Key Concepts#
Three-Part Message Envelope#
Every DCI message has three parts:
{
"signature": "Signature: namespace=\"dci\", kidId=\"...\", ...",
"header": {
"version": "1.0.0",
"message_id": "uuid",
"action": "search",
"sender_id": "openspp.example.org",
"receiver_id": "crvs.national.gov"
},
"message": {
"transaction_id": "uuid",
"search_request": [...]
}
}
Signature - HTTP Signature for message authenticity (Ed25519 or RSA)
Header - Metadata about the message (sender, receiver, action)
Message - The actual request/response payload
Registry Types#
DCI defines standard registry types:
Registry Type |
Code |
OpenSPP Role |
Purpose |
|---|---|---|---|
Social Registry |
|
Server |
Expose beneficiary/household data |
CRVS |
|
Client |
Import birth/death events |
IBR |
|
Client |
Check enrollments in other programs |
Disability Registry |
|
Client |
Query disability status |
Farmer Registry |
|
Server |
Expose farmer registrants |
Interaction Patterns#
Synchronous Search:
sequenceDiagram
Client->>Server: POST /registry/sync/search
Note over Server: Process immediately
Server-->>Client: 200 OK with results
Asynchronous Search:
sequenceDiagram
Client->>Server: POST /registry/search (with callback_uri)
Server-->>Client: 202 Accepted
Note over Server: Process in background via queue_job
Server->>Client: POST {callback_uri}/on-search
Client-->>Server: 200 OK
Subscribe/Notify:
sequenceDiagram
Subscriber->>Registry: POST /registry/subscribe
Registry-->>Subscriber: 202 Accepted
Note over Registry: Event occurs (e.g., new registration)
Registry->>Subscriber: POST {callback_uri}/notify
Subscriber-->>Registry: 200 OK
OpenSPP DCI Architecture#
Module Structure#
spp_dci/ # Core infrastructure
├── models/
│ ├── dci_envelope.py # Message envelope schemas
│ └── dci_signing_key.py # Key management
└── services/
├── signature.py # HTTP signature creation/verification
└── mapper.py # DCI ↔ OpenSPP data mapping
spp_dci_server/ # Server implementation
├── routers/
│ ├── auth.py # OAuth2 token endpoint
│ ├── registry.py # Search/subscribe endpoints
│ └── wellknown.py # JWKS and metadata
└── services/
├── search_service.py # Search logic
└── subscription_service.py # Event subscriptions
spp_dci_client/ # Client implementation
├── models/
│ └── dci_data_source.py # External registry config
└── services/
└── dci_client.py # Generic DCI client
spp_dci_client_crvs/ # CRVS-specific client
spp_dci_client_ibr/ # IBR-specific client
spp_dci_client_dr/ # DR-specific client
Integration with Existing Infrastructure#
DCI builds on OpenSPP's existing API V2 infrastructure:
Component |
Reused From |
Purpose |
|---|---|---|
Authentication |
|
OAuth2 client credentials flow |
External IDs |
|
UUID-based identifiers for data exchange |
Consent Management |
|
Privacy-preserving data sharing |
Background Jobs |
|
Async request processing |
FastAPI |
|
REST endpoint framework |
Use Cases#
Server Use Cases#
1. MIS Reporting Dashboard
A national MIS needs to query OpenSPP for beneficiary counts by region:
# External system queries OpenSPP
response = await dci_client.search_by_expression(
conditions=[
{"attribute": "is_registrant", "operator": "=", "value": True},
{"attribute": "area_id.code", "operator": "=", "value": "REGION_01"}
]
)
2. Inter-Program Coordination
Another program wants to check if households are already enrolled:
# Query OpenSPP IBR server for enrollment status
response = await ibr_client.check_enrollment(
identifier_type="urn:gov:national-id",
identifier_value="12345678"
)
Client Use Cases#
1. CRVS Birth Verification
Verify a registrant's birth against the national CRVS before activation:
from odoo.addons.spp_dci_client_crvs.services.crvs_service import CRVSService
crvs = CRVSService(self.env, data_source_code="crvs_main")
birth = crvs.verify_birth(
identifier_type="urn:gov:national-id",
identifier_value=partner.registry_id_ids[0].value,
)
if birth:
partner.write({"birth_date": birth["date"], "verified_by_crvs": True})
2. Duplication Prevention
Before enrolling in a cash transfer program, check IBR for existing enrollments:
from odoo.addons.spp_dci_client_ibr.services.ibr_service import IBRService
data_source = self.env["spp.dci.data.source"].search(
[("code", "=", "ibr_main")], limit=1,
)
ibr = IBRService(data_source, self.env)
result = ibr.check_duplication(partner)
if result.get("is_duplicate"):
raise UserError(_("Already enrolled in: %s") % result["programs"])
3. Disability Targeting
Query the disability registry for PWD status to determine eligibility:
from odoo.addons.spp_dci_client_dr.services.dr_service import DRService
dr = DRService(self.env, data_source_code="dr_main")
status = dr.get_disability_status(partner)
has_severe_disability = status and any(
score >= 3 for score in status.get("functional_scores", {}).values()
)
Data Schemas#
DCI uses JSON-LD schemas for data exchange:
Person Schema#
{
"@context": "https://schema.spdci.org/core/v1",
"@type": "Person",
"identifier": [
{
"identifier_type": "urn:gov:national-id",
"identifier_value": "12345678"
}
],
"name": {
"given_name": "John",
"surname": "Doe"
},
"sex": "male",
"birth_date": "1990-05-15",
"address": [...],
"phone_number": ["+1234567890"],
"registration_date": "2024-01-15T10:30:00Z"
}
Group/Household Schema#
{
"@context": "https://schema.spdci.org/core/v1",
"@type": "Group",
"group_identifier": [...],
"group_type": "Household",
"group_head_info": {...},
"group_size": 5,
"member_list": [...],
"poverty_score": 0.75
}
Authentication and Security#
OpenSPP's DCI server uses two complementary authentication mechanisms on every request:
1. Bearer token (allowlist)#
Every request must carry Authorization: Bearer <token>. The server validates the token against an allowlist configured in the dci.api_tokens Odoo system parameter (comma-separated values). This is a pre-shared token scheme, not an OAuth 2.0 flow — there is no /oauth/token endpoint on the DCI server that issues tokens dynamically. Administrators rotate tokens by updating the system parameter.
2. HTTP Message Signature (draft-cavage)#
Every request must also carry a signed DCI message envelope. The signature field in the envelope is an HTTP Message Signatures–style parameter string:
namespace="dci", kidId="<sender_id>|<key_id>|<algorithm>",
algorithm="ed25519", created="<unix_ts>", expires="<unix_ts>",
headers="(created) (expires) digest", signature="<base64>"
The signing string covers (created), (expires), and the SHA-256 digest of the compact-JSON-serialized {header, message} pair. The server verifies the signature using the sender's registered public key (from spp.dci.sender.registry) or by fetching the sender's JWKS.
For the exact signing string format, digest computation, and signature-header grammar, see DCI Protocol Details.
Clients may use OAuth 2.0 to authenticate to external registries#
When OpenSPP acts as a DCI client (querying an external registry), the spp.dci.data.source record may be configured to use OAuth 2.0 client-credentials to obtain a bearer token from the external registry's authorization server, then include that token on outbound DCI requests. This is the external registry's choice, not a DCI-spec requirement. See OpenSPP as DCI Client.
Base URL and endpoint prefix#
The DCI server mounts under the FastAPI root_path /dci_api/v1 (configured in spp_dci_server/data/fastapi_endpoint_data.xml). Registry endpoints are then nested under /social/registry/, giving full URLs like:
POST https://openspp.example.org/dci_api/v1/social/registry/sync/searchGET https://openspp.example.org/dci_api/v1/.well-known/jwks.json
Illustrative request#
# NOTE: requires a signed DCI envelope body; this is a simplified example
curl -X POST https://openspp.example.org/dci_api/v1/social/registry/sync/search \
-H "Authorization: Bearer <your-allowlisted-token>" \
-H "Content-Type: application/json" \
-d @signed_search_request.json
For the complete signed-envelope structure, see DCI Protocol Details.
What's next#
OpenSPP as DCI Server — implement OpenSPP as a DCI server
OpenSPP as DCI Client — integrate with external DCI registries
DCI Protocol Details — detailed protocol specifications
openspp.org