Offboarding Initiations
An initiation is a request to start offboarding an employee, submitted by a manager (or HR/admin) and optionally routed through an approval chain before the real offboarding record gets created. This is a separate, earlier step from the Offboarding API - an initiation only becomes an offboarding once it's approved (or auto-approved).
Base path: /api/offboarding-initiations
This feature is gated behind the managerInitiation plan feature and is session-authenticated, like the rest of the app API - it is not part of the API-key-based /api/v1 surface.
Approval behavior
Whether an initiation needs approval at all depends on your org's settings and who's submitting it:
- If HR or an admin submits the request, it's auto-approved immediately and the offboarding is created on the spot.
- If your org has
require_initiation_approvalturned off, everything auto-approves the same way. - Otherwise, the initiation is routed through an approval chain: either the
chain_idyou pass explicitly, or the org'sdefault_chain_id. If neither resolves to a real chain, there's no formal chain - the request just sits pending and PurpletGo posts an interactive notification to Slack/Teams instead of routing through named steps.
Chains are made of ordered steps, each targeting either a specific approver_id or an approver_role. Approving a step advances current_step; approving the last step creates the offboarding. Steps can also be delegated to someone else. Chain configuration itself (creating/editing chains and their steps) lives under /api/approval-chains, not documented here.
Initiation object
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"org_id": "org_xyz",
"initiated_by": "usr_mgr001",
"initiated_by_name": "Alex Chen",
"initiated_by_email": "alex@acme.com",
"employee_name": "Jordan Lee",
"employee_email": "jordan.lee@acme.com",
"employee_role": "Senior Engineer",
"employee_department": "Engineering",
"exit_date": "2025-07-11",
"separation_type": "resignation",
"apps_to_revoke": ["github", "slack"],
"notes": "Employee has signed their resignation letter.",
"status": "pending",
"chain_id": "9c1e...",
"chain_name": "Standard Engineering Exit",
"current_step": 2,
"reviewed_by": null,
"reviewed_by_name": null,
"reviewed_at": null,
"rejection_reason": null,
"offboarding_id": null,
"created_at": "2025-06-26T10:30:00.000Z",
"updated_at": "2025-06-26T11:00:00.000Z"
}Status values: pending · approved · rejected. There's no cancelled status - withdrawing a pending initiation deletes it outright (see Withdraw).
initiated_by_name, initiated_by_email, reviewed_by_name and chain_name are joined in by the list and get endpoints only. Submit and edit return the raw row, without them.
notes is stored sealed when the deployment has an encryption key, and every endpoint returns the column as stored - a pii:v1:... string, never the plaintext you sent. The example above shows the value before sealing. Treat notes as write-only from the API's point of view.
Endpoints
Submit an initiation
POST /api/offboarding-initiations
Restricted to managers, HR, and admins.
Request body
{
"employee_name": "Jordan Lee",
"employee_email": "jordan.lee@acme.com",
"employee_role": "Senior Engineer",
"employee_department": "Engineering",
"exit_date": "2025-07-11",
"separation_type": "resignation",
"apps_to_revoke": ["github", "slack"],
"no_apps": false,
"notes": "Employee has signed their resignation letter.",
"chain_id": null
}| Field | Required | Type | Description |
|---|---|---|---|
employee_name | Yes | string | Free-text name - there's no employee lookup by ID |
exit_date | Yes | date | ISO 8601 date (YYYY-MM-DD) |
employee_email | No | string | |
employee_role | No | string | |
employee_department | No | string | Must be inside the caller's department scope, if they have one (403 otherwise) |
separation_type | No | enum | resignation · termination · layoff · retirement · contract_end · other. Defaults to resignation |
apps_to_revoke | See note | string[] | Apps to revoke. Only the org's connected integrations are accepted (400 otherwise) |
no_apps | See note | boolean | true confirms there is nothing to revoke for this person |
notes | No | string | Internal notes, max 2000 characters |
chain_id | No | uuid | Explicit approval chain to use instead of the org default |
You must either list at least one app in apps_to_revoke or send no_apps: true. An empty list with no no_apps is refused with 400 and { "code": "apps_choice_required" } - there's no silent empty default.
Response 201 Created - the raw initiation row (no initiated_by_name, chain_name or other joined fields). If it was auto-approved, status will already be "approved" and offboarding_id will be populated.
Other outcomes
| Status | When |
|---|---|
400 | Missing/invalid field, or no apps choice (apps_choice_required) |
403 | Caller lacks offboardings_create, the department is outside their scope, or exit_date is already past and the caller lacks the access-revocation permission ({ "code": "past_exit_date_permission" }) |
409 | employee_email already has an open offboarding ({ "code": "duplicate" }) |
List initiations
GET /api/offboarding-initiations
Managers only see initiations they submitted. Reviewers (superadmin, admin and hr with offboardings_view) see the org's initiations within their scope: a reviewer limited to some departments sees initiations for those departments plus their own submissions, and one limited to their direct reports sees only their own submissions.
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | string | pending · approved · rejected |
limit | integer | Default 50, max 200 |
offset | integer | Row offset |
Response 200 OK
{
"rows": [ /* initiation objects */ ],
"total": 1,
"limit": 50,
"offset": 0
}Get an initiation
GET /api/offboarding-initiations/:id
Returns the initiation with a step_history array attached - one entry per approval step that's been reached, including who acted, when, and any notes or delegation.
Managers can only fetch their own initiations; HR/admins can fetch any.
Edit an initiation
PATCH /api/offboarding-initiations/:id
Only while status is pending, and only by the original submitter or HR/admin. Accepts any subset of the submit-time fields (employee_name, employee_email, employee_role, employee_department, exit_date, separation_type, apps_to_revoke, notes).
Approve an initiation
POST /api/offboarding-initiations/:id/approve
Request body
{ "notes": "Confirmed with finance. All clear to proceed." }notes is optional. The approval that creates the offboarding also accepts apps_to_revoke (string[]) and no_apps (boolean) to override the apps chosen at submit time; the same connected-apps and apps-choice rules as Submit apply.
For chain-based initiations, only the designated approver (or role, or an active delegate) for the current step can call this - you'll get 403 otherwise, and 409 if that step was already acted on or the initiation is no longer pending. Approving the final step creates the offboarding and sets status: "approved" with offboarding_id populated. For non-chain initiations, only HR/admins can approve.
Response 200 OK - the updated initiation. On the final approval, also includes offboarding_id.
The approval that creates the offboarding can also fail with 409 when the employee already has an open offboarding ({ "code": "duplicate" }).
Reject an initiation
POST /api/offboarding-initiations/:id/reject
Request body
{
"rejection_reason": "Incorrect employee record - please resubmit with corrected exit date.",
"notes": "Optional - recorded on the approval chain step; falls back to rejection_reason if omitted."
}rejection_reason is required. notes is optional. No offboarding is created; status becomes "rejected".
Delegate the current step
POST /api/offboarding-initiations/:id/delegate
Only meaningful for chain-based initiations - returns 400 if the initiation has no chain. Only the current step's designated approver can delegate, and not to the original initiator or to themself.
Request body
{ "delegate_id": "usr_hr002", "notes": "Out this week, please cover." }Response 200 OK
{ "delegated": true, "delegate_id": "usr_hr002", "step": 2 }Withdraw an initiation
DELETE /api/offboarding-initiations/:id
Only while status is pending, by the original submitter or HR/admin. Deletes the row outright.
Response 200 OK
{ "deleted": true }Approval settings
GET /api/offboarding-initiations/settings
{ "require_initiation_approval": true, "default_chain_id": "9c1e..." }PATCH /api/offboarding-initiations/settings
Needs the settings_manage permission (Super Admin and Admin by default - HR does not have it unless a custom role grants it). Body: require_initiation_approval (boolean) and/or default_chain_id (uuid or null - must belong to your org).
HRIS integration example
A typical pattern for wiring an HRIS termination webhook to PurpletGo:
app.post('/webhooks/bamboohr', async (req, res) => {
const event = req.body;
if (event.type !== 'employee.terminated') return res.sendStatus(200);
await fetch('https://app.purpletgo.com/api/offboarding-initiations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': sessionCookie, // this endpoint is session-authenticated, not API-key based
},
body: JSON.stringify({
employee_name: event.employeeName,
employee_email: event.employeeEmail,
exit_date: event.lastDay,
separation_type: 'termination',
no_apps: true, // or apps_to_revoke: [...] - one of the two is required
notes: `BambooHR event ID: ${event.id}`,
}),
});
res.sendStatus(202);
});Related
- Offboarding API - the record that gets created once an initiation is approved
- Checklists - tasks generated automatically when the offboarding is created