Developers

Employees API

Reference for managing employee records via the Performance Blocks API — CRUD, status transitions, CSV upsert, and reporting lines.

Plan availability: The Performance Blocks API is available on the Agentic plan. Team plan customers can upgrade in Settings → Billing.

The Employees API is the system of record for the people in your Performance Blocks organization. It supports full CRUD, lifecycle transitions (deactivate / reactivate / rehire), reporting line management, custom fields, and a bulk CSV upsert designed for HRIS sync workflows.

This is the API you wire up first when building any integration. Other resources reference employees by ID; an HRIS-synced employee directory is the foundation for observations, summaries, and objectives that line up with your real org chart.

Resource shape

{
  "id": "emp_01HXX9YK7Z8N2P3Q4R5S6T7U8V",
  "email": "jordan.reyes@example.com",
  "first_name": "Jordan",
  "last_name": "Reyes",
  "role": "manager",
  "manager_id": "emp_01HYY...",
  "department_id": "dep_01HZZ...",
  "status": "active",
  "custom_fields": {
    "employee_number": "E-04321",
    "location": "remote-us",
    "start_date": "2024-08-12"
  },
  "created_at": "2024-08-12T14:00:00.000Z",
  "updated_at": "2026-04-01T09:30:00.000Z",
  "deactivated_at": null
}

Field reference

Field Type Notes
id string ULID prefixed with emp_. Read-only.
email string Unique within the organization. Required.
first_name string 1–80 characters. Required.
last_name string 1–80 characters. Required.
role enum employee, manager, org_admin. Defaults to employee.
manager_id string | null The employee's manager. null for the top of a reporting tree.
department_id string | null Optional department reference.
status enum active, deactivated, pending_invite. Read-only — change via lifecycle endpoints.
custom_fields object Arbitrary key/value map. Strings, numbers, booleans, and ISO dates only — no nested objects. Up to 32 keys, key length ≤ 64 chars, value length ≤ 1024 chars.
created_at string (datetime) Read-only.
updated_at string (datetime) Read-only.
deactivated_at string (datetime) | null Set when status is deactivated.

HRIS-managed fields

If an employee is sourced from an HRIS connector, certain fields are managed externally and become read-only in this API:

  • email
  • first_name
  • last_name
  • manager_id
  • department_id
  • custom_fields keys mapped from HRIS attributes

Attempts to write these fields on an HRIS-managed employee return 422 unprocessable with a field_managed_externally detail. The role field, status transitions, and custom_fields keys not mapped from HRIS remain writable.

The current is_hris_managed flag and hris_source (e.g. workday, bamboohr) are returned on every employee resource as read-only metadata.

Expand paths

?expand= accepts:

  • manager — inlines the manager Employee object.
  • department — inlines the Department object.
  • direct_reports — inlines an array of direct-report Employee objects (limited to 100; use /employees?filter[manager_id]=... for full paging).

List employees

GET /employees

Query parameters

Parameter Type Notes
limit integer Default 50, max 100.
cursor string Pagination.
sort string last_name, first_name, created_at, -created_at. Default last_name.
filter[email] string Exact match.
filter[role] enum Repeat for multiple.
filter[manager_id] string Direct reports of this manager.
filter[department_id] string Members of this department.
filter[status] enum active (default), deactivated, pending_invite, all.
filter[custom_fields.<key>] string Exact match on a custom field value.
expand string See above.

Example: curl

curl "https://api.performanceblocks.com/v1/employees\
?filter[manager_id]=emp_01HYY...\
&filter[status]=active\
&limit=100" \
  -H "Authorization: Bearer $PB_API_KEY"

Get an employee

GET /employees/{id}
curl https://api.performanceblocks.com/v1/employees/emp_01HXX... \
  -H "Authorization: Bearer $PB_API_KEY"

Returns 404 if the employee is not in your organization.

Create an employee

POST /employees

Required scope: employees:write.

Request body

{
  "email": "casey.lin@example.com",
  "first_name": "Casey",
  "last_name": "Lin",
  "role": "employee",
  "manager_id": "emp_01HYY...",
  "department_id": "dep_01HZZ...",
  "custom_fields": {
    "employee_number": "E-04999",
    "location": "remote-eu",
    "start_date": "2026-05-15"
  },
  "send_invite": true
}
Field Notes
email Required. Must be a valid email and unique in your organization (across all statuses).
first_name, last_name Required.
role Optional. Defaults to employee.
manager_id Optional. Must reference an active employee in your organization.
department_id Optional. Must reference a department in your organization.
custom_fields Optional. See size limits in field reference.
send_invite Optional, default false. If true, the new employee receives an invitation email and starts in pending_invite status until they accept. If false, the employee is created in active status with no invite.

A successful create returns 201 with the new resource.

Example: TypeScript

const res = await fetch('https://api.performanceblocks.com/v1/employees', {
  method: 'POST',
  headers: {
    Authorization: `Bearer  

Update an employee

PATCH /employees/{id}

Required scope: employees:write.

PATCH is partial. Send only the fields you're changing. To clear manager_id or department_id, send null.

curl -X PATCH https://api.performanceblocks.com/v1/employees/emp_01HXX... \
  -H "Authorization: Bearer $PB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"manager_id": "emp_01HZZ...", "role": "manager"}'

Restrictions

  • HRIS-managed fields cannot be updated via the API.
  • Setting manager_id to a value that would create a reporting cycle returns 422.
  • Setting manager_id on a deactivated employee is allowed (for backfill), but the employee remains deactivated.

Reporting line management

Reporting lines are modeled as the manager_id foreign key. To restructure:

  1. Update the employee's manager_id directly. Their direct reports (if any) are not affected.
  2. To bulk re-parent direct reports under a new manager, page through GET /employees?filter[manager_id]={old} and PATCH each one with the new manager_id.

There is no atomic "move team" endpoint. For large reorgs, consider running the moves in a single window and using the CSV upsert below.

To find an employee's full reporting chain, expand manager and walk up. To find their full subtree, page GET /employees?filter[manager_id]=... recursively (or fetch once with ?expand=direct_reports for the immediate level).

Status transitions

Deactivate

POST /employees/{id}/deactivate

Sets status to deactivated and deactivated_at to now. The employee can no longer log in. Their historical observations, summaries, and objectives are preserved. Returns 409 if already deactivated.

curl -X POST https://api.performanceblocks.com/v1/employees/emp_01HXX.../deactivate \
  -H "Authorization: Bearer $PB_API_KEY"

Reactivate

POST /employees/{id}/reactivate

Reverses a recent deactivation. Sets status to active and clears deactivated_at. The employee's old reporting line and custom fields are preserved as-is. Returns 409 if not currently deactivated.

Rehire

POST /employees/{id}/rehire

For employees who left and returned. Differs from reactivate in that it:

  • Sets status to active and clears deactivated_at.
  • Optionally accepts a body to update fields on rehire (new manager_id, new department_id, updated custom_fields).
  • Logs a separate employee.rehired event for HR analytics.
{
  "manager_id": "emp_01HZZ...",
  "department_id": "dep_01HXX...",
  "custom_fields": { "rehire_date": "2026-05-01" }
}

Returns the updated resource.

CSV bulk upsert

For HRIS sync, use the CSV endpoint instead of one-by-one POSTs. It dedupes by email and runs idempotently.

POST /employees/csv
Content-Type: text/csv

Required scope: employees:write.

The request body is a CSV file with a header row. Recognized columns:

Column Type Notes
email string Required. Used as the upsert key.
first_name string Required for new employees.
last_name string Required for new employees.
role enum Optional.
manager_email string Optional. Resolved to manager_id. Both rows can be in the same file; the API resolves dependencies.
department_name string Optional. Resolved to department_id. Department is created if missing (org admin only).
status enum Optional. active or deactivated. Use deactivated to terminate.
custom.<key> string Custom field columns. Prefix with custom. (e.g. custom.employee_number).

Example

email,first_name,last_name,manager_email,department_name,custom.employee_number
ceo@example.com,Pat,Smith,,,E-00001
vp@example.com,Sam,Patel,ceo@example.com,Engineering,E-00010
ic@example.com,Jordan,Reyes,vp@example.com,Engineering,E-00100

Request

curl -X POST https://api.performanceblocks.com/v1/employees/csv \
  -H "Authorization: Bearer $PB_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @employees.csv

Response

{
  "data": {
    "created": 12,
    "updated": 47,
    "deactivated": 3,
    "skipped": 0,
    "errors": []
  }
}

If any row fails validation, that row is reported in errors (with row_number, email, code, message) and the rest of the file is still processed. The upsert is best-effort batch, not transactional — partial success is the expected outcome.

Maximum file size 5 MB or 10,000 rows, whichever is smaller. For larger imports, split the file.

Webhook events

Event Fires when
employee.created Employee is created via API or CSV.
employee.updated Any field changes (including HRIS-driven changes).
employee.deactivated Status moves to deactivated.
employee.reactivated Status moves from deactivated to active via reactivate.
employee.rehired Rehire endpoint is called.

The delivery data field contains the full Employee resource. See Webhooks.

Common error responses

Status Code Cause
400 validation_error Missing required field, invalid email, custom field too large.
401 authentication_required Missing or invalid API key.
403 permission_denied Key lacks employees:write.
404 not_found Employee not in your organization.
409 conflict Email already exists; status transition not allowed; reporting cycle.
422 unprocessable HRIS-managed field write attempt; manager not in your organization.

Use cases

One-way HRIS sync

Run a daily job that exports your HRIS to CSV and POSTs to /employees/csv. Mark status=deactivated for terminations. The endpoint handles ordering (managers before reports) automatically. Track errors from each run and alert on non-zero.

Reactive sync via webhooks

If your HRIS supports outbound webhooks, translate each event to a single POST /employees or PATCH /employees/{id} request. Use a stable idempotency key derived from the HRIS event ID. Retry with backoff on 429 and 5xx.

Onboarding automation

When a new hire's start date arrives, your onboarding system calls POST /employees with send_invite: true. The employee receives the invitation, accepts, and starts in active status. Subscribe to employee.created to trigger downstream actions (provision Slack, schedule first 1:1, etc.).

© 2026 Performance Blocks. All rights reserved.