Deactivating/Reactivating an Employee
Deactivate a user with DELETE /v2/company/{COMPANY_ID}/users/USER_ID and an inactive_reason in the body. Reactivate with PUT /v2/company/{COMPANY_ID}/users/USER_ID and active set to true.
DELETE is a soft delete. It sets active to false and blocks sign-in. It does not remove the user, their history, or their time punches.
Deactivation blocks a person from signing in to 7shifts.Only call these endpoints when the employee's status has actually changed in your system. A sync bug that deactivates users locks staff out of their schedules mid-service. A bug that reactivates them lets former employees back into the company.
Reactivating does not restore account access on its own.In 7shifts, a reactivated employee receives a new invite email and must accept it before they can sign in again. Until they accept, the profile is schedulable but the person is locked out and shows an "invite pending" state. Do not report a reactivation as complete in your own system on the strength of a
200response.
Use user for the API object and employee for the person.
Lifecycle
stateDiagram-v2
[*] --> Active
Active --> Inactive: DELETE with inactive_reason
Inactive --> InvitePending: PUT active true
InvitePending --> Active: employee accepts invite email
InvitePending --> Inactive: deactivated again
note right of Inactive
Profile, history, punches,
and wages all retained
end note
note right of InvitePending
Schedulable, but the
employee cannot sign in yet
end note
The gap between PUT succeeding and the employee regaining access is the part most integrations get wrong. A 200 means the profile is active. It does not mean the person is back in.
Read the valid inactive reasons first
inactive_reason accepts a fixed set of values, and the set varies by company. Fetch it rather than hardcoding it.
curl --request GET \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/inactive_reasons' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01'{
"data": [
"quit",
"terminated_or_let_go",
"medical_or_personal_leave",
"went_back_to_school",
"other"
],
"object": "inactive_reason_enum"
}The response above is abridged. A real company returns more values, including near-duplicates that differ only in wording. Match on the exact string your call returns, and fall back to other when your system's reason has no clean equivalent. See List Inactive Reasons.
The reason you send is visible to managers in the web app. They can filter and sort the inactive list by it, and it appears alongside the termination date and the name of whoever performed the deactivation.
7shifts does not validate whether a reason is appropriate or lawful in your jurisdiction, and does not generate termination paperwork. Termination records carry legal weight in many places. Consult your own employment advisor about which reasons to record and how long to retain them.
Deactivate a user
curl --request DELETE \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/users/USER_ID' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01' \
--header 'Content-Type: application/json' \
--data '{
"inactive_reason": "quit",
"inactive_comments": "{FREE_TEXT_NOTE}"
}'This DELETE carries a request body. Some HTTP clients strip bodies from DELETE requests by default — if the call returns 400 complaining about a missing reason, check your client before checking your payload.
inactive_comments is free text and optional. Managers see it on the inactive profile, so treat it as visible to people, not as an internal debug field.
Deactivation is not retroactively editable. Profiles deactivated before the reason and date fields existed have those fields blank permanently, so expect nulls when reading historical records.
See Deactivate User for the full parameter list.
Reactivate a user
curl --request PUT \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/users/USER_ID' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01' \
--header 'Content-Type: application/json' \
--data '{
"active": true
}'The profile becomes active immediately and can be added to a schedule right away. Account access is separate: the employee must accept a fresh invite email before signing in.
Poll the user record or watch the user.reactivated webhook rather than assuming access is restored. See Webhooks.
Reactivation restores the profile but does not restore role assignments removed while the user was inactive. Verify locations, departments, and roles afterward. See Mapping.
Admins are a special case
A user who was an admin at the time of deactivation can only be reactivated by the account owner. If reactivation fails for a former admin, this is the likely cause, and it needs a human in the web app rather than a retry. See How to reactivate a deactivated admin.
Employee Sync can undo your reactivation
If a company runs a POS employee sync, that sync is also writing user status. A reactivated employee who is still inactive in the POS gets deactivated again on the next sync, with a reason of "deactivated on POS".
graph TD
A[Your system reactivates<br/>via PUT active true] --> B[Profile active in 7shifts]
B --> C{Employee status<br/>in the POS?}
C -->|Active and mapped| D[Stays active]
C -->|Inactive, ignored,<br/>or unmapped| E[Employee Sync<br/>deactivates again]
E --> F[Reason: deactivated on POS]
This is a data problem, not an API problem, and retrying the PUT will not fix it. The employee must be active and correctly mapped in the POS. In the web app, that means checking the Ignored and Unmapped tabs under Integrations > Mapping.
If you are building the POS integration, treat 7shifts user status as something you own for mapped employees. Reactivating in 7shifts without reactivating in the source system produces a flapping record.
See Employee Sync and What "deactivated on POS" means.
What deactivation does not do
| Effect | Deactivation |
|---|---|
| Sign-in | Blocked |
| User record and ID | Retained |
| Historical time punches | Retained |
| Past shifts | Retained |
| Future scheduled shifts | Not removed. Clear them separately if needed. |
| Wage records | Retained |
| Reports | Still included. Timesheet reports have an "include inactive" option. |
| Tip pool reports | Included only if the user was correctly mapped before deactivation. |
Because the ID persists, keep your mapping between your employee record and the 7shifts user ID after deactivation. Reactivating a user you have unmapped means creating a duplicate.
Find inactive users
GET /v2/company/{COMPANY_ID}/users returns active users by default. Use the active=false parameter to include inactive ones. An inactive user cannot be mapped in a new integration until someone reactivates them.
Permissions
The API acts with the permissions of the credential you use, and reactivation in particular is permission-gated. A 403 on a call that works for one company can be a permissions difference rather than a bug.
| Actor | Can deactivate | Can reactivate |
|---|---|---|
| Account owner | Yes | Yes, including former admins |
| Admin | Yes | Yes, except former admins |
| Manager or assistant manager | With the deactivate or edit permission | With the add-employees permission, and only for their own locations and departments |
Error responses
| Status | Meaning | What to do |
|---|---|---|
400 | Missing or invalid inactive_reason, or a stripped request body | Confirm the value came from GET /v2/company/{COMPANY_ID}/inactive_reasons and that your client sends bodies on DELETE. |
401 | Token missing, expired, or revoked | Re-authenticate. See Authentication. |
403 | Token cannot modify this user | Check permissions and scope. For a former admin, reactivation requires the account owner. |
404 | User does not exist in this company | Confirm USER_ID and that it belongs to {COMPANY_ID}. |
429 | Rate limited | Back off and retry. Bulk offboarding will hit this. |
Deactivating an already-inactive user is not an error worth retrying on. Check active in the response rather than treating a non-200 as a failed sync.
A user who reverts to inactive shortly after a successful reactivation is almost always the POS sync, not a failed write.
Related
Updated 5 days ago
