API Resources
Contents
API Resources#
For: developers
The core resource types exposed by API V2 (Individual, Group, Program, ProgramMembership, Consent) with their fields, relationships, and CRUD operations.
Prerequisites#
A working API client and OAuth token (see Authentication)
Understanding of External Identifiers — every resource is addressed by
system|valueAwareness of Consent Management — responses are filtered based on consent scope
Available Resources#
OpenSPP API V2 provides five core resources:
Resource |
Description |
Endpoint |
|---|---|---|
Individual |
Person in the registry |
|
Group |
Household or other group |
|
Program |
Social protection program |
|
ProgramMembership |
Enrollment in a program |
|
Consent |
Data sharing consent |
|
Note
Consent records do not follow the standard CRUD pattern. They are only readable individually (GET /Consent/{id}), revocable (POST /Consent/{id}/$revoke), and deletable (DELETE /Consent/{id}). There is no list endpoint or POST /Consent create operation — consents are created through separate grant flows. See Consent Management for details.
Common Patterns#
All resources follow consistent REST patterns:
Operation |
HTTP Method |
Endpoint |
Supported Resources |
|---|---|---|---|
Read |
GET |
|
Individual, Group, Program, ProgramMembership |
Search |
GET |
|
Individual, Group, Program, ProgramMembership |
Create |
POST |
|
Individual, Group, ProgramMembership |
Update (full) |
PUT |
|
Individual, Group, ProgramMembership |
Update (partial) |
PATCH |
|
Individual, Group |
Delete |
DELETE |
|
Consent only |
Note
PUT replaces the entire resource — you must send all fields. PATCH uses JSON Merge Patch (RFC 7396) — only send the fields you want to change. Both support optimistic locking via the If-Match header.
DELETE is not supported for Individual, Group, Program, or ProgramMembership. To remove a person or membership from active use, set active: false via PATCH instead.
Individual Resource#
Represents a person in the social protection registry.
Data Model#
{
"type": "Individual",
"identifier": [
{
"system": "urn:gov:ph:psa:national-id",
"value": "PH-123456789"
}
],
"active": true,
"name": {
"family": "Santos",
"given": "Maria",
"middle": "Dela Cruz",
"text": "SANTOS, Maria Dela Cruz"
},
"birthDate": "1985-03-15",
"birthDateEstimated": false,
"gender": {
"coding": [
{
"system": "urn:iso:std:iso:5218",
"code": "2",
"display": "Female"
}
],
"text": "Female"
},
"telecom": [
{
"system": "phone",
"value": "+639171234567",
"use": "mobile",
"rank": 1
},
{
"system": "email",
"value": "maria.santos@example.com",
"use": "home"
}
],
"address": [
{
"type": "physical",
"text": "123 Rizal St, Barangay 1, Manila",
"line": ["123 Rizal St", "Barangay 1"],
"city": "Manila",
"state": "Metro Manila",
"postalCode": "1000",
"country": "PH"
}
],
"photo": "data:image/jpeg;base64,...",
"groupMembership": [
{
"group": {
"reference": "Group/urn:openspp:group|HH-2024-001",
"display": "Santos Household"
},
"role": {
"coding": [
{
"system": "urn:openspp:vocab:relationship",
"code": "head",
"display": "Head of Household"
}
]
},
"period": {
"start": "2024-01-01"
}
}
],
"extension": {
"farmer": {
"url": "urn:openspp:extension:farmer",
"farmSize": 2.5,
"farmSizeUnit": "hectares",
"primaryCrop": {
"coding": [
{
"system": "urn:fao:agrovoc",
"code": "rice",
"display": "Rice"
}
]
}
}
},
"meta": {
"versionId": "3",
"lastUpdated": "2024-11-28T10:30:00Z",
"source": "urn:openspp:system:field-registration"
}
}
Field Reference#
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Always "Individual" |
|
array |
Yes |
External identifiers (at least one) |
|
boolean |
No |
Whether record is active (default: true) |
|
object |
Yes |
Human name |
|
date |
No |
Birth date (YYYY-MM-DD) |
|
boolean |
No |
Whether birth date is estimated |
|
CodeableConcept |
No |
Gender (ISO 5218) |
|
array |
No |
Contact points (phone, email) |
|
array |
No |
Physical/postal addresses |
|
string |
No |
Base64-encoded photo |
|
array |
No |
Group/household memberships |
|
object |
No |
Module-specific fields |
|
object |
No |
Resource metadata |
Operations#
Read Individual#
GET /api/v2/spp/Individual/urn:gov:ph:psa:national-id|PH-123456789
Authorization: Bearer TOKEN
Query Parameters:
Parameter |
Description |
|---|---|
|
Comma-separated fields to include: |
|
Extensions to include: |
Example: Python
def get_individual(identifier, token, base_url):
"""Fetch an individual by identifier."""
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{base_url}/Individual/{identifier}",
headers=headers
)
response.raise_for_status()
return response.json()
# Usage
individual = get_individual(
identifier="urn:gov:ph:psa:national-id|PH-123456789",
token=token,
base_url="https://{your-domain}/api/v2/spp"
)
Create Individual#
POST /api/v2/spp/Individual
Authorization: Bearer TOKEN
Content-Type: application/json
{
"type": "Individual",
"identifier": [
{
"system": "urn:gov:ph:psa:national-id",
"value": "PH-987654321"
}
],
"name": {
"given": "Juan",
"family": "Dela Cruz"
},
"birthDate": "1990-05-15",
"gender": {
"coding": [
{
"system": "urn:iso:std:iso:5218",
"code": "1",
"display": "Male"
}
]
}
}
Response:
HTTP/1.1 201 Created
Location: /api/v2/spp/Individual/urn:gov:ph:psa:national-id|PH-987654321
Example: Python
def create_individual(data, token, base_url):
"""Create a new individual."""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(
f"{base_url}/Individual",
headers=headers,
json=data
)
response.raise_for_status()
return response.json()
Update Individual#
PUT /api/v2/spp/Individual/urn:gov:ph:psa:national-id|PH-123456789
Authorization: Bearer TOKEN
Content-Type: application/json
If-Match: "3"
{
"type": "Individual",
"identifier": [...],
"name": {...},
...
}
Note: Requires If-Match header with current version ID for optimistic locking.
Partial Update Individual (PATCH)#
Use PATCH to update specific fields without sending the entire resource:
PATCH /api/v2/spp/Individual/urn:gov:ph:psa:national-id|PH-123456789
Authorization: Bearer TOKEN
Content-Type: application/merge-patch+json
If-Match: "3"
{
"telecom": [
{
"system": "phone",
"value": "+639179876543",
"use": "mobile"
}
]
}
Only the specified fields are updated. Omitted fields remain unchanged. Use null to clear a field's value.
Example: Python
def patch_individual(identifier, updates, version_id, token, base_url):
"""Partially update an individual."""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/merge-patch+json",
"If-Match": f'"{version_id}"'
}
response = requests.patch(
f"{base_url}/Individual/{identifier}",
headers=headers,
json=updates
)
response.raise_for_status()
return response.json()
# Update only the phone number
updated = patch_individual(
identifier="urn:gov:ph:psa:national-id|PH-123456789",
updates={"telecom": [{"system": "phone", "value": "+639179876543", "use": "mobile"}]},
version_id="3",
token=token,
base_url=base_url
)
Group Resource#
Represents a household or other group of individuals.
Data Model#
{
"type": "Group",
"identifier": [
{
"system": "urn:openspp:group",
"value": "HH-2024-001"
}
],
"active": true,
"groupType": "household",
"name": "Santos Household",
"quantity": 4,
"member": [
{
"entity": {
"reference": "Individual/urn:gov:ph:psa:national-id|PH-123456789",
"display": "Maria Santos"
},
"role": {
"coding": [
{
"system": "urn:openspp:vocab:relationship",
"code": "head",
"display": "Head"
}
]
},
"period": {
"start": "2024-01-01"
},
"inactive": false
}
],
"address": [
{
"type": "physical",
"text": "123 Rizal St, Barangay 1, Manila",
"city": "Manila",
"state": "Metro Manila",
"country": "PH"
}
],
"meta": {
"versionId": "5",
"lastUpdated": "2024-11-28T10:30:00Z"
}
}
Note
Module-specific Group fields (e.g., household characteristics like children_under_5) are exposed via the extension mechanism rather than core schema fields. Request them with ?_extensions=<extension-name>.
Operations#
Search Groups#
GET /api/v2/spp/Group?groupType=household&name=Santos
Authorization: Bearer TOKEN
Search Parameters:
Parameter |
Type |
Description |
|---|---|---|
|
token |
System|value |
|
token |
Group type: |
|
string |
Group name (contains) |
|
reference |
Has member: |
|
date |
Modified since: |
Example: Python
def search_groups(params, token, base_url):
"""Search for groups."""
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{base_url}/Group",
headers=headers,
params=params
)
response.raise_for_status()
return response.json()
# Usage
results = search_groups(
params={"groupType": "household", "name": "Santos"},
token=token,
base_url="https://{your-domain}/api/v2/spp"
)
Create Group#
POST /api/v2/spp/Group
Authorization: Bearer TOKEN
Content-Type: application/json
{
"type": "Group",
"identifier": [
{
"system": "urn:openspp:group",
"value": "HH-2024-NEW"
}
],
"groupType": "household",
"name": "Dela Cruz Household",
"member": [
{
"entity": {
"reference": "Individual/urn:gov:ph:psa:national-id|PH-987654321"
},
"role": {
"coding": [
{
"system": "urn:openspp:vocab:relationship",
"code": "head"
}
]
}
}
]
}
Program Resource#
Represents a social protection program.
Data Model#
{
"type": "Program",
"identifier": [
{
"system": "urn:openspp:program",
"value": "4Ps"
}
],
"active": true,
"name": "Pantawid Pamilyang Pilipino Program",
"description": "Conditional cash transfer program for poor families",
"programType": {
"coding": [
{
"system": "urn:openspp:vocab:program-type",
"code": "cash_transfer",
"display": "Cash Transfer"
}
]
},
"targetType": "group",
"eligibilityCriteria": "Households with children under 18 and income below poverty line",
"period": {
"start": "2008-02-01"
},
"meta": {
"versionId": "1",
"lastUpdated": "2024-01-01T00:00:00Z"
}
}
Operations#
List Programs#
GET /api/v2/spp/Program?status=active
Authorization: Bearer TOKEN
Example: Python
def list_programs(token, base_url):
"""List active programs."""
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{base_url}/Program",
headers=headers,
params={"status": "active"}
)
response.raise_for_status()
return response.json()
ProgramMembership Resource#
Represents enrollment of a beneficiary in a program.
Data Model#
{
"type": "ProgramMembership",
"identifier": [
{
"system": "urn:openspp:program-membership",
"value": "4PS-2024-001234"
}
],
"program": {
"reference": "Program/urn:openspp:program|4Ps",
"display": "Pantawid Pamilyang Pilipino Program"
},
"beneficiary": {
"reference": "Group/urn:openspp:group|HH-2024-001",
"display": "Santos Household"
},
"status": "enrolled",
"enrollmentDate": "2024-01-15",
"exitDate": null,
"exitReason": null,
"meta": {
"versionId": "2",
"lastUpdated": "2024-01-15T10:00:00Z"
}
}
Status Values#
Status |
Description |
|---|---|
|
Initial state — record created but not yet enrolled |
|
Enrolled in the program (active beneficiary) |
|
Temporarily paused (eligibility suspended, may resume) |
|
Left the program (see |
|
Determined ineligible after evaluation |
|
Marked as duplicate of another membership |
Operations#
Search Enrollments#
GET /api/v2/spp/ProgramMembership?beneficiary=Individual/urn:openspp:vocab:id-type%23national_id|IND-001&status=enrolled
Authorization: Bearer TOKEN
Search Parameters:
Parameter |
Type |
Description |
|---|---|---|
|
reference |
Individual or Group reference |
|
reference |
Program reference |
|
token |
Enrollment status |
Example: Python
def get_program_memberships(beneficiary_ref, token, base_url):
"""Get program enrollments for a beneficiary."""
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
f"{base_url}/ProgramMembership",
headers=headers,
params={"beneficiary": beneficiary_ref, "status": "enrolled"}
)
response.raise_for_status()
return response.json()
# Usage
memberships = get_program_memberships(
beneficiary_ref="Individual/urn:gov:ph:psa:national-id|PH-123456789",
token=token,
base_url="https://{your-domain}/api/v2/spp"
)
for membership in memberships["data"]:
print(f"Enrolled in: {membership['program']['display']}")
print(f"Status: {membership['status']}")
Enroll Beneficiary#
POST /api/v2/spp/ProgramMembership
Authorization: Bearer TOKEN
Content-Type: application/json
{
"type": "ProgramMembership",
"program": {
"reference": "Program/urn:openspp:program|4Ps"
},
"beneficiary": {
"reference": "Group/urn:openspp:group|HH-2024-001"
},
"status": "enrolled",
"enrollmentDate": "2024-11-28"
}
Extension Fields#
Modules can add custom fields via extensions. Request extensions using the _extensions parameter:
GET /api/v2/spp/Individual/...?_extensions=farmer,disability
Available Extensions#
Check the capability statement for available extensions:
GET /api/v2/spp/metadata
Response includes:
{
"extension": [
{
"url": "urn:openspp:extension:farmer",
"module": "spp_farmer_registry",
"appliesTo": ["Individual"],
"fields": ["farmSize", "farmSizeUnit", "primaryCrop", "livestockCount"]
},
{
"url": "urn:openspp:extension:disability",
"module": "spp_disability_registry",
"appliesTo": ["Individual"],
"fields": ["disabilityType", "severity", "assistanceNeeded"]
}
]
}
Using Extensions#
def get_individual_with_extensions(identifier, extensions, token, base_url):
"""Fetch individual with specific extensions."""
headers = {"Authorization": f"Bearer {token}"}
params = {"_extensions": ",".join(extensions)}
response = requests.get(
f"{base_url}/Individual/{identifier}",
headers=headers,
params=params
)
response.raise_for_status()
return response.json()
# Usage
individual = get_individual_with_extensions(
identifier="urn:gov:ph:psa:national-id|PH-123456789",
extensions=["farmer"],
token=token,
base_url="https://{your-domain}/api/v2/spp"
)
if "extension" in individual and "farmer" in individual["extension"]:
farmer_data = individual["extension"]["farmer"]
print(f"Farm size: {farmer_data['farmSize']} {farmer_data['farmSizeUnit']}")
Resource Metadata#
All resources include metadata:
{
"meta": {
"versionId": "3",
"lastUpdated": "2024-11-28T10:30:00Z",
"source": "urn:openspp:system:field-registration"
}
}
Field |
Description |
|---|---|
|
Version number for optimistic locking |
|
Last modification timestamp (ISO 8601) |
|
System that created/modified the resource |
Version Control#
Use the versionId for optimistic locking:
PUT /api/v2/spp/Individual/...
If-Match: "3"
If another client modified the resource, you'll get a 409 Conflict:
{
"type": "urn:openspp:error:conflict",
"title": "Conflict",
"status": 409,
"detail": "Resource version mismatch. Expected: 3, Current: 5"
}
Common mistakes#
Which resource should I use for households?
Use the Group resource with type: "household".
Can I create a ProgramMembership without creating the Individual first?
No. The beneficiary (Individual or Group) must exist before enrollment.
How do I know which extensions are available?
Check the capability statement: GET /api/v2/spp/metadata
Getting 409 Conflict on updates?
Another client modified the resource. Fetch the latest version, merge your changes, and retry with the new versionId.
What's the difference between PUT and PATCH?
PUT replaces the entire resource — you must send all fields. PATCH (JSON Merge Patch, RFC 7396) updates only the fields you send, leaving others unchanged. Both are available for Individual and Group resources.
Complete Example: Full Workflow#
import requests
class OpenSPPAPI:
"""Complete API client example."""
def __init__(self, base_url, token):
self.base_url = base_url
self.token = token
self.headers = {"Authorization": f"Bearer {token}"}
def create_individual(self, identifier, name, birth_date):
"""Create a new individual."""
data = {
"type": "Individual",
"identifier": [identifier],
"name": name,
"birthDate": birth_date
}
response = requests.post(
f"{self.base_url}/Individual",
headers={**self.headers, "Content-Type": "application/json"},
json=data
)
response.raise_for_status()
return response.json()
def create_group(self, identifier, name, members):
"""Create a new group."""
data = {
"type": "Group",
"identifier": [identifier],
"groupType": "household",
"name": name,
"member": members
}
response = requests.post(
f"{self.base_url}/Group",
headers={**self.headers, "Content-Type": "application/json"},
json=data
)
response.raise_for_status()
return response.json()
def enroll_in_program(self, program_ref, beneficiary_ref):
"""Enroll a beneficiary in a program."""
data = {
"type": "ProgramMembership",
"program": {"reference": program_ref},
"beneficiary": {"reference": beneficiary_ref},
"status": "enrolled",
"enrollmentDate": "2024-11-28"
}
response = requests.post(
f"{self.base_url}/ProgramMembership",
headers={**self.headers, "Content-Type": "application/json"},
json=data
)
response.raise_for_status()
return response.json()
# Usage
api = OpenSPPAPI(
base_url="https://{your-domain}/api/v2/spp",
token="YOUR_TOKEN"
)
# Create individual
individual = api.create_individual(
identifier={"system": "urn:gov:ph:psa:national-id", "value": "PH-NEW-001"},
name={"given": "Juan", "family": "Dela Cruz"},
birth_date="1990-05-15"
)
print(f"Created individual: {individual['identifier'][0]['value']}")
# Create household
group = api.create_group(
identifier={"system": "urn:openspp:group", "value": "HH-NEW-001"},
name="Dela Cruz Household",
members=[
{
"entity": {
"reference": f"Individual/urn:gov:ph:psa:national-id|PH-NEW-001"
},
"role": {
"coding": [
{"system": "urn:openspp:vocab:relationship", "code": "head"}
]
}
}
]
)
print(f"Created group: {group['identifier'][0]['value']}")
# Enroll in program
membership = api.enroll_in_program(
program_ref="Program/urn:openspp:program|4Ps",
beneficiary_ref=f"Group/urn:openspp:group|HH-NEW-001"
)
print(f"Enrolled in program: {membership['program']['display']}")
What's next#
Search and Filtering - Advanced search and filtering
Batch Operations - Create multiple resources atomically
Consent Management - Understanding consent-based access
Error Handling - Error handling
See also#
FHIR Resource Types - FHIR resource patterns
G2P Connect Resources - G2P resource definitions
openspp.org