Skip to content

People API

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 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.

Trigger a full sync of active people from Productive.

Permission: manage:people (executive only)

Response:

{
"ok": true,
"synced": 25,
"matched_users": 18
}

The sync:

  1. Fetches all active people from Productive (paginated)
  2. Upserts into people table
  3. Matches to app users by email
  4. Resolves manager relationships
  5. Updates matched users’ job_title and picture
  6. Populates user_external_ids for the productive provider
  7. Removes people no longer active in Productive

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: employment fields (status, basis, dates, location)
  • Executive / Head (subtree) / Manager (direct reports): contact and demographics fields
  • Executive only: financial fields (bank account, super fund details), xero_mapping (link status and last sync timestamp)

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"
}

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.

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.