Constituent Management Integration Guide
A comprehensive guide to managing constituents (people) in Oho, including creation, bulk operations, HR system integration, and lifecycle management.
Overview
Base URL: https://app.weareoho.com/api
Authentication: Bearer token (JWT) required in Authorization header
Content-Type: application/json
This guide covers everything you need to effectively manage constituents in Oho, from individual creation to bulk operations and HR system integration.
Why This Guide?
Constituents are the foundation of your compliance program. Effective constituent management enables:
- ✅ Centralized person records - Single source of truth for employee/volunteer data
- ✅ Streamlined onboarding - Quick setup for new hires with compliance tracking
- ✅ HR system integration - Sync with existing workforce management systems
- ✅ Lifecycle tracking - Manage employees from hire to offboarding
- ✅ Bulk operations - Import and update hundreds of records efficiently
- ✅ Audit trails - Complete history of checks linked to each person
Prerequisites
Ensure you have:
- Valid Oho API access token (see Authentication)
- Understanding of Core Concepts - Constituents
- Your organization's HR data structure documented
- Decision on external_id mapping strategy
Core Concepts
What is a Constituent?
A constituent represents an individual person in your organization who requires background checks or compliance screening. Think of constituents as your employee/volunteer records in Oho.
Key characteristics:
- Stores personal information (name, DOB, email, phone)
- Links to multiple accreditations (background checks) over time
- Belongs to exactly one organization
- Can be active or inactive
- Optionally links to your HR system via
external_id
Constituent vs Accreditation
Understanding the relationship:
Constituent (Person Record)
├── Personal Details (name, email, DOB)
├── External ID (link to your HR system)
└── Accreditations (Background Checks)
├── VIC WWC (2024-01-15) - Active
├── NDIS Screening (2024-03-10) - Cleared
└── QLD Blue Card (2025-01-05) - Active
Constituents = WHO is being checked Accreditations = WHAT checks have been performed
Creating Constituents
Method 1: Create Individual Constituent
The primary endpoint for creating constituents is POST /constituents/ensure_exists. This endpoint is idempotent and will either create a new constituent or return an existing one based on email.
Basic Creation
curl -X POST https://app.weareoho.com/api/constituents/ensure_exists \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Sarah",
"last_name": "Chen",
"email": "sarah.chen@example.com"
}'
Response:
{
"id": 12345,
"first_name": "Sarah",
"last_name": "Chen",
"email": "sarah.chen@example.com",
"phone": null,
"mobile_number": null,
"date_of_birth": null,
"external_id": null,
"active": true,
"created_at": "2024-11-05T10:00:00Z",
"updated_at": "2024-11-05T10:00:00Z"
}
Full Details Creation
curl -X POST https://app.weareoho.com/api/constituents/ensure_exists \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Michael",
"last_name": "Thompson",
"email": "michael.thompson@example.com",
"phone": "+61-2-9876-5432",
"mobile_number": "+61-412-345-678",
"date_of_birth": "1988-07-22",
"external_id": "EMP-2024-1234"
}'
Response includes all provided fields plus:
id- Oho's unique identifier for this constituentactive- Defaults totruecreated_atandupdated_attimestamps
Always provide external_id to link Oho constituents to your HR system. This makes syncing and cross-referencing much easier.
Method 2: Check if Constituent Exists
Before creating, you can check if a constituent already exists:
curl -X POST https://app.weareoho.com/api/constituents/check_exists \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "sarah.chen@example.com"
}'
Response (if exists):
{
"exists": true,
"constituent_id": 12345
}
Response (if not exists):
{
"exists": false,
"constituent_id": null
}
- Use
check_existswhen you need to know if a constituent exists before taking action - Use
ensure_existswhen you want to create if missing or get existing (idempotent) ensure_existsis typically preferred for most integrations
Lifecycle Management
Onboarding Workflow
Complete workflow for new employee onboarding:
New Employee Hired
↓
1. Create Constituent
POST /constituents/ensure_exists
↓
2. Submit Required Checks
POST /api/scan (WWC, NDIS, etc.)
↓
3. Monitor via Webhooks
Receive completion notifications
↓
4. Verify Compliance
GET /constituents/{id}
↓
Employee Cleared to Start
Complete Example:
import requests
import time
JWT_TOKEN = "your-jwt-token"
BASE_URL = "https://app.weareoho.com/api"
headers = {
"Authorization": f"Bearer {JWT_TOKEN}",
"Content-Type": "application/json"
}
def onboard_employee(employee_data):
"""Complete onboarding workflow"""
print(f"Onboarding {employee_data['first_name']} {employee_data['last_name']}")
# Step 1: Create constituent
print("Step 1: Creating constituent...")
constituent_response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": employee_data["first_name"],
"last_name": employee_data["last_name"],
"email": employee_data["email"],
"phone": employee_data.get("phone"),
"date_of_birth": employee_data.get("date_of_birth"),
"external_id": employee_data.get("employee_id")
}
)
constituent = constituent_response.json()
print(f"✓ Constituent created: ID {constituent['id']}")
# Step 2: Submit required background checks
print("\nStep 2: Submitting background checks...")
checks_to_submit = []
# Determine which checks are required based on role/location
if employee_data.get("works_with_children"):
if employee_data["state"] == "VIC":
checks_to_submit.append({
"type": "vicwwc",
"identifier": employee_data["wwc_number"],
"name": "Victorian WWC"
})
elif employee_data["state"] == "NSW":
checks_to_submit.append({
"type": "nswwwc",
"identifier": employee_data["wwc_number"],
"name": "NSW WWC"
})
# Submit all checks
correlation_ids = []
for check in checks_to_submit:
check_response = requests.post(
f"{BASE_URL}/api/scan",
headers=headers,
json={
"type": check["type"],
"identifier": check["identifier"],
"first_name": employee_data["first_name"],
"surname": employee_data["last_name"],
"birth_date": employee_data.get("date_of_birth"),
"constituent": {"id": constituent["id"]}
}
)
if check_response.status_code == 200:
correlation_id = check_response.json()["correlation_id"]
correlation_ids.append(correlation_id)
print(f"✓ Submitted {check['name']}: {correlation_id}")
else:
print(f"✗ Failed to submit {check['name']}: {check_response.text}")
print(f"\n✓ Onboarding initiated for {employee_data['first_name']} {employee_data['last_name']}")
print(f" Constituent ID: {constituent['id']}")
print(f" Checks submitted: {len(correlation_ids)}")
print(f" Correlation IDs: {correlation_ids}")
return {
"constituent_id": constituent["id"],
"correlation_ids": correlation_ids
}
# Example usage
new_employee = {
"employee_id": "EMP-2024-5678",
"first_name": "Jessica",
"last_name": "Martinez",
"email": "jessica.martinez@example.com",
"phone": "+61-3-9876-5432",
"date_of_birth": "1992-05-14",
"state": "VIC",
"works_with_children": True,
"wwc_number": "1234567A"
}
result = onboard_employee(new_employee)
Offboarding Workflow
def offboard_employee(external_id):
"""Offboard an employee (mark as inactive)"""
print(f"Offboarding employee: {external_id}")
# Step 1: Find constituent by external_id
list_response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = list_response.json()
constituent = next(
(c for c in constituents if c.get("external_id") == external_id),
None
)
if not constituent:
print(f"✗ Constituent not found with external_id: {external_id}")
return False
# Step 2: Mark as inactive
update_response = requests.patch(
f"{BASE_URL}/constituents/{constituent['id']}",
headers=headers,
json={"active": False}
)
if update_response.status_code == 200:
print(f"✓ Constituent {constituent['id']} marked as inactive")
return True
else:
print(f"✗ Failed to update constituent: {update_response.text}")
return False
# Example usage
offboard_employee("EMP-2024-1234")
Updating Constituents
Update Individual Fields
curl -X PATCH https://app.weareoho.com/api/constituents/12345 \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "newemail@example.com",
"phone": "+61-2-8888-9999",
"active": true
}'
Auto-Enrichment Pattern
When submitting checks with a constituent ID, Oho may auto-update constituent fields with data from the check:
# Constituent initially created with minimal data
constituent_response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": "Tom",
"last_name": "Wilson",
"email": "tom.wilson@example.com"
# No date_of_birth provided
}
)
constituent_id = constituent_response.json()["id"]
# Submit check with full details
check_response = requests.post(
f"{BASE_URL}/api/scan",
headers=headers,
json={
"type": "vicwwc",
"identifier": "9876543A",
"first_name": "Tom",
"surname": "Wilson",
"birth_date": "1990-08-05", # Full details provided
"constituent": {"id": constituent_id}
}
)
# The constituent record may now include date_of_birth
Auto-enrichment updates replace existing values and cannot be undone via the UI. Only provide accurate data when submitting checks.
Retrieving Constituents
List All Constituents
curl -X GET https://app.weareoho.com/api/constituents \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
[
{
"id": 12345,
"first_name": "Sarah",
"last_name": "Chen",
"email": "sarah.chen@example.com",
"phone": "+61-2-9876-5432",
"date_of_birth": "1992-03-15",
"external_id": "EMP-001",
"active": true,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-11-05T14:30:00Z"
},
// ... more constituents
]
Get Individual Constituent
Retrieve a single constituent with all linked accreditations:
curl -X GET https://app.weareoho.com/api/constituents/12345 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Response includes accreditations:
{
"id": 12345,
"first_name": "Sarah",
"last_name": "Chen",
"email": "sarah.chen@example.com",
"active": true,
"accreditations": [
{
"id": 5001,
"type": "vicwwc",
"identifier": "1234567A",
"status": "completed",
"registry_response": {
"may_engage": true,
"oho_status": "active",
"expiry_date": "2027-06-15"
},
"completed_at": "2024-01-15T10:30:00Z"
},
{
"id": 5002,
"type": "ndis",
"identifier": "NDIS-123456",
"status": "completed",
"registry_response": {
"clearance": "cleared"
},
"completed_at": "2024-03-10T09:15:00Z"
}
]
}
Filter Active/Inactive
# Get only active constituents
response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = response.json()
active_constituents = [c for c in constituents if c["active"]]
inactive_constituents = [c for c in constituents if not c["active"]]
print(f"Active: {len(active_constituents)}")
print(f"Inactive: {len(inactive_constituents)}")
Handling Duplicates
Email Uniqueness
Emails must be unique within an organization. Attempting to create a duplicate will either:
- Return the existing constituent (with
ensure_exists) - Fail with validation error (with direct creation)
Example - Duplicate Detection:
# First creation
response1 = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com"
}
)
constituent1 = response1.json()
print(f"First call - ID: {constituent1['id']}")
# Second creation with same email
response2 = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": "Johnny", # Different first name
"last_name": "Smith",
"email": "john.smith@example.com" # Same email
}
)
constituent2 = response2.json()
print(f"Second call - ID: {constituent2['id']}")
# Same ID returned
assert constituent1["id"] == constituent2["id"]
Using external_id to Prevent Duplicates
def find_or_create_constituent(hr_employee):
"""Find constituent by external_id or create new"""
# Get all constituents
response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = response.json()
# Look for existing by external_id
existing = next(
(c for c in constituents if c.get("external_id") == hr_employee["employee_id"]),
None
)
if existing:
print(f"Found existing constituent: {existing['id']}")
return existing
# Create new if not found
response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": hr_employee["first_name"],
"last_name": hr_employee["last_name"],
"email": hr_employee["email"],
"external_id": hr_employee["employee_id"]
}
)
new_constituent = response.json()
print(f"Created new constituent: {new_constituent['id']}")
return new_constituent
Common Workflows
Workflow 1: New Hire with Multiple Checks
def onboard_with_multiple_checks(employee):
"""Onboard employee with state-specific checks"""
# Create constituent
constituent = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json={
"first_name": employee["first_name"],
"last_name": employee["last_name"],
"email": employee["email"],
"date_of_birth": employee["date_of_birth"],
"external_id": employee["employee_id"]
}
).json()
# Determine required checks
checks = []
if employee["state"] == "VIC":
checks.append(("vicwwc", employee["vic_wwc"]))
elif employee["state"] == "NSW":
checks.append(("nswwwc", employee["nsw_wwc"]))
if employee["requires_ndis"]:
checks.append(("ndis", employee["ndis_number"]))
# Submit all checks
correlation_ids = []
for check_type, identifier in checks:
response = requests.post(
f"{BASE_URL}/api/scan",
headers=headers,
json={
"type": check_type,
"identifier": identifier,
"first_name": employee["first_name"],
"surname": employee["last_name"],
"birth_date": employee["date_of_birth"],
"constituent": {"id": constituent["id"]}
}
)
correlation_ids.append(response.json()["correlation_id"])
return {
"constituent_id": constituent["id"],
"checks_submitted": len(correlation_ids),
"correlation_ids": correlation_ids
}
Workflow 2: Compliance Audit Report
Generate a compliance report for all active employees:
def generate_compliance_report():
"""Generate compliance status for all active constituents"""
# Get all constituents
response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = response.json()
active_constituents = [c for c in constituents if c["active"]]
report = []
for constituent in active_constituents:
# Get full details with accreditations
detail_response = requests.get(
f"{BASE_URL}/constituents/{constituent['id']}",
headers=headers
)
details = detail_response.json()
accreditations = details.get("accreditations", [])
# Check compliance status
has_valid_wwc = any(
acc["type"] in ["vicwwc", "nswwwc", "qldblue", "sawwc", "wawwc", "taswwc", "ntwwc", "actwwc"]
and acc["status"] == "completed"
and acc.get("registry_response", {}).get("oho_status") == "active"
for acc in accreditations
)
report.append({
"name": f"{constituent['first_name']} {constituent['last_name']}",
"email": constituent["email"],
"external_id": constituent.get("external_id"),
"active": constituent["active"],
"total_checks": len(accreditations),
"has_valid_wwc": has_valid_wwc,
"compliant": has_valid_wwc # Add more conditions as needed
})
return report
# Generate and print report
report = generate_compliance_report()
print(f"\nCompliance Report - {len(report)} active employees")
print("=" * 80)
compliant = sum(1 for r in report if r["compliant"])
non_compliant = len(report) - compliant
print(f"Compliant: {compliant}")
print(f"Non-compliant: {non_compliant}")
print("\nNon-compliant employees:")
for employee in report:
if not employee["compliant"]:
print(f" - {employee['name']} ({employee['email']})")
Workflow 3: Bulk Status Check
Check compliance status for specific employees:
def check_bulk_compliance(external_ids):
"""Check compliance for specific employees"""
# Get all constituents
response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = response.json()
results = []
for external_id in external_ids:
constituent = next(
(c for c in constituents if c.get("external_id") == external_id),
None
)
if not constituent:
results.append({
"external_id": external_id,
"found": False,
"compliant": False
})
continue
# Get details with accreditations
detail_response = requests.get(
f"{BASE_URL}/constituents/{constituent['id']}",
headers=headers
)
details = detail_response.json()
accreditations = details.get("accreditations", [])
# Check for active WWC
has_wwc = any(
acc["type"].endswith("wwc")
and acc["status"] == "completed"
and acc.get("registry_response", {}).get("oho_status") == "active"
for acc in accreditations
)
results.append({
"external_id": external_id,
"found": True,
"name": f"{constituent['first_name']} {constituent['last_name']}",
"email": constituent["email"],
"active": constituent["active"],
"checks_count": len(accreditations),
"has_wwc": has_wwc,
"compliant": has_wwc
})
return results
# Check specific employees
employees_to_check = ["EMP-001", "EMP-002", "EMP-003"]
results = check_bulk_compliance(employees_to_check)
for result in results:
status = "✓ Compliant" if result["compliant"] else "✗ Non-compliant"
print(f"{result['external_id']}: {status}")
Error Handling
Common Errors
1. Duplicate Email
Error:
{
"error": {
"code": "duplicate_email",
"message": "Email already exists for another constituent"
}
}
Solution:
- Use
ensure_existswhich returns existing constituent - Or check existence first with
check_exists - Update existing constituent instead of creating new
2. Invalid Email Format
Error:
{
"error": {
"code": "invalid_email",
"message": "Email format is invalid"
}
}
Solution:
- Validate email format before submission
- Ensure email includes @ and domain
3. Missing Required Fields
Error:
{
"error": {
"code": "validation_error",
"message": "Missing required fields",
"details": {
"required": ["first_name", "last_name", "email"]
}
}
}
Solution:
- Ensure all required fields are provided
- Required:
first_name,last_name,email
4. Constituent Not Found
Error:
{
"error": {
"code": "not_found",
"message": "Constituent with ID 99999 not found"
}
}
Solution:
- Verify constituent ID is correct
- Ensure you're using the right organization token
- Check if constituent was deleted
Error Handling Pattern
def safe_create_constituent(constituent_data):
"""Create constituent with comprehensive error handling"""
try:
response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json=constituent_data,
timeout=30
)
if response.status_code in [200, 201]:
return {
"success": True,
"constituent": response.json()
}
elif response.status_code == 400:
error = response.json().get("error", {})
return {
"success": False,
"error": "validation_error",
"message": error.get("message"),
"details": error.get("details")
}
elif response.status_code == 401:
return {
"success": False,
"error": "authentication_error",
"message": "Invalid or expired token"
}
elif response.status_code == 409:
return {
"success": False,
"error": "duplicate",
"message": "Constituent already exists"
}
else:
return {
"success": False,
"error": "unknown_error",
"message": f"Unexpected status code: {response.status_code}",
"response": response.text
}
except requests.exceptions.Timeout:
return {
"success": False,
"error": "timeout",
"message": "Request timed out after 30 seconds"
}
except requests.exceptions.ConnectionError:
return {
"success": False,
"error": "connection_error",
"message": "Could not connect to Oho API"
}
except Exception as e:
return {
"success": False,
"error": "exception",
"message": str(e)
}
# Usage
result = safe_create_constituent({
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com"
})
if result["success"]:
print(f"Created constituent: {result['constituent']['id']}")
else:
print(f"Error: {result['error']} - {result['message']}")
Best Practices
1. Always Use external_id
Link Oho constituents to your HR system:
# Good - with external_id
{
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"external_id": "EMP-2024-1234" # Your HR system ID
}
# Not ideal - without external_id
{
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com"
# Harder to sync later
}
2. Use ensure_exists for Idempotency
Prefer ensure_exists over direct creation:
# Good - idempotent
response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json=constituent_data
)
# Safe to call multiple times
# Risky - may create duplicates
response = requests.post(
f"{BASE_URL}/constituents", # Direct creation
headers=headers,
json=constituent_data
)
3. Implement Rate Limiting for Bulk Operations
import time
def bulk_create_with_rate_limit(constituents, delay=0.2):
"""Create constituents with rate limiting"""
results = []
for i, constituent in enumerate(constituents):
response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json=constituent
)
results.append(response.json())
# Rate limit: wait between requests
if i < len(constituents) - 1:
time.sleep(delay) # 200ms delay
return results
4. Keep Active Status Current
Update active status when employment changes:
# When employee leaves
requests.patch(
f"{BASE_URL}/constituents/{constituent_id}",
headers=headers,
json={"active": False}
)
# When employee returns
requests.patch(
f"{BASE_URL}/constituents/{constituent_id}",
headers=headers,
json={"active": True}
)
5. Validate Data Before Submission
import re
def validate_constituent_data(data):
"""Validate data before submission"""
errors = []
# Check required fields
required = ["first_name", "last_name", "email"]
for field in required:
if not data.get(field):
errors.append(f"Missing required field: {field}")
# Validate email format
email = data.get("email", "")
if email and not re.match(r"[^@]+@[^@]+\.[^@]+", email):
errors.append(f"Invalid email format: {email}")
# Validate date_of_birth format (YYYY-MM-DD)
dob = data.get("date_of_birth")
if dob and not re.match(r"\d{4}-\d{2}-\d{2}", dob):
errors.append(f"Invalid date_of_birth format: {dob} (expected YYYY-MM-DD)")
return {
"valid": len(errors) == 0,
"errors": errors
}
# Usage
validation = validate_constituent_data({
"first_name": "John",
"last_name": "Smith",
"email": "invalid-email"
})
if validation["valid"]:
# Proceed with creation
pass
else:
print("Validation errors:")
for error in validation["errors"]:
print(f" - {error}")
6. Create Constituents Before Checks
Always create constituent records before submitting checks:
# Good workflow
constituent = create_constituent(employee_data)
submit_wwc_check(constituent["id"], wwc_data)
submit_ndis_check(constituent["id"], ndis_data)
# Not ideal workflow
submit_wwc_check(None, wwc_data) # Standalone check
# Harder to track and manage
7. Implement Logging and Monitoring
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_constituent_with_logging(data):
"""Create constituent with detailed logging"""
logger.info(f"Creating constituent: {data.get('email')}")
try:
response = requests.post(
f"{BASE_URL}/constituents/ensure_exists",
headers=headers,
json=data,
timeout=30
)
if response.status_code in [200, 201]:
constituent = response.json()
logger.info(f"✓ Created constituent {constituent['id']} for {data.get('email')}")
return constituent
else:
logger.error(f"✗ Failed to create constituent: {response.status_code} - {response.text}")
return None
except Exception as e:
logger.exception(f"✗ Exception creating constituent: {e}")
return None
FAQ
How do I handle employees who work in multiple states?
Create one constituent and submit multiple state-specific checks linked to that constituent:
# Create constituent once
constituent = create_constituent(employee_data)
# Submit checks for all relevant states
submit_check(constituent["id"], "vicwwc", vic_card_number)
submit_check(constituent["id"], "nswwwc", nsw_card_number)
Can I update a constituent's email address?
Yes, use PATCH to update the email:
requests.patch(
f"{BASE_URL}/constituents/{constituent_id}",
headers=headers,
json={"email": "newemail@example.com"}
)
What happens if I delete a constituent?
Deleting a constituent sets active: false but preserves the record and linked accreditations for audit purposes.
How do I handle seasonal employees who return each year?
Keep the constituent record and update the active status:
# Start of season
requests.patch(
f"{BASE_URL}/constituents/{constituent_id}",
headers=headers,
json={"active": True}
)
# End of season
requests.patch(
f"{BASE_URL}/constituents/{constituent_id}",
headers=headers,
json={"active": False}
)
Can I bulk export all constituents?
Yes, use GET /constituents to retrieve all records:
response = requests.get(
f"{BASE_URL}/constituents",
headers=headers
)
constituents = response.json()
# Export to CSV
import csv
with open('constituents.csv', 'w', newline='') as csvfile:
fieldnames = ['id', 'first_name', 'last_name', 'email', 'external_id', 'active']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for constituent in constituents:
writer.writerow({
'id': constituent['id'],
'first_name': constituent['first_name'],
'last_name': constituent['last_name'],
'email': constituent['email'],
'external_id': constituent.get('external_id'),
'active': constituent['active']
})
How often should I sync with my HR system?
Recommendations:
- Real-time: Use webhooks from HR system for immediate updates (new hires, terminations)
- Batch: Daily sync for bulk updates (contact info changes, status updates)
- Hybrid: Real-time for critical events, daily sync for everything else
What's the rate limit for constituent creation?
Standard API rate limits apply. For bulk operations:
- Implement 100-200ms delays between requests
- Use exponential backoff for retries
- Contact support for higher limits if needed
Related Documentation
- Constituents Core Concept - Understanding constituent entity
- WWC Integration Guide - Submitting checks linked to constituents
- Webhooks Integration Guide - Real-time updates for constituent accreditations
- API Reference - Complete endpoint documentation
Version: 1.0 Last Updated: 2026-01-24 Status: Production Ready