REST API v1
Bearer auth, cursor pagination, webhooks on every event, maintained SDKs.
Base URL
https://api.pulsehr.it/v1 Self-host override
$ export PULSE_API_BASE=https://pulse.internal/v1 Authentication
All requests authenticate with a Bearer token. Mint keys in
/developers inside the app.
Keys carry a scope (read:*,
write:leave, …) and an environment
(pk_live_* or pk_test_*).
$ curl https://api.pulsehr.it/v1/employees \
-H "Authorization: Bearer pk_live_a9f3…" \
-H "Accept: application/json" Pagination
Every list endpoint returns cursor pagination. Pass ?limit=50&cursor=….
Responses include next_cursor (null if there are no more pages) and
total_count.
{
"data": [ /* 50 employees */ ],
"next_cursor": "eyJpZCI6ImVtcF8xOTgifQ==",
"total_count": 312
} Rate limits
1,000 requests per minute per API key on Standard. Self-host has no limit (your Postgres will be the bottleneck before this is). Every response carries three headers:
- X-RateLimit-Limit: 1000
- X-RateLimit-Remaining: 873
- X-RateLimit-Reset: 2026-04-20T12:05:00Z
Error format
Every error returns the same envelope — an HTTP status, a stable code string,
a human message, and (when relevant) a fields array pinpointing what's wrong.
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "unprocessable_entity",
"message": "end_date must be after start_date",
"fields": ["end_date"],
"request_id": "req_01HR3…"
}
} | HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_request | Payload failed schema validation. Response body lists the failing fields. |
| 401 | unauthenticated | Missing or malformed Bearer token. Check the Authorization header. |
| 403 | permission_denied | Authenticated, but the API key scope doesn't allow this operation. |
| 404 | not_found | Resource ID unknown, or scoped to another tenant. |
| 409 | conflict | Version mismatch on optimistic lock, or duplicate idempotency key. |
| 422 | unprocessable_entity | Semantically invalid — e.g. end_date before start_date. |
| 429 | rate_limited | 1,000 req/min exceeded. Retry-After header tells you when to try again. |
| 500 | internal_error | Our bug. Every 5xx is paged and we'll email you with the incident ID within 24 h. |
Resources
Partial list — the full spec is published as OpenAPI 3.1 at api.pulsehr.it/v1/openapi.json. Generated clients for TS / Python / Go drop on every merge.
Active and former people in the workspace.
Key fields
- id
- full_name
- role
- department
- manager_id
- start_date
- end_date
- location
- custom_fields
Methods
- GET /v1/employees
- GET /v1/employees/{id}
- POST /v1/employees
- PATCH /v1/employees/{id}
- DELETE /v1/employees/{id}
Webhook events
- employee.created
- employee.updated
- employee.offboarded
Async-standup entries. Three lines per person per day.
Key fields
- id
- employee_id
- date
- lines[]
- topic
- created_at
Methods
- GET /v1/status-log
- GET /v1/status-log/{id}
- POST /v1/status-log
Webhook events
- status_log.posted
Peer recognition. Feeds the Employee Score Recognition factor.
Key fields
- id
- from_employee_id
- to_employee_id
- coins
- tag
- note
- created_at
Methods
- GET /v1/kudos
- POST /v1/kudos
Webhook events
- kudos.awarded
Weekly self-reported load — light, balanced, heavy, overloaded.
Key fields
- id
- employee_id
- week_start
- level
- created_at
Methods
- GET /v1/workload-checkins
- POST /v1/workload-checkins
Webhook events
- workload.recorded
Personal leave journal. The IC logs their own days off.
Key fields
- id
- employee_id
- kind
- start_date
- end_date
- days
- note
Methods
- GET /v1/leave
- GET /v1/leave/balance/{employee_id}
- POST /v1/leave
- DELETE /v1/leave/{id}
Webhook events
- leave.logged
- leave.cancelled
Webhooks
Configure endpoints in /developers → webhooks.
Every delivery is HMAC-SHA256 signed with your endpoint's signing secret —
verify via the X-Pulse-Signature header.
Failed deliveries retry with exponential backoff for 72 hours; replay any
event from the dashboard.
See the full event catalogue on /ecosystem.
POST https://your.app/pulse/hook
Content-Type: application/json
X-Pulse-Event: leave.approved
X-Pulse-Signature: t=1745148720,v1=9f8a…
X-Pulse-Delivery: del_01HR3…
{
"id": "evt_01HR3…",
"type": "leave.approved",
"created_at": "2026-04-20T10:12:03Z",
"data": { /* full leave payload */ }
} SDKs
TypeScript
@pulsehr/sdk
bun add @pulsehr/sdk Python
pulsehr
pip install pulsehr Go
pulsehr-go
go get github.com/pulsehr/pulsehr-go Quick start — TypeScript
import { Pulse } from "@pulsehr/sdk";
const pulse = new Pulse({ apiKey: process.env.PULSE_API_KEY! });
// Post a status-log entry on behalf of an authenticated user
const entry = await pulse.statusLog.create({
employee_id: "emp_01HR3…",
lines: [
"Shipped the OG localiser. Three locales live.",
"Hit a CLS regression on /product; fix in 1742.",
"Pairing with Niccolò on the workload trend curve.",
],
});
// Send a kudos
await pulse.kudos.create({
to_employee_id: "emp_01HR4…",
coins: 10,
tag: "craft",
note: "Picked up the failing migration without anyone asking.",
});