Corps API Specification
Corps API Specification
REST API Endpoints for the Volunteer Network & Event Management Module
Version: 1.0
Base Path: /api/corps
Authentication
All endpoints require authentication via Supabase Auth. Include the session cookie or Bearer token.
Role Requirements
| Role | Access Level |
|---|---|
volunteer | Own data, public opportunities |
recruiter | Own org data, manage opportunities |
corps_admin | Full org access, reporting |
admin | System-wide access |
Volunteer Profiles
GET /api/corps/volunteers
List volunteers (filtered by permission level).
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | UUID | Filter by organization |
skills | string[] | Filter by skills |
is_active | boolean | Filter active status |
search | string | Search name/email |
page | number | Page number (default: 1) |
limit | number | Items per page (default: 25) |
Response:
{
"volunteers": [
{
"id": "uuid",
"user_id": "uuid",
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"skills": ["Food Service", "Customer Service"],
"physical_capability": "moderate",
"is_active": true,
"total_hours": 47.5,
"created_at": "2026-01-01T00:00:00Z"
}
],
"total": 150,
"page": 1,
"limit": 25
}
GET /api/corps/volunteers/[id]
Get volunteer profile details.
Response:
{
"id": "uuid",
"user_id": "uuid",
"org_id": "uuid",
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"bio": "Passionate about community service...",
"skills": ["Food Service", "Customer Service"],
"interests": ["homelessness", "food_security"],
"physical_capability": "moderate",
"tech_comfort": "intermediate",
"availability": {
"monday": [{ "start": "09:00", "end": "17:00" }],
"saturday": [{ "start": "08:00", "end": "12:00" }]
},
"is_active": true,
"onboarding_completed": true,
"stats": {
"total_hours": 47.5,
"events_attended": 12,
"pending_hours": 3.0
},
"achievements": [
{ "id": "uuid", "name": "Helping Hand", "icon": "✋", "awarded_at": "..." }
],
"created_at": "2026-01-01T00:00:00Z"
}
GET /api/corps/volunteers/me
Get current user's volunteer profile.
POST /api/corps/volunteers
Create volunteer profile (self-registration).
Request Body:
{
"bio": "string",
"skills": ["string"],
"interests": ["string"],
"physical_capability": "light|moderate|heavy",
"tech_comfort": "none|basic|intermediate|advanced",
"availability": { /* schedule object */ },
"emergency_contact": {
"name": "string",
"phone": "string",
"relationship": "string"
},
"city": "string",
"zip_code": "string",
"max_travel_miles": 25
}
PATCH /api/corps/volunteers/[id]
Update volunteer profile.
Request Body: (partial update)
{
"bio": "Updated bio...",
"skills": ["Food Service", "Driving"]
}
POST /api/corps/volunteers/[id]/deactivate
Deactivate a volunteer profile.
Request Body:
{
"reason": "Moving out of area"
}
Volunteer Opportunities
GET /api/corps/opportunities
List volunteer opportunities.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | UUID | Filter by organization |
status | string | draft, published, full, completed, cancelled |
labor_type | string | Filter by labor type |
skills_needed | string[] | Filter by required skills |
location_type | string | onsite, remote, hybrid |
start_after | datetime | Events starting after this time |
start_before | datetime | Events starting before this time |
zip_code | string | Nearby opportunities |
radius_miles | number | Search radius (default: 25) |
search | string | Search title/description |
page | number | Page number |
limit | number | Items per page |
Response:
{
"opportunities": [
{
"id": "uuid",
"org_id": "uuid",
"organization_name": "Downtown Shelter",
"title": "Meal Service Volunteer",
"short_description": "Help serve dinner to guests",
"labor_type": "service",
"physical_demand": "light",
"location_type": "onsite",
"address": "123 Main St",
"city": "Portland",
"start_datetime": "2026-01-20T17:00:00Z",
"end_datetime": "2026-01-20T20:00:00Z",
"max_volunteers": 10,
"confirmed_count": 6,
"status": "published",
"is_full": false
}
],
"total": 45,
"page": 1,
"limit": 25
}
GET /api/corps/opportunities/[id]
Get opportunity details.
Response:
{
"id": "uuid",
"org_id": "uuid",
"organization_name": "Downtown Shelter",
"created_by": "uuid",
"title": "Meal Service Volunteer",
"description": "Full description...",
"short_description": "Help serve dinner to guests",
"labor_type": "service",
"physical_demand": "light",
"tech_level": "none",
"skills_needed": ["Food Service"],
"requirements": "Must be 18+, food handler card preferred",
"min_volunteers": 4,
"max_volunteers": 10,
"location_type": "onsite",
"location_name": "Main Shelter Kitchen",
"address": "123 Main St",
"city": "Portland",
"state": "Oregon",
"zip_code": "97201",
"latitude": 45.5155,
"longitude": -122.6789,
"location_instructions": "Enter through side door, check in at desk",
"schedule_type": "one_time",
"start_datetime": "2026-01-20T17:00:00Z",
"end_datetime": "2026-01-20T20:00:00Z",
"estimated_hours": 3.0,
"status": "published",
"auto_approve_signups": true,
"auto_approve_hours": false,
"allow_waitlist": true,
"reminder_hours_before": 24,
"image_url": "https://...",
"confirmed_count": 6,
"pending_count": 2,
"waitlist_count": 0,
"is_full": false,
"user_signup_status": "confirmed", // null if not signed up
"created_at": "2026-01-01T00:00:00Z"
}
POST /api/corps/opportunities
Create a new opportunity (recruiters+).
Request Body:
{
"title": "Meal Service Volunteer",
"description": "Full description...",
"short_description": "Help serve dinner to guests",
"labor_type": "service",
"physical_demand": "light",
"tech_level": "none",
"skills_needed": ["Food Service"],
"requirements": "Must be 18+",
"min_volunteers": 4,
"max_volunteers": 10,
"location_type": "onsite",
"location_name": "Main Shelter Kitchen",
"address": "123 Main St",
"city": "Portland",
"state": "Oregon",
"zip_code": "97201",
"schedule_type": "one_time",
"start_datetime": "2026-01-20T17:00:00Z",
"end_datetime": "2026-01-20T20:00:00Z",
"auto_approve_signups": true,
"status": "draft"
}
PATCH /api/corps/opportunities/[id]
Update an opportunity.
POST /api/corps/opportunities/[id]/publish
Publish a draft opportunity.
POST /api/corps/opportunities/[id]/cancel
Cancel an opportunity.
Request Body:
{
"reason": "Weather conditions unsafe",
"notify_volunteers": true
}
POST /api/corps/opportunities/[id]/complete
Mark opportunity as completed.
Signups
GET /api/corps/opportunities/[id]/signups
List signups for an opportunity (recruiters+).
Response:
{
"signups": [
{
"id": "uuid",
"volunteer_id": "uuid",
"volunteer_name": "Jane Smith",
"volunteer_email": "jane@example.com",
"status": "confirmed",
"signed_up_at": "2026-01-15T10:00:00Z",
"confirmed_at": "2026-01-15T12:00:00Z",
"check_in_at": null,
"check_out_at": null,
"volunteer_notes": "Can bring extra supplies"
}
],
"total": 8
}
POST /api/corps/opportunities/[id]/signup
Sign up for an opportunity.
Request Body:
{
"notes": "I can bring extra supplies"
}
Response:
{
"id": "uuid",
"status": "confirmed", // or "pending" or "waitlist"
"message": "You're signed up! We'll send a reminder 24 hours before."
}
DELETE /api/corps/opportunities/[id]/signup
Cancel own signup.
PATCH /api/corps/signups/[id]
Update signup status (recruiters+).
Request Body:
{
"status": "confirmed",
"organizer_notes": "Assigned to serving station"
}
POST /api/corps/signups/[id]/check-in
Check volunteer in.
Request Body:
{
"check_in_at": "2026-01-20T16:55:00Z"
}
POST /api/corps/signups/[id]/check-out
Check volunteer out.
Request Body:
{
"check_out_at": "2026-01-20T20:05:00Z"
}
Hours
GET /api/corps/hours
List volunteer hours.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
volunteer_id | UUID | Filter by volunteer |
org_id | UUID | Filter by organization |
opportunity_id | UUID | Filter by opportunity |
status | string | pending, approved, rejected, auto_approved |
date_from | date | Filter by date range start |
date_to | date | Filter by date range end |
labor_type | string | Filter by labor type |
page | number | Page number |
limit | number | Items per page |
Response:
{
"hours": [
{
"id": "uuid",
"volunteer_id": "uuid",
"volunteer_name": "Jane Smith",
"opportunity_id": "uuid",
"opportunity_title": "Meal Service",
"hours_worked": 3.0,
"date_worked": "2026-01-20",
"labor_type": "service",
"description": "Served dinner, helped with cleanup",
"status": "approved",
"submitted_at": "2026-01-20T20:30:00Z",
"approved_at": "2026-01-21T09:00:00Z",
"approved_by_name": "John Coordinator"
}
],
"total": 47,
"total_hours": 142.5,
"page": 1,
"limit": 25
}
GET /api/corps/hours/me
Get current volunteer's hours.
POST /api/corps/hours
Log volunteer hours (manual entry).
Request Body:
{
"opportunity_id": "uuid", // optional
"hours_worked": 2.5,
"date_worked": "2026-01-18",
"labor_type": "administrative",
"description": "Data entry for client intake forms"
}
PATCH /api/corps/hours/[id]
Update hours entry (before approval).
POST /api/corps/hours/[id]/approve
Approve hours (recruiters+).
Request Body:
{
"hours_worked": 2.5 // optional override
}
POST /api/corps/hours/[id]/reject
Reject hours (recruiters+).
Request Body:
{
"reason": "Hours don't match event records"
}
GET /api/corps/hours/pending
List pending hours for approval (recruiters+).
Notifications
GET /api/corps/notifications
List notifications for current user.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
unread_only | boolean | Filter unread only |
type | string | Filter by notification type |
page | number | Page number |
limit | number | Items per page |
Response:
{
"notifications": [
{
"id": "uuid",
"notification_type": "event_reminder",
"title": "Reminder: Meal Service Tomorrow",
"message": "Don't forget your shift at Downtown Shelter...",
"opportunity_id": "uuid",
"action_url": "/corps/volunteer/my-shifts",
"delivered_at": "2026-01-19T17:00:00Z",
"read_at": null
}
],
"unread_count": 3,
"total": 25
}
POST /api/corps/notifications/[id]/read
Mark notification as read.
POST /api/corps/notifications/read-all
Mark all notifications as read.
POST /api/corps/notifications/send
Send notification (recruiters+).
Request Body:
{
"notification_type": "volunteer_needed",
"title": "Urgent: Volunteers Needed Tonight",
"message": "We're short-staffed for tonight's dinner service...",
"opportunity_id": "uuid",
"target_type": "skill_match",
"target_criteria": {
"skills": ["Food Service"],
"zip_codes": ["97201", "97202", "97203"]
},
"channels": ["in_app", "email"]
}
Reports
GET /api/corps/reports/hours-summary
Get hours summary report (recruiters+).
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | UUID | Filter by organization |
date_from | date | Start date |
date_to | date | End date |
group_by | string | day, week, month, labor_type, volunteer |
Response:
{
"summary": {
"total_hours": 1250.5,
"total_volunteers": 85,
"total_events": 42,
"average_hours_per_volunteer": 14.7
},
"by_period": [
{ "period": "2026-01", "hours": 450.0, "volunteers": 45 },
{ "period": "2025-12", "hours": 380.0, "volunteers": 38 }
],
"by_labor_type": [
{ "labor_type": "service", "hours": 800.0, "percentage": 64 },
{ "labor_type": "administrative", "hours": 250.0, "percentage": 20 }
]
}
GET /api/corps/reports/participation
Get participation metrics.
Response:
{
"active_volunteers": 85,
"new_volunteers_this_month": 12,
"retention_rate": 0.78,
"average_events_per_volunteer": 3.2,
"no_show_rate": 0.05,
"top_contributors": [
{ "volunteer_name": "Jane Smith", "total_hours": 47.5 }
]
}
GET /api/corps/reports/opportunities
Get opportunity metrics.
Response:
{
"total_opportunities": 42,
"by_status": {
"published": 8,
"completed": 30,
"cancelled": 4
},
"fill_rate": 0.85,
"average_volunteers_per_event": 6.2
}
GET /api/corps/reports/export
Export report data.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
report_type | string | hours, volunteers, opportunities |
format | string | csv, pdf |
date_from | date | Start date |
date_to | date | End date |
Response: File download
Achievements
GET /api/corps/achievements
List available achievements.
Response:
{
"achievements": [
{
"id": "uuid",
"name": "Century Volunteer",
"description": "Logged 100 volunteer hours",
"icon": "💯",
"badge_color": "#F59E0B",
"trigger_type": "hours_milestone",
"trigger_value": 100,
"is_public": true
}
]
}
GET /api/corps/volunteers/[id]/achievements
Get volunteer's earned achievements.
POST /api/corps/achievements/[id]/award
Manually award achievement (corps_admin+).
Request Body:
{
"volunteer_id": "uuid",
"notes": "For exceptional leadership during crisis response"
}
Skills
GET /api/corps/skills
List available skills.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
category | string | Filter by category |
Response:
{
"skills": [
{
"id": "uuid",
"name": "Food Service",
"category": "Service",
"description": "Preparing and serving meals",
"icon": "🍽️"
}
]
}
POST /api/corps/skills
Create custom skill (corps_admin+).
Dashboard Data
GET /api/corps/dashboard/volunteer
Get volunteer dashboard data.
Response:
{
"profile": { /* volunteer profile */ },
"upcoming_shifts": [ /* next 5 signups */ ],
"hours_this_month": 12.5,
"hours_this_year": 47.5,
"recent_achievements": [ /* last 3 */ ],
"recommended_opportunities": [ /* 3-5 matched */ ]
}
GET /api/corps/dashboard/recruiter
Get recruiter dashboard data.
Response:
{
"upcoming_events": [ /* next 5 opportunities */ ],
"events_needing_volunteers": [ /* under min_volunteers */ ],
"pending_signups": 8,
"pending_hours": 15,
"hours_this_month": 450.0,
"active_volunteers": 42
}
GET /api/corps/dashboard/admin
Get admin dashboard data.
Response:
{
"total_hours_this_period": 1250.5,
"total_hours_previous_period": 1100.0,
"hours_change_percent": 13.7,
"active_volunteers": 85,
"new_volunteers_this_month": 12,
"upcoming_events": 8,
"hours_by_labor_type": [ /* breakdown */ ],
"participation_trend": [ /* weekly data */ ]
}
Error Responses
All endpoints return standard error format:
{
"error": {
"code": "SIGNUP_FULL",
"message": "This opportunity is full. You have been added to the waitlist.",
"details": {
"waitlist_position": 3
}
}
}
Common Error Codes
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Not authenticated |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid request data |
SIGNUP_FULL | 409 | Opportunity at capacity |
ALREADY_SIGNED_UP | 409 | Already registered |
SIGNUP_CLOSED | 400 | Signups not available |
HOURS_ALREADY_APPROVED | 400 | Cannot modify approved hours |
Webhooks (Future)
For external integrations, webhooks can be configured:
volunteer.createdsignup.createdsignup.confirmedhours.approvedopportunity.publishedachievement.awarded
Use links in each imported doc to open its source.