HollyHR Developer Docs
  • HollyHR
  • Sign in
  • Manage API keys
  • Start Here
  • Core API
  • AI and MCP
  • API Reference
  • Recipes
  • Resources
Recipe indexGitHub Actions recipesMCP smoke testSafe MCP leave bookingPeople syncTest SDK from sourceSlack who's awayGoogle Sheets exportReceive webhooks in NodeCreate person + webhookWebhook + payroll referencesPayroll readiness export
Recipes

People sync

Use this recipe to keep an external directory, access-control list, reporting tool, or data warehouse aligned with HollyHR's safe People projection.

The default sync uses people:read only. It does not read dates of birth, home contact details, demographics, compensation, bank details, tax or government identifiers, document bytes, avatar bytes, or sensitive notes.

What it reads

  • GET /people
  • GET /people/{personId} for detail refreshes
  • GET /people/{personId}/org-links when you need historical org placement
  • optional GET /people/{personId}/employment for current employment detail

Use webhooks for public API-origin changes when you need near-real-time notifications, then fetch current state through REST. UI-origin changes inside HollyHR do not currently emit public API webhooks.

Scopes

Code
people:read

Add people:write only for a deliberate provisioning or lifecycle integration. Do not add people:personal:read unless your integration has a documented need for one-person-at-a-time elevated personal profile reads.

Baseline sync

Code
const apiBaseUrl = process.env.HOLLYHR_API_BASE_URL; const apiToken = process.env.HOLLYHR_API_TOKEN; if (!apiBaseUrl || !apiToken) { throw new Error("Missing HOLLYHR_API_BASE_URL or HOLLYHR_API_TOKEN"); } async function holly(path) { const response = await fetch(`${apiBaseUrl}${path}`, { headers: { Authorization: `Bearer ${apiToken}`, Accept: "application/json", }, }); if (!response.ok) { throw new Error(`HollyHR API request failed: ${response.status} ${await response.text()}`); } return response.json(); } async function listAll(path) { const items = []; let cursor; do { const separator = path.includes("?") ? "&" : "?"; const page = await holly( `${path}${cursor ? `${separator}cursor=${encodeURIComponent(cursor)}` : ""}`, ); items.push(...page.data); cursor = page.pagination?.next_cursor; } while (cursor); return items; } const people = await listAll("/people?limit=100"); for (const person of people) { await upsertDirectoryUser({ hollyhrId: person.id, displayName: person.display_name, workEmail: person.work_email, status: person.status, department: person.department?.name ?? null, jobTitle: person.job_title ?? null, managerId: person.manager?.id ?? null, updatedAt: person.updated_at, }); }

Incremental sync

Store the latest successful updated_at checkpoint from your downstream store, then request only changed records:

Code
const checkpoint = "2026-06-16T09:00:00.000Z"; const changedPeople = await listAll( `/people?limit=100&updated_since=${encodeURIComponent(checkpoint)}`, );

Always follow cursor pagination until pagination.next_cursor is empty before advancing the checkpoint.

Optional safe writes

For starter provisioning, use POST /people with people:write and an Idempotency-Key. HollyHR accepts safe setup fields such as work email, name, job title, and start date. It does not send invite emails and does not accept payroll, bank, tax, government-id, home contact, date-of-birth, demographic, notes, avatar, or document payloads.

For updates, read the person first and send the returned ETag as If-Match with an idempotency key. If the API returns 412, fetch the latest record and ask a human or deterministic merge rule to resolve the conflict.

Webhook pairing

Create a webhook for person.created, person.updated, person.ended, and person.reactivated if your integration needs notification-driven refreshes. The endpoint key needs webhooks:manage plus people:read.

Webhook payloads are thin notifications. Deduplicate by HollyHR-Webhook-Id, verify the signature, then call GET /people/{personId} for the current projection before updating downstream systems.

Last modified on June 24, 2026
Safe MCP leave bookingTest SDK from source
On this page
  • What it reads
  • Scopes
  • Baseline sync
  • Incremental sync
  • Optional safe writes
  • Webhook pairing
Javascript
Javascript