Employee API reference (for developers)
Endpoints, authentication, request and response formats, error codes, limits and examples for the HIPAA Certify Employee API v1.
Base URL: https://hipaacertify.com/api/v1. HTTPS only, JSON in and out.
Authentication#
Send the key in the Authorization header:
Authorization: Bearer hck_ab12cd34_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Keys belong to one organization and carry permissions: read, write, deactivate. After 20 invalid-key attempts from an IP in 10 minutes, further invalid attempts from that IP are refused for a while (valid keys keep working).
Endpoints#
| Method | Path | Permission | Purpose |
|---|---|---|---|
| GET | /ping |
read | Check the key: returns organization, permissions and limits |
| GET | /employees |
read | List employees (?page=, ?per_page= up to 500, ?email=, ?external_id=, ?active=true/false) |
| POST | /employees/upsert |
write | Add or update up to 1,000 employees |
| POST | /employees/sync |
write (+ deactivate) | Send your whole roster, up to 10,000. Adds and updates, then deactivates people missing from the list |
Employee record#
{
"external_id": "E10442",
"email": "[email protected]",
"name": "Jane Doe",
"job_title": "Registered Nurse",
"department": "Emergency",
"hire_date": "2024-03-18",
"job_codes": ["RN"],
"active": true
}
| Field | Required | Notes |
|---|---|---|
email |
Yes | Lower-cased. Unique per organization. |
name |
New employees | Never blanked on update |
external_id |
Recommended | Your HR employee ID: a string or a whole number, up to 100 printable characters (exact match, case-sensitive). Matching uses it first, so email changes are safe. |
job_title, department, hire_date |
No | hire_date is YYYY-MM-DD. Unknown departments are created unless "create_departments": false. |
job_codes |
No | Must already exist and be active under Job Codes. An unknown code rejects that record. |
active |
No | Defaults to true. false deactivates the person, which needs the deactivate permission. |
Fields you leave out are not changed. Any other field is rejected, so don't send personal health information or extra HR data.
Matching rules#
external_idalready linked to someone → that person, even if their email changed.- Otherwise
email→ the employee with that email, and theexternal_idgets linked to them. - No match → a new employee. This uses a plan slot; Medium allows 100, Large and invoiced are unlimited.
- A removed employee who comes back (rehire) is restored with their training history.
- Owner and manager accounts are never matched or changed. Their emails are rejected with
email_belongs_to_admin.
Request options#
{
"employees": [ { ... }, { ... } ],
"dry_run": false,
"send_invites": false,
"create_departments": true,
"deactivate_missing": "linked",
"confirm_mass_deactivation": false,
"include_unchanged": false
}
dry_run: runs everything and returns the results, then undoes it. Use it before going live.send_invites: emails the "set up your password" invitation to new, restored or reactivated people who were never invited. Leave itfalseif your organization signs in with SSO.deactivate_missing(sync only):"linked"(default): deactivate people the API has seen before who aren't in this list"all": any employee not in the list"none": never deactivate
confirm_mass_deactivation: needed when a sync would deactivate more than the safety limit. The default limit is 10% or 25 people.include_unchanged: also list records that didn't change inresults.
An empty employees list is always refused, so a broken export can't deactivate everyone.
Response#
{
"mode": "sync",
"dry_run": false,
"summary": { "received": 412, "created": 3, "updated": 7, "restored": 1, "reactivated": 0,
"deactivated": 2, "unchanged": 398, "errors": 1 },
"results": [
{ "index": 5, "external_id": "E10442", "email": "[email protected]", "user_id": 3051, "status": "updated", "changed": ["job_title"] },
{ "index": 9, "external_id": "E20017", "email": "bad@", "status": "error",
"error": { "code": "invalid_record", "message": "The email field must be a valid email address." } },
{ "index": null, "external_id": "E00881", "email": "[email protected]", "user_id": 2210, "status": "deactivated", "reason": "not_in_sync" }
]
}
A request that is processed returns 200, even if some records had errors. Check summary.errors. A record with an error that matches an existing employee is still treated as "present", so it never causes that person to be deactivated.
Idempotency#
Send Idempotency-Key: <unique value> on POST requests. For 24 hours, retrying the same key and body returns the original response, with header Idempotent-Replayed: true, and nothing is applied twice. Reusing the key with a different body returns idempotency_key_reused.
Limits#
- 120 requests per minute per key. Headers
X-RateLimit-LimitandX-RateLimit-Remainingare included, andRetry-Afteris sent on 429. - Calls for one organization are processed one at a time, so parallel requests can't exceed your plan limit.
Errors#
| HTTP | error.code |
Meaning |
|---|---|---|
| 401 | invalid_key, key_expired |
Missing, wrong, revoked or expired key |
| 403 | ip_not_allowed |
Key restricted to other IP addresses |
| 403 | api_not_enabled, api_disabled |
Not enabled by HIPAA Certify, or turned off by the owner |
| 402 | plan_not_eligible, subscription_inactive, payment_failed |
Plan or billing doesn't allow API use right now |
| 403 | insufficient_scope |
Key lacks the needed permission |
| 409 | mass_deactivation_guard |
Sync would deactivate too many. Nothing was changed. |
| 409 | sync_in_progress |
Another request for your organization is still running. Retry in a minute. |
| 413 | too_many_records |
More than 1,000 (upsert) or 10,000 (sync) |
| 422 | invalid_body, empty_request, unknown_options, idempotency_key_reused |
Fix the request |
| 429 | rate_limited, too_many_failed_attempts |
Slow down; see Retry-After |
| 503 | api_unavailable |
Temporarily switched off by HIPAA Certify |
Record-level codes (inside results):
invalid_record,unknown_fields,duplicate_in_requestemail_in_use,email_belongs_to_admin,email_held_by_removed_employeeexternal_id_conflict,unknown_job_code,unknown_departmentemployee_limit_reached,insufficient_scope
Example#
curl -X POST https://hipaacertify.com/api/v1/employees/sync \
-H "Authorization: Bearer $HC_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-2026-10-28" \
-d '{"dry_run": true, "employees": [
{"external_id":"E10442","email":"[email protected]","name":"Jane Doe","department":"Emergency"}
]}'
Last updated October 27, 2026 · Steps checked against the platform on October 28, 2026.