← Back to Documentation Home

API Overview

Planning Center Online (PCO) exposes a REST API conforming to the JSON:API 1.0 specification. Each PCO product has its own versioned API endpoint.

6
API Modules
100
Requests / Minute
2
Auth Methods
JSON:API
Spec Format

Base URLs

ProductBase PathAPI Version
Peoplehttps://api.planningcenteronline.com/people/v2/2022-07-14
Check-Inshttps://api.planningcenteronline.com/check-ins/v2/2019-07-17
Groupshttps://api.planningcenteronline.com/groups/v2/2018-08-01
Calendarhttps://api.planningcenteronline.com/calendar/v2/2021-07-20
Serviceshttps://api.planningcenteronline.com/services/v2/
Givinghttps://api.planningcenteronline.com/giving/v2/

JSON:API 1.0 Request/Response Format

All responses follow JSON:API structure with data, included (sideloaded relationships), links (pagination), and meta sections.

// GET response (single resource)
{
  "data": {
    "type": "Person",
    "id": "12345",
    "attributes": {
      "first_name": "John",
      "last_name": "Smith",
      "remote_id": 42        // your system's ID stored on PCO
    },
    "relationships": { ... }
  },
  "included": [ ... ],     // sideloaded via ?include=emails,phone_numbers
  "links": { "next": "..." } // cursor-based pagination
}

// POST/PATCH request body
{
  "data": {
    "type": "Person",
    "attributes": {
      "first_name": "John",
      "last_name": "Smith"
    }
  }
}

Key Query Features

  • Sideloading: ?include=emails,phone_numbers — fetch related resources in a single request.
  • Filtering: ?where[first_name]=John — field-level filtering.
  • Search: ?where[search_name_or_email_or_phone_number]=... — combined search.
  • Ordering: ?order=last_name or ?order=-created_at (descending).
  • Pagination: ?per_page=100 (max 100). Follow links.next URL for next page.

Authentication

PCO offers two authentication methods. Both produce the same API access.

Option 1: Personal Access Token (PAT)

Best for single-org server-to-server integration. No user interaction required after initial setup.

  • Created at api.planningcenteronline.com/oauth/applications
  • Uses HTTP Basic Auth: application_id:secret
  • Full read/write access to that org's data
  • Never expires (until revoked)
# Example: List all people with PAT
curl -u "app_id:secret" \
  https://api.planningcenteronline.com/people/v2/people?per_page=100

Option 2: OAuth 2.0

Best for multi-org apps where each church authorizes access independently.

  • Authorization: api.planningcenteronline.com/oauth/authorize
  • Token exchange: api.planningcenteronline.com/oauth/token
  • Access tokens expire after 2 hours
  • Refresh tokens are long-lived
  • Scopes: people, check_ins, groups, calendar, services, giving
Recommendation For a single church deployment, use a Personal Access Token — simpler setup, no OAuth flow needed, no token refresh logic. For a multi-church SaaS product, use OAuth 2.0 so each church authorizes independently.

Write Capabilities Matrix

This is the most important constraint for integration planning. PCO's API is heavily read-biased — most resources are read-only.

ResourceModuleCreateUpdateDeleteNotes
PersonPeople Yes Yes Yes remote_id field for storing your system's ID
EmailPeople Yes Yes Yes Scoped to person: /people/{id}/emails
Phone NumberPeople Yes Yes Yes Scoped to person. PCO auto-normalizes to E.164
AddressPeople Yes Yes Yes On Person (not Household)
HouseholdPeople Yes Yes Yes Maps to your families table
Household MembershipPeople Yes Yes Yes No relationship role — just "is a member"
Group MembershipGroups Yes Yes No Role: "leader" or "member" only
GroupGroups Read-Only Read-Only No Groups created in PCO admin only
Group TypeGroups Read-Only Read-Only No
Check-InCheck-Ins Read-Only Read-Only No Created via PCO Check-Ins app or Church Center only
Check-In EventCheck-Ins Read-Only Read-Only No Configured in PCO admin only
Station / Location / LabelCheck-Ins Read-Only Read-Only No All check-in infrastructure is read-only
Calendar EventCalendar Read-Only Read-Only No Events created in PCO Calendar admin only
Event InstanceCalendar Read-Only Read-Only No Individual occurrences of recurring events
Critical Limitation You cannot push check-in records to PCO via their API. Check-ins performed in Church Check-In stay local. If a church uses both systems, check-in data will only exist in the system where the check-in physically happened.

People API Read/Write

The People API is the most complete for integration. Full CRUD on people, emails, phones, addresses, and households.

Person Endpoints

MethodEndpointPurpose
GET/people/v2/peopleList all people (paginated, max 100/page)
GET/people/v2/people?where[search_name_or_email_or_phone_number]=...Search by name, email, or phone
GET/people/v2/people?include=emails,phone_numbersSideload contacts in one request
GET/people/v2/people/{id}Get single person
POST/people/v2/peopleCreate person
PATCH/people/v2/people/{id}Update person
DELETE/people/v2/people/{id}Delete person

Writable Person Attributes

first_name, last_name, given_name, nickname, middle_name, birthdate (YYYY-MM-DD), gender, child (boolean), grade, graduation_year, medical_notes, status, membership, avatar, remote_id (integer — store your system's people.id here)

remote_id is the sync key PCO's remote_id field (writable integer on Person) is designed for exactly this use case — storing an external system's ID on the PCO record. Set this to your people.id to enable reliable bidirectional lookup without maintaining a separate mapping table.

Contact Endpoints (scoped to person)

ResourceCreateListWritable Attributes
Email POST /people/v2/people/{id}/emails GET /people/v2/people/{id}/emails address, location (label), is_primary
Phone POST /people/v2/people/{id}/phone_numbers GET /people/v2/people/{id}/phone_numbers number, location (label), is_primary
Address POST /people/v2/people/{id}/addresses GET /people/v2/people/{id}/addresses street, city, state, zip, location, is_primary

Household Endpoints

MethodEndpointPurpose
GET/people/v2/householdsList all households
POST/people/v2/householdsCreate household
PATCH/people/v2/households/{id}Update household
POST/people/v2/households/{id}/household_membershipsAdd person to household
DELETE/people/v2/households/{id}/household_memberships/{mid}Remove from household

Household writable attributes: name, primary_contact_id, avatar.

Groups API Partial Write

Groups and group types are read-only. Only memberships can be created and updated via the API.

Read-Only Endpoints

MethodEndpointPurpose
GET/groups/v2/groupsList all groups
GET/groups/v2/groups/{id}Get single group (name, description, enrollment, schedule)
GET/groups/v2/group_typesList group types (category)
GET/groups/v2/eventsGroup-specific events

Group attributes (read-only): name, description, enrollment_open, enrollment_strategy, memberships_count, schedule (freetext), header_image, location_type_preference.

Writable: Memberships

MethodEndpointPurpose
GET/groups/v2/groups/{id}/membershipsList group members
POST/groups/v2/groups/{id}/membershipsAdd member to group
PATCH/groups/v2/groups/{id}/memberships/{mid}Update membership (role, joined_at)

Membership writable: person_id (write-only), role ("leader" or "member" only), joined_at.

No Delete on Memberships The Groups API does not expose a DELETE endpoint for memberships. Members can only be removed through the PCO admin interface.

Check-Ins API Read-Only

The entire Check-Ins API is read-only. Check-in records are created through the PCO Check-Ins iPad app or Church Center. This is the biggest constraint for integration.

Data Model Hierarchy

PCO Check-Ins Structure Event Recurring template (e.g. "Sunday Service")EventPeriod Specific session (e.g. "this week's Sunday Service")EventTime Time slot (e.g. "9:00 AM" or "11:00 AM")CheckIn Individual person check-in record Station Physical device (iPad) running the check-in app Location Hierarchical room/area with age/grade filters Label Printed label template ("Person" or "Group" type) Pass Barcode pass for quick phone lookup Headcount Aggregate counts by AttendanceType (not per-person)

Key Read Endpoints

EndpointPurposeKey Attributes
GET /check-ins/v2/check_ins List all check-in records first_name, last_name, security_code, medical_notes, kind, checked_out_at
GET /check-ins/v2/events List check-in events name, frequency
GET /check-ins/v2/event_times List event time slots starts_at, shows_at, hides_at, total_count, guest_count, volunteer_count
GET /check-ins/v2/stations List check-in stations name, online, mode, check_in_count
GET /check-ins/v2/labels Label templates name, xml (template), prints_for, roll
GET /check-ins/v2/headcounts Aggregate counts total (by AttendanceType)

Useful Check-In Queries

# Recent check-ins with person and event data sideloaded
GET /check-ins/v2/check_ins
  ?include=event,person,locations
  &order=-checked_out_at
  &per_page=100

# Headcounts by event time
GET /check-ins/v2/event_times
  ?include=headcounts
  &filter=future

Location Model (Hierarchical)

PCO Locations are hierarchical: a Location with kind="Folder" contains child locations. Each location has age/grade filtering: age_min_in_months, age_max_in_months, grade_min, grade_max, gender, child_or_adult, max_occupancy, min_volunteers, attendees_per_volunteer.

This is analogous to Church Management Platform's hierarchical church_group system with parent_group_id, but PCO uses it for room-based assignment rather than organizational-group-based event resolution.

Calendar API Read-Only

Calendar events and instances are read-only. Events are created and managed in PCO Calendar admin.

Key Endpoints

EndpointPurposeKey Attributes
GET /calendar/v2/events List calendar events (templates) name, description, approval_status, visible_in_church_center
GET /calendar/v2/event_instances Specific occurrences starts_at, ends_at, recurrence, recurrence_description, location
GET /calendar/v2/event_instances?filter=future&order=starts_at Upcoming events

Resource Booking

PCO Calendar includes room/resource booking with ResourceBooking, Resource, ResourceFolder, and ResourceApprovalGroup resources. All read-only via API but useful for pulling room assignment data into Church Management Platform's locations.

Entity Mapping

Side-by-side comparison of Church Management Platform entities and their PCO equivalents.

Church Management PlatformPCO EquivalentSync DirectionNotes
people Person (People API) Push & Pull Use remote_id on PCO to store your people.id
person_emails Email (People API) Push & Pull labellocation
person_phones PhoneNumber (People API) Push & Pull Both store E.164. PCO auto-normalizes
families Household (People API) Push & Pull family_namename
family_members HouseholdMembership Push & Pull PCO has NO relationship role (Parent/Child/Spouse)
church_group Group (Groups API) Pull Only Read-only. Your hierarchy is richer (3-level vs flat)
group_type GroupType (Groups API) Pull Only Read-only
group_membership Membership (Groups API) Push & Pull PCO role: "leader"/"member" only (no Coach/Other)
checkins CheckIn (Check-Ins API) Pull Only Cannot push check-ins to PCO
events EventInstance (Calendar API) Pull Only Read-only. Your group_id link has no PCO equivalent
locations Location (Check-Ins API) Pull Only PCO locations are hierarchical with age/grade filters
small_group No direct equivalent N/A Your small group metadata (meeting day, area, childcare) has no PCO home
forms Form (People API) Pull Only PCO forms are different (workflow forms vs registration)
Family address Address on Person Push & Pull PCO puts addresses on Person, not Household
people.allergies Person.medical_notes Push & Pull PCO uses freetext; yours uses text[] array

Model Differences & Gaps

Family / Household Structure

  • PCO HouseholdMembership has no relationship role (no Parent/Child/Spouse). PCO determines child status from the Person's child boolean flag.
  • PCO addresses are on Person (not Household). Each person can have multiple addresses. Your system stores address on families.
  • Your authorized_pickup_ids JSONB array has no PCO equivalent.
  • Your relationships table (Parent, Child, Guardian, Spouse, Grandparent) has no PCO match.

Groups

  • Your 3-level hierarchy (Department → Area → Group via church_group.level) is richer than PCO's flat GroupType → Group.
  • PCO membership role is only "leader" or "member" — no equivalent to your small_group_leader_type (Leader/Coach/Other).
  • Your small group metadata (meeting day, frequency, time, area, childcare, demographics) has no PCO equivalent. PCO stores schedule as a single freetext string.
  • PCO enrollment_strategy and enrollment_open map loosely to your join_rule_id.

Check-Ins

  • PCO uses a Station model (physical iPads) vs. your software kiosk.
  • PCO resolves children to rooms via Location age/grade filters. Your system uses hierarchical group-based event resolution (walking parent_group_id chain).
  • Your smart check-in window (5h/30min logic) has no PCO equivalent — PCO uses manual shows_at/hides_at configuration per station.
  • PCO Headcounts are a separate concept from individual check-ins — aggregate counts by AttendanceType (e.g. "Regulars", "Guests", "Volunteers").

Events / Calendar

  • PCO Calendar Event → EventInstance maps to your recurrence_group_id system.
  • Your events.group_id (linking events to church groups for kiosk auto-resolution) has no PCO equivalent. PCO has separate Check-Ins Events and Calendar Events (can be integrated but distinct).

Webhooks

PCO supports webhooks for real-time change notifications, reducing the need for polling.

Available Events

Confirmed for the People module:

EventFires When
people.v2.events.person.createdNew person added in PCO
people.v2.events.person.updatedPerson record modified
people.v2.events.person.destroyedPerson deleted from PCO

Check-Ins webhooks have been requested but not confirmed as available (GitHub issue #1332 closed without resolution).

Webhook Payload

{
  "data": [{
    "id": "webhook-delivery-id",
    "attributes": {
      "name": "people.v2.events.person.updated",
      "payload": {
        "data": {
          "type": "Person",
          "id": "12345",
          "attributes": { ... }
        }
      }
    }
  }]
}

Security

  • Verify via X-PCO-Webhooks-Authenticity header using HMAC-SHA256 with your webhook secret.
  • Use timing-safe comparison to prevent timing attacks.

Retry Policy

  • Up to 16 retries with exponential backoff over 5+ days.
  • Email alerts after 1 hour of failure and after final attempt.
  • Subscription auto-deactivated after all retries exhausted.

Rate Limits & Pagination

Rate Limit: 100 Requests / Minute

PCO enforces 100 requests per minute per organization. No documented burst allowance. No batch/bulk endpoints exist — each person, email, phone, address, household requires its own API call.

Impact on Bulk Operations

OperationAPI CallsTime at Rate Limit
List 5,000 people (with sideloaded contacts)50 GET requests (100/page)30 seconds
Push 5,000 people (create/update)5,000 requests~50 minutes
Push 5,000 people + emails + phones~15,000 requests~2.5 hours
Full initial sync (people + families + memberships)~25,000+ requests~4+ hours

Pagination

  • Max per_page=100.
  • Cursor-based via links.next URL in response. No offset parameter.
  • Must follow links.next URLs sequentially — cannot jump to page N.

Mitigation Strategies

  • Sideload aggressively?include=emails,phone_numbers,addresses to get everything in one request per page.
  • Incremental sync — use ?where[updated_at][gte]=2026-02-20T00:00:00Z to pull only records changed since last sync.
  • Webhooks for real-time — subscribe to person.created/updated events instead of polling.
  • Queue writes — use a background job queue (with rate limiter) to spread write operations across time windows.
  • Off-peak scheduling — run bulk syncs during low-activity hours (e.g., overnight).

Integration Strategies

Three viable approaches, depending on the church's needs:

Option A

PCO as Source of Truth

Pull people, groups, and events from PCO into Church Management Platform. Your system is the better check-in frontend that reads PCO data. Check-ins happen locally only.

  • Church manages people/groups in PCO
  • Periodic sync pulls changes into your DB
  • Webhooks for real-time person updates
  • Check-in data stays in your system
Fastest to build
Option B

Church Management Platform as Source of Truth

Push people and families to PCO so the church sees data in both systems. Groups and events stay local. PCO is a read-only mirror.

  • New people created in your system → pushed to PCO
  • Updates flow your system → PCO
  • Groups/events managed locally
  • Church uses PCO for features you don't have (Giving, Services)
Best for co-existence
Option C

Bidirectional Where Possible

Pull groups/events from PCO (read-only). Push people/families to PCO (writable). Sync memberships both ways. Check-ins stay local.

  • Most complex to build
  • Conflict resolution needed (last-write-wins?)
  • Memberships: push new adds, pull PCO-originated joins
  • Use remote_id + updated_at for change detection
Most comprehensive
Recommendation for churches currently on PCO Start with Option A (PCO as source of truth). It's the fastest to build, requires the least conflict resolution logic, and addresses the primary use case: "use your check-in system with our existing PCO data." Once proven, Option C can be layered on incrementally.

Sync Architecture

Regardless of which strategy is chosen, the sync system would follow this general architecture.

Sync Service Components

Proposed Architecture PcoSyncService New service in ChurchCheckIn.API/Services/IPcoApiClient HTTP client wrapper for PCO JSON:API calls └— Auth (PAT or OAuth token management) └— Rate limiter (100 req/min token bucket) └— Pagination helper (follow links.next) └— JSON:API serializer/deserializer ↓ SyncMappingTable New DB table: pco_sync_map └— local_type (TEXT: "person", "family", "group", etc.) └— local_id (INTEGER) └— pco_id (TEXT — PCO uses string IDs) └— pco_updated_at (TIMESTAMPTZ) └— last_synced_at (TIMESTAMPTZ) └— sync_direction (TEXT: "push", "pull", "both") ↓ Background Jobs Scheduled sync tasks └— Full sync (nightly, off-peak) └— Incremental sync (every 15 min, changes only) └— Webhook handler (real-time person changes)

Sync Mapping Table

CREATE TABLE pco_sync_map (
    id          SERIAL PRIMARY KEY,
    local_type  TEXT NOT NULL,       -- 'person', 'family', 'group', 'membership'
    local_id    INTEGER NOT NULL,
    pco_id      TEXT NOT NULL,        -- PCO uses string IDs
    pco_updated_at TIMESTAMPTZ,
    last_synced_at TIMESTAMPTZ,
    sync_status TEXT DEFAULT 'synced', -- 'synced', 'pending_push', 'pending_pull', 'conflict'
    UNIQUE(local_type, local_id),
    UNIQUE(local_type, pco_id)
);

Sync Flow: Pull People from PCO

Cron / webhook
triggers sync
GET /people/v2/people
?include=emails,phones
?where[updated_at][gte]=...
Check pco_sync_map
for existing match
Insert or update
local people table
Update sync_map
last_synced_at

Sync Flow: Push People to PCO

New person created
in Church Management Platform
Queue background job
(rate-limited)
POST /people/v2/people
+ POST emails, phones
+ POST household
Store PCO ID
in pco_sync_map
Set remote_id
on PCO Person

Conflict Resolution

For bidirectional sync, conflicts occur when both systems modify the same record between syncs.

  • Last-write-wins — compare updated_at timestamps. Simplest but can lose data.
  • Source-of-truth per field — e.g., PCO owns email, your system owns allergies. More complex but no data loss.
  • Flag for review — mark conflicts in sync_status='conflict' for admin resolution. Safest but requires manual intervention.

Webhook Handler Endpoint

// New endpoint in a PcoWebhookController
POST /api/pco/webhook

// 1. Verify X-PCO-Webhooks-Authenticity header (HMAC-SHA256)
// 2. Parse JSON:API payload
// 3. Route by event name:
//    person.created  -> upsert into people table
//    person.updated  -> update people table
//    person.destroyed -> soft-delete (set is_active=false)
// 4. Update pco_sync_map
// 5. Return 200 OK