People API
People Endpoints
Section titled “People Endpoints”GET /api/people
Section titled “GET /api/people”Get all synced people from Productive.
Permission: view:people
Response:
{ "people": [ { "id": "uuid", "productive_id": "12345", "user_id": "user-uuid-or-null", "first_name": "Jane", "last_name": "Doe", "email": "jane@dotcollective.com.au", "title": "Senior Developer", "avatar_url": "https://...", "manager_id": "uuid-of-manager", "synced_at": "2026-03-03T10:00:00" } ], "synced_at": "2026-03-03T10:00:00", "stale": false}Data is cached in D1 with a 6-hour TTL. If stale, a background refresh is triggered automatically while serving the cached data.
GET /api/people/org-chart
Section titled “GET /api/people/org-chart”Get people structured as a tree for org chart visualization.
Permission: view:people
Response:
{ "roots": [ { "id": "uuid", "productive_id": "12345", "user_id": "user-uuid", "first_name": "Jane", "last_name": "Doe", "title": "CEO", "avatar_url": "https://...", "manager_id": null, "synced_at": "2026-03-03T10:00:00", "children": [ { "id": "uuid", "first_name": "John", "last_name": "Smith", "title": "VP Engineering", "children": [] } ] } ], "synced_at": "2026-03-03T10:00:00", "stale": false}People with no manager are tree roots. Children are sorted alphabetically by name at each level.
POST /api/people/sync
Section titled “POST /api/people/sync”Trigger a full sync of active people from Productive.
Permission: manage:people (executive only)
Response:
{ "ok": true, "synced": 25, "matched_users": 18}The sync:
- Fetches all active people from Productive (paginated)
- Upserts into
peopletable - Matches to app users by email
- Resolves manager relationships
- Updates matched users’
job_titleandpicture - Populates
user_external_idsfor theproductiveprovider - Removes people no longer active in Productive
GET /api/people/:id/profile
Section titled “GET /api/people/:id/profile”Get employee profile fields for a person (permission-scoped). All data is read directly from the people table (the source of truth for employee/HR data). The person does not need a linked user_id — this enables profiles for people synced from Xero who don’t have an app account.
Permission: view:people (data scoping varies by access level)
Response:
{ "profile": { "employment": { "start_date": "2023-01-15", "end_date": null, "employment_status": "Active", "employment_basis": "FULLTIME", "location": "Melbourne" }, "contact": { "phone": "03 1234 5678", "mobile": "0400 123 456", "personal_email": "jane@gmail.com", "home_address": "{\"AddressLine1\":\"123 Main St\",\"City\":\"Melbourne\"}" }, "demographics": { "gender": "F", "date_of_birth": "1990-05-20" }, "financial": { "super_fund_name": "Australian Super", "super_fund_type": "REGULATED", "super_member_number": "123456789", "bank_account_name": "Jane Doe", "bank_bsb": "063-000", "bank_account_number": "12345678" } }}Data scoping by access level:
- All levels:
employmentfields (status, basis, dates, location) - Executive / Head (subtree) / Manager (direct reports):
contactanddemographicsfields - Executive only:
financialfields (bank account, super fund details),xero_mapping(link status and last sync timestamp)
GET /api/people/:id/xero-matches
Section titled “GET /api/people/:id/xero-matches”Find matching Xero employees for a person. If exactly one high-confidence match is found (email or full name), auto-links and returns auto_linked: true.
Permission: manage:people (executive only)
Response:
{ "matches": [ { "employee_id": "xero-guid", "first_name": "Jane", "last_name": "Doe", "email": "jane@dotcollective.com.au", "status": "ACTIVE", "confidence": "exact_email", "score": 100 } ], "auto_linked": true, "xero_employee_id": "xero-guid"}POST /api/people/:id/xero-link
Section titled “POST /api/people/:id/xero-link”Manually link a person to a Xero employee.
Permission: manage:people (executive only)
Request: { "xero_employee_id": "xero-guid" }
Response: { "ok": true }
Returns 409 if the Xero employee is already linked to another user.
POST /api/people/:id/xero-sync
Section titled “POST /api/people/:id/xero-sync”Trigger a Xero payroll data sync for a linked person.
Permission: manage:people (executive only)
Response: { "ok": true }
Returns 400 if the person is not linked to Xero.