跳到主要内容

Offboard an employee and archive their workspace

This example shows a practical offboarding flow in ONLYOFFICE DocSpace:

  • disable the employee account,
  • reassign their content to an archive owner,
  • move the employee workspace folder to an archive location,
  • apply archive access rules (archive owner + optional HR),
  • generate a short offboarding report (audit + optional export placeholder).

Before you start

  1. Replace https://yourportal.onlyoffice.com and YOUR_API_KEY with your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations.
  2. Before you can make requests to the API, you need to authenticate. Check out the Personal access tokens page to learn how to obtain and use access tokens.
Full example
// Config
const API_HOST = process.env.DOCSPACE_API_HOST; // Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
const API_KEY = process.env.DOCSPACE_API_KEY; // Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.

const EMPLOYEE_USER_ID = "EMPLOYEE-USER-ID";
const EMPLOYEE_WORKSPACE_FOLDER_ID = 539564; // e.g., /Employees/John Doe
const ARCHIVE_FOLDER_ID = 748239; // e.g., /Archive/Employees
const ARCHIVE_OWNER_ID = "ARCHIVE-OWNER-USER-ID";
const HR_GROUP_ID = ""; // optional

const HEADERS: Record<string, string> = {
Accept: "application/json",
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function docspaceRequest(path: string, method: string = "GET", body: any = null) {
const url = `${API_HOST}${path}`;

const res = await fetch(url, {
method,
headers: HEADERS,
body: body ? JSON.stringify(body) : undefined,
});

if (!res.ok) {
const text = await res.text();
console.error(`[ERROR] ${method} ${url} -> ${res.status} ${text}`);
return null;
}

try {
return await res.json();
} catch {
console.error(`[ERROR] Non-JSON response: ${method} ${url}`);
return null;
}
}

function getResponseNode(data: any) {
if (data && typeof data === "object" && "response" in data) return (data as any).response;
return data;
}

// Step 1: Disable the user
async function disableUser(userId: string) {
const payload = { disabled: true };
const data = await docspaceRequest(`/api/2.0/people/${userId}/status`, "PUT", payload);
return Boolean(data);
}

// Step 2: Reassign the user content to the archive owner
async function checkReassignmentNeeded(userId: string, targetType: string = "User") {
const params = new URLSearchParams({ UserId: userId, Type: targetType });
const data = await docspaceRequest(`/api/2.0/people/reassign/necessary?${params}`, "GET");
const necessary = Boolean(data?.response ?? false);
return necessary;
}

async function startReassignment(fromUserId: string, toUserId: string, deleteProfile: boolean = false) {
const payload = { fromUserId, toUserId, deleteProfile };
const data = await docspaceRequest(`/api/2.0/people/reassign/start`, "POST", payload);
return Boolean(data);
}

async function waitReassignmentComplete(userId: string) {
while (true) {
const data = await docspaceRequest(`/api/2.0/people/reassign/progress/${userId}`, "GET");
const info = data?.response ?? {};
const pct = Number(info?.percentage ?? 0);
const done = Boolean(info?.isCompleted);

console.log(`[INFO] Reassign progress: ${pct}%`);
if (done) return true;

await sleep(2000);
}
}

// Step 3: Move the employee workspace folder into the archive folder
async function moveFolder(folderId: number, destFolderId: number) {
const payload = {
folderIds: [folderId],
destFolderId,
deleteAfter: true,
content: true,
};

// This is the same "fileops" family used in working move examples.
const candidates = [
"/api/2.0/files/fileops/move",
"/api/2.0/files/folderops/move",
];

for (const p of candidates) {
const data = await docspaceRequest(p, "PUT", payload);
if (data) return true;
}

return false;
}

// Step 4: Apply archive access rules to the archived folder
async function applyArchiveAccess(folderId: number, archiveOwnerId: string, hrGroupId: string) {
const entries: any[] = [
{ id: String(archiveOwnerId), isGroup: false, access: 4 }, // full access for archive owner
];

if (hrGroupId) {
entries.push({ id: String(hrGroupId), isGroup: true, access: 1 }); // read-only for HR
}

const payload = { entries };

// Different builds may expose folder access via different endpoints.
const candidates = [
`/api/2.0/files/folder/${folderId}/share`,
`/api/2.0/files/folder/${folderId}/access`,
];

for (const p of candidates) {
const data = await docspaceRequest(p, "PUT", payload);
if (data) return true;
}

return false;
}

// Step 5: Produce an offboarding report (audit + optional export placeholder)
async function getAuditEvents(dtFromIso: string, dtToIso: string) {
const params = new URLSearchParams({ from: dtFromIso, to: dtToIso });
const data = await docspaceRequest(`/api/2.0/security/audit/events/filter?${params}`, "GET");
const events = getResponseNode(data);
return Array.isArray(events) ? events : [];
}

function printOffboardingReport(args: {
userId: string;
archivedFolderId: number;
auditEventsCount: number;
}) {
const folderLink = `${API_HOST}/products/files/#folder=${args.archivedFolderId}`;

console.log("--- OFFBOARDING REPORT ---");
console.log(`User: ${args.userId}`);
console.log(`Archived folder: ${args.archivedFolderId}`);
console.log(`Folder link: ${folderLink}`);
console.log(`Audit events (time range): ${args.auditEventsCount}`);
console.log("Export: not included in this example (depends on your internal archiving workflow).");
console.log("--- END OF REPORT ---");
}

// Run flow
(async () => {
console.log(`[INFO] Offboarding started. userId=${EMPLOYEE_USER_ID}`);

// Step 1: Disable the employee account
const disabled = await disableUser(EMPLOYEE_USER_ID);
if (!disabled) {
console.error("[ERROR] Failed to disable user.");
process.exitCode = 1;
return;
}
console.log("[INFO] User disabled.");

// Step 2: Reassign ownership of the employee's assets (optional)
const needed = await checkReassignmentNeeded(EMPLOYEE_USER_ID);
if (needed) {
const started = await startReassignment(EMPLOYEE_USER_ID, ARCHIVE_OWNER_ID, false);
if (!started) {
console.error("[ERROR] Failed to start reassignment.");
return;
}
await waitReassignmentComplete(EMPLOYEE_USER_ID);
console.log("[INFO] Reassignment completed.");
} else {
console.log("[INFO] Reassignment not required.");
}

// Step 3: Move the employee workspace folder to the archive
const moved = await moveFolder(EMPLOYEE_WORKSPACE_FOLDER_ID, ARCHIVE_FOLDER_ID);
if (!moved) {
console.error("[ERROR] Failed to move employee workspace folder to archive.");
return;
}
console.log("[INFO] Employee workspace moved to archive.");

// Step 4: Apply archive access rules (HR + archive owner)
const accessOk = await applyArchiveAccess(EMPLOYEE_WORKSPACE_FOLDER_ID, ARCHIVE_OWNER_ID, HR_GROUP_ID);
if (!accessOk) {
console.error("[ERROR] Failed to apply archive access rules.");
process.exitCode = 1;
return;
}
console.log("[INFO] Archive access rules applied.");

// Step 5: Pull recent audit events and print a summary report
const to = new Date();
const from = new Date(to.getTime() - 7 * 24 * 60 * 60 * 1000);
const iso = (d: Date) => d.toISOString().slice(0, 19); // YYYY-MM-DDTHH:MM:SS

const events = await getAuditEvents(iso(from), iso(to));
printOffboardingReport({
userId: EMPLOYEE_USER_ID,
archivedFolderId: EMPLOYEE_WORKSPACE_FOLDER_ID,
auditEventsCount: events.length,
});

console.log("[INFO] Offboarding completed.");
})().catch((e) => console.error("[ERROR]", e?.message || e));

Step 1: Disable the employee account

The script disables the employee account to prevent further access to DocSpace.

This is done using PUT /api/2.0/people/:userId with body { "disabled": true }.

After this step, the user can no longer sign in, but their files remain available.

async function disableUser(userId: string) {
const payload = { disabled: true };
const data = await docspaceRequest(`/api/2.0/people/${userId}`, "PUT", payload);
return Boolean(data);
}

Step 2: Reassign the employee's content

Before archiving the workspace, the script transfers ownership of the employee's files to an archive owner account.

The reassignment is started using POST /api/2.0/people/reassign/start.

The script waits until the reassignment is completed before continuing.

async function checkReassignmentNeeded(userId: string, targetType: string = "User") {
const params = new URLSearchParams({ UserId: userId, Type: targetType });
const data = await docspaceRequest(`/api/2.0/people/reassign/necessary?${params}`, "GET");
const necessary = Boolean(data?.response ?? false);
return necessary;
}

Step 3: Move the employee workspace to the archive

The employee's personal workspace folder is moved to a dedicated archive location.

This is done using PUT /api/2.0/files/fileops/move with the destination folder set to the archive folder ID.

async function moveFolder(folderId: number, destFolderId: number) {
const payload = {
folderIds: [folderId],
destFolderId,
deleteAfter: true,
content: true,
};

// This is the same "fileops" family used in working move examples.
const candidates = [
"/api/2.0/files/fileops/move",
"/api/2.0/files/folderops/move",
];

for (const p of candidates) {
const data = await docspaceRequest(p, "PUT", payload);
if (data) return true;
}

return false;
}

Step 4: Apply archive access rules

After the workspace is archived, the script updates access rules so that:

  • the archive owner has full access,
  • other users lose access.

This is applied using PUT /api/2.0/files/rooms/:roomId/share (or the corresponding folder access method, depending on the workspace type).

async function applyArchiveAccess(folderId: number, archiveOwnerId: string, hrGroupId: string) {
const entries: any[] = [
{ id: String(archiveOwnerId), isGroup: false, access: 4 }, // full access for archive owner
];

if (hrGroupId) {
entries.push({ id: String(hrGroupId), isGroup: true, access: 1 }); // read-only for HR
}

const payload = { entries };

// Different builds may expose folder access via different endpoints.
const candidates = [
`/api/2.0/files/folder/${folderId}/share`,
`/api/2.0/files/folder/${folderId}/access`,
];

for (const p of candidates) {
const data = await docspaceRequest(p, "PUT", payload);
if (data) return true;
}

return false;
}

Step 5: Generate an offboarding summary

Finally, the script retrieves recent audit data related to the employee using /api/2.0/security/audit/events/filter.

async function getAuditEvents(dtFromIso: string, dtToIso: string) {
const params = new URLSearchParams({ from: dtFromIso, to: dtToIso });
const data = await docspaceRequest(`/api/2.0/security/audit/events/filter?${params}`, "GET");
const events = getResponseNode(data);
return Array.isArray(events) ? events : [];
}