Automatic home folder creation for new users in a shared Employees room
This example shows how to automatically create a personal home folder for every new user inside an existing shared room/folder (for example, Employees), instead of creating a separate room per user.
Before you start
- Replace
https://yourportal.onlyoffice.comandYOUR_API_KEYwith your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations. - 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.
- Expose your webhook endpoint over HTTPS in production (terminate TLS in your app or behind a reverse proxy/load balancer, and reject non-HTTPS requests). For local testing, use a secure tunnel that provides an HTTPS URL.
Webhook configuration
This example relies on DocSpace webhooks. To register and manage webhooks on your portal, see Webhooks and the Help Center instructions: https://helpcenter.onlyoffice.com/administration/docspace-webhooks.aspx.
- Register a webhook and enable the triggers used in this example.
- Set the payload URL to your backend endpoint.
- Generate/set a secret key and store it on the backend as
WEBHOOK_SECRET. DocSpace sends the signature in thex-docspace-signature-256header so you can validate the request against the raw body.
Timeouts and retries
DocSpace retries failed webhook deliveries. According to the webhook docs:
- Up to 5 attempts are made, with exponential backoff (
2^attemptseconds). - Any successful status (any 2xx) stops retries.
- If your endpoint returns
410 Gone, the webhook is removed from the portal.
Keep the handler fast: validate the request, enqueue background work if needed, and return quickly. Make processing idempotent because the same event may be delivered more than once.
Full example
- Node.js
- Python
import express from "express";
import crypto from "crypto";
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 WEBHOOK_SECRET = process.env.DOCSPACE_WEBHOOK_SECRET; // Optional. If empty, the signature check is skipped (dev only).
const EMPLOYEES_ROOM_ID = 539564;
const TEMPLATE_FOLDER_ID = 341029;
const HR_GROUP_ID = "694513";
const ALLOWED_TRIGGERS = new Set(["user.created"]);
const HEADERS = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
};
const app = express();
// Step 1: Receive and validate the webhook request
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
},
})
);
function verify_signature(secretKey, rawBody, signatureHeader) {
// If WEBHOOK_SECRET is empty, skip verification
if (!secretKey) return true;
if (!signatureHeader || !signatureHeader.startsWith("sha256=")) return false;
const parts = signatureHeader.split("=", 2);
if (parts.length < 2 || !parts[1]) return false;
const received = parts[1].trim().toLowerCase();
const expected = crypto.createHmac("sha256", secretKey).update(rawBody).digest("hex").toLowerCase();
try {
return crypto.timingSafeEqual(Buffer.from(received, "utf8"), Buffer.from(expected, "utf8"));
} catch {
return false;
}
}
async function docspace_request(pathname, method = "GET", json_body = null) {
const url = `${API_HOST}${pathname}`;
let res;
try {
res = await fetch(url, {
method: String(method).toUpperCase(),
headers: HEADERS,
body: json_body ? JSON.stringify(json_body) : undefined,
});
} catch (e) {
console.error(`[ERROR] Request failed: ${method} ${url} -> ${e?.message || e}`);
return null;
}
if (!res.ok) {
const text = await res.text().catch(() => "");
console.error(`[ERROR] API 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 get_response_node(data) {
if (data && typeof data === "object" && !Array.isArray(data) && "response" in data) {
return data.response;
}
return data;
}
function extract_trigger_and_payload(body) {
const trigger = String((body?.event?.trigger || "")).trim();
const payload = body?.payload;
return { trigger, payload: payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null };
}
function extract_user_id(payload) {
const raw = payload?.id;
const val = raw !== undefined && raw !== null ? String(raw).trim() : "";
return val ? val : null;
}
function build_display_name(payload) {
const first_name = String(payload?.firstName || "").trim();
const last_name = String(payload?.lastName || "").trim();
const full = `${first_name} ${last_name}`.trim();
if (full) return full;
const user_name = payload?.userName;
if (user_name) return String(user_name);
const email = payload?.email;
if (email) return String(email);
return "New user";
}
// Step 2: Create or reuse the user's home folder
async function list_children(folder_id) {
const data = await docspace_request(`/api/2.0/files/${folder_id}`, "GET", null);
const node = get_response_node(data);
if (Array.isArray(node)) {
return node.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
if (node && typeof node === "object" && !Array.isArray(node)) {
const items = node.files || node.folders || node.items || node.response || [];
if (Array.isArray(items)) {
return items.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
}
if (data && typeof data === "object" && !Array.isArray(data)) {
const resp = data.response;
if (Array.isArray(resp)) {
return resp.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
}
return [];
}
async function find_folder_by_title(parent_id, title) {
const items = await list_children(parent_id);
const title_norm = String(title || "").trim();
for (const it of items) {
const it_title = String(it.title || "").trim();
if (it_title !== title_norm) continue;
const is_folder =
it.isFolder === true ||
it.folder === true ||
String(it.type || "").toLowerCase() === "folder" ||
it.folderId !== undefined && it.folderId !== null;
if (!is_folder) continue;
const raw_id = it.id ?? it.folderId;
const n = Number(raw_id);
if (Number.isFinite(n)) return n;
}
return null;
}
async function create_folder(parent_folder_id, title) {
const payload = { title };
const data = await docspace_request(`/api/2.0/files/folder/${parent_folder_id}`, "POST", payload);
const node = get_response_node(data);
if (!node || typeof node !== "object" || Array.isArray(node)) return null;
const raw_id = node.id ?? node.folderId;
const n = Number(raw_id);
return Number.isFinite(n) ? n : null;
}
async function get_or_create_home_folder(parent_id, folder_title) {
const existing = await find_folder_by_title(parent_id, folder_title);
if (existing !== null) {
console.log(`[INFO] Home folder already exists: "${folder_title}" (id=${existing})`);
return existing;
}
const created = await create_folder(parent_id, folder_title);
if (created !== null) {
console.log(`[INFO] Home folder created: "${folder_title}" (id=${created})`);
return created;
}
console.error(`[ERROR] Failed to create home folder: "${folder_title}"`);
return null;
}
// Step 3: Copy starter documents from the template folder
function _is_folder_item(it) {
return (
it?.isFolder === true ||
it?.folder === true ||
String(it?.type || "").toLowerCase() === "folder"
);
}
async function list_files_in_folder(folder_id) {
const items = await list_children(folder_id);
const out = [];
for (const it of items) {
if (_is_folder_item(it)) continue;
if (it && typeof it === "object" && "id" in it) out.push(it);
}
return out;
}
async function copy_file_to_folder(file_id, dest_folder_id) {
const payload = { destFolderId: dest_folder_id, destTitle: null };
const data = await docspace_request(`/api/2.0/files/file/${file_id}/copyas`, "POST", payload);
return data !== null;
}
async function copy_templates_to_home(template_folder_id, home_folder_id) {
const files = await list_files_in_folder(template_folder_id);
if (!files.length) {
console.warn(`[WARN] No files found in TEMPLATE_FOLDER_ID=${template_folder_id}. Nothing to copy.`);
return;
}
for (const f of files) {
const raw_id = f.id;
const file_id = Number(raw_id);
if (!Number.isFinite(file_id)) continue;
const ok = await copy_file_to_folder(file_id, home_folder_id);
if (ok) console.log(`[INFO] Template copied: fileId=${file_id} -> homeFolderId=${home_folder_id}`);
else console.warn(`[WARN] Failed to copy template: fileId=${file_id} -> homeFolderId=${home_folder_id}`);
}
}
// Step 4: Share the home folder with the user (and optional HR group)
async function share_home_folder(home_folder_id, user_id) {
const share = [{ shareTo: user_id, access: "ReadWrite" }];
if (HR_GROUP_ID) {
share.push({ shareTo: String(HR_GROUP_ID), access: "Read" });
}
const payload = { share };
const data = await docspace_request(`/api/2.0/files/folder/${home_folder_id}/share`, "PUT", payload);
return data !== null;
}
// Step 5: Send a welcome message (optional)
function send_welcome_email(user_payload, home_folder_id) {
const email = String(user_payload?.email || "unknown");
const first_name = String(user_payload?.firstName || "there");
const folder_link = `${API_HOST}/products/files/#/files/folder/${home_folder_id}`;
console.log("[WELCOME EMAIL]");
console.log(`To: ${email}`);
console.log("Subject: Welcome to DocSpace");
console.log(`Body: Hello ${first_name}, your personal folder has been created in /Employees.`);
console.log(`Link: ${folder_link}`);
}
async function handle_user_created(user_id, user_payload) {
const display_name = build_display_name(user_payload);
console.log(`[INFO] User created: userId=${user_id}, displayName=${display_name}`);
const home_folder_id = await get_or_create_home_folder(EMPLOYEES_ROOM_ID, display_name);
if (home_folder_id === null) {
throw new Error(`Failed to create or find home folder for userId=${user_id}`);
}
await copy_templates_to_home(TEMPLATE_FOLDER_ID, home_folder_id);
const shared = await share_home_folder(home_folder_id, user_id);
if (shared) console.log(`[INFO] Home folder shared: homeFolderId=${home_folder_id}`);
else console.warn(`[WARN] Failed to share home folder: homeFolderId=${home_folder_id}`);
send_welcome_email(user_payload, home_folder_id);
console.log("[INFO] Provisioning completed.");
}
app.head("/docspace/webhook", (req, res) => res.status(200).send(""));
app.get("/docspace/webhook", (req, res) => res.json({ status: "ok" }));
app.post("/docspace/webhook", async (req, res) => {
// Step 1: Receive and validate the webhook request
const raw_body = req.rawBody;
const signature_header = req.header("x-docspace-signature-256");
if (!verify_signature(WEBHOOK_SECRET, raw_body, signature_header)) {
console.warn("[WARN] Invalid webhook signature.");
return res.status(401).send("Invalid signature");
}
const body = req.body;
if (!body || typeof body !== "object") return res.json({ status: "ok" });
const { trigger, payload } = extract_trigger_and_payload(body);
if (!ALLOWED_TRIGGERS.has(trigger)) {
console.log(`[INFO] Trigger '${trigger}' is not handled. Skipping.`);
return res.json({ status: "ok" });
}
if (!payload) {
console.log("[INFO] No payload object. Skipping.");
return res.json({ status: "ok" });
}
const user_id = extract_user_id(payload);
if (user_id === null) {
console.log("[INFO] No user ID in payload. Skipping.");
return res.json({ status: "ok" });
}
try {
await handle_user_created(user_id, payload);
} catch (e) {
console.error("[ERROR] Handler failed:", e?.message || e);
return res.status(500).send("Internal error");
}
return res.json({ status: "ok" });
});
const port = Number(process.env.PORT || 3000);
app.listen(port, "0.0.0.0", () => {
console.log(`Webhook listener: http://localhost:${port}/docspace/webhook`);
});
from flask import Flask, request, jsonify
import hmac
import hashlib
import requests
import os
API_HOST = os.environ.get("DOCSPACE_API_HOST") # Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
API_KEY = os.environ.get("DOCSPACE_API_KEY") # Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.
WEBHOOK_SECRET = os.environ.get("DOCSPACE_WEBHOOK_SECRET") # Optional. If empty, the signature check is skipped (dev only).
EMPLOYEES_ROOM_ID = 539564
TEMPLATE_FOLDER_ID = 341029
HR_GROUP_ID = "694513"
ALLOWED_TRIGGERS = {
"user.created",
}
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
}
app = Flask(__name__)
def verify_signature(secret_key, raw_body, signature_header):
# If WEBHOOK_SECRET is empty, skip verification
if not secret_key:
return True
if not signature_header or not signature_header.startswith("sha256="):
return False
parts = signature_header.split("=", 1)
if len(parts) < 2 or not parts[1]:
return False
received = parts[1].strip().lower()
expected = hmac.new(secret_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest().lower()
return hmac.compare_digest(received, expected)
def docspace_request(
path,
method = "GET",
json_body = None
):
url = f"{API_HOST}{path}"
try:
r = requests.request(method.upper(), url, headers=HEADERS, json=json_body, timeout=30)
except Exception as e:
print(f"[ERROR] Request failed: {method} {url} -> {e}")
return None
if not (200 <= r.status_code < 300):
print(f"[ERROR] API error: {method} {url} -> {r.status_code} {r.text}")
return None
try:
return r.json()
except Exception:
print(f"[ERROR] Non-JSON response: {method} {url}")
return None
def get_response_node(data):
if isinstance(data, dict) and "response" in data:
return data.get("response")
return data
def extract_trigger_and_payload(body):
trigger = str(((body.get("event") or {}).get("trigger") or "")).strip()
payload = body.get("payload")
return trigger, payload if isinstance(payload, dict) else None
def extract_user_id(payload):
raw = payload.get("id")
val = str(raw).strip() if raw is not None else ""
return val if val else None
def build_display_name(payload):
first_name = str(payload.get("firstName") or "").strip()
last_name = str(payload.get("lastName") or "").strip()
full = (first_name + " " + last_name).strip()
if full:
return full
user_name = payload.get("userName")
if user_name:
return str(user_name)
email = payload.get("email")
if email:
return str(email)
return "New user"
# Step 2: Create /Employees/{DisplayName} (or reuse it)
def list_children(folder_id):
data = docspace_request(f"/api/2.0/files/{folder_id}", "GET")
node = get_response_node(data)
if isinstance(node, list):
return [x for x in node if isinstance(x, dict)]
if isinstance(node, dict):
items = node.get("files") or node.get("folders") or node.get("items") or node.get("response") or []
if isinstance(items, list):
return [x for x in items if isinstance(x, dict)]
if isinstance(data, dict):
resp = data.get("response")
if isinstance(resp, list):
return [x for x in resp if isinstance(x, dict)]
return []
def find_folder_by_title(parent_id, title):
items = list_children(parent_id)
title_norm = str(title or "").strip()
for it in items:
it_title = str(it.get("title") or "").strip()
if it_title != title_norm:
continue
is_folder = (
it.get("isFolder") is True
or it.get("folder") is True
or str(it.get("type") or "").lower() == "folder"
or it.get("folderId") is not None
)
if not is_folder:
continue
raw_id = it.get("id") or it.get("folderId")
try:
return int(raw_id)
except Exception:
continue
return None
def create_folder(parent_folder_id, title):
payload = {"title": title}
data = docspace_request(f"/api/2.0/files/folder/{parent_folder_id}", "POST", json_body=payload)
node = get_response_node(data)
if not isinstance(node, dict):
return None
folder_id_raw = node.get("id") or node.get("folderId")
try:
return int(folder_id_raw)
except Exception:
return None
def get_or_create_home_folder(parent_id, folder_title):
existing = find_folder_by_title(parent_id, folder_title)
if existing is not None:
print(f'[INFO] Home folder already exists: "{folder_title}" (id={existing})')
return existing
created = create_folder(parent_id, folder_title)
if created is not None:
print(f'[INFO] Home folder created: "{folder_title}" (id={created})')
return created
print(f'[ERROR] Failed to create home folder: "{folder_title}"')
return None
# Step 3: Copy starter documents from the template folder
def list_files_in_folder(folder_id):
items = list_children(folder_id)
out = []
for it in items:
is_folder = (
it.get("isFolder") is True
or it.get("folder") is True
or str(it.get("type") or "").lower() == "folder"
)
if is_folder:
continue
if "id" in it:
out.append(it)
return out
def copy_file_to_folder(file_id, dest_folder_id):
payload = {"destFolderId": dest_folder_id, "destTitle": None}
data = docspace_request(f"/api/2.0/files/file/{file_id}/copyas", "POST", json_body=payload)
return data is not None
def copy_templates_to_home(template_folder_id, home_folder_id):
files = list_files_in_folder(template_folder_id)
if not files:
print(f"[WARN] No files found in TEMPLATE_FOLDER_ID={template_folder_id}. Nothing to copy.")
return
for f in files:
raw_id = f.get("id")
try:
file_id = int(raw_id)
except Exception:
continue
ok = copy_file_to_folder(file_id, home_folder_id)
if ok:
print(f"[INFO] Template copied: fileId={file_id} -> homeFolderId={home_folder_id}")
else:
print(f"[WARN] Failed to copy template: fileId={file_id} -> homeFolderId={home_folder_id}")
# Step 4: Share the home folder with the user (and optional HR group)
def share_home_folder(home_folder_id, user_id):
share = [
{"shareTo": user_id, "access": "ReadWrite"},
]
if HR_GROUP_ID:
share.append({"shareTo": str(HR_GROUP_ID), "access": "Read"})
payload = {"share": share}
data = docspace_request(f"/api/2.0/files/folder/{home_folder_id}/share", "PUT", json_body=payload)
return data is not None
# Step 5: Send a welcome message (optional)
def send_welcome_email(user_payload, home_folder_id):
email = str(user_payload.get("email") or "unknown")
first_name = str(user_payload.get("firstName") or "there")
folder_link = f"{API_HOST}/products/files/#/files/folder/{home_folder_id}"
print("[WELCOME EMAIL]")
print(f"To: {email}")
print("Subject: Welcome to DocSpace")
print(f"Body: Hello {first_name}, your personal folder has been created in /Employees.")
print(f"Link: {folder_link}")
def handle_user_created(user_id, user_payload):
display_name = build_display_name(user_payload)
print(f"[INFO] User created: userId={user_id}, displayName={display_name}")
home_folder_id = get_or_create_home_folder(EMPLOYEES_ROOM_ID, display_name)
if home_folder_id is None:
raise Exception(f"Failed to create or find home folder for userId={user_id}")
copy_templates_to_home(TEMPLATE_FOLDER_ID, home_folder_id)
if share_home_folder(home_folder_id, user_id):
print(f"[INFO] Home folder shared: homeFolderId={home_folder_id}")
else:
print(f"[WARN] Failed to share home folder: homeFolderId={home_folder_id}")
send_welcome_email(user_payload, home_folder_id)
print("[INFO] Provisioning completed.")
@app.route("/docspace/webhook", methods=["POST", "HEAD", "GET"])
def docspace_webhook():
if request.method == "HEAD":
return "", 200
if request.method == "GET":
return jsonify({"status": "ok"}), 200
# Step 1: Receive and validate the user.created webhook
raw_body = request.data
signature_header = request.headers.get("x-docspace-signature-256")
if not verify_signature(WEBHOOK_SECRET, raw_body, signature_header):
print("[WARN] Invalid webhook signature.")
return "Invalid signature", 401
body = request.get_json(silent=True)
if not isinstance(body, dict) or not body:
return jsonify({"status": "ok"}), 200
trigger, payload = extract_trigger_and_payload(body)
if trigger not in ALLOWED_TRIGGERS:
print(f"[INFO] Trigger '{trigger}' is not handled. Skipping.")
return jsonify({"status": "ok"}), 200
if not payload:
print("[INFO] No payload object. Skipping.")
return jsonify({"status": "ok"}), 200
user_id = extract_user_id(payload)
if user_id is None:
print("[INFO] No user ID in payload. Skipping.")
return jsonify({"status": "ok"}), 200
try:
handle_user_created(user_id, payload)
except Exception as e:
print("[ERROR] Handler failed:", e)
return "Internal error", 500
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
port = int(os.environ.get("PORT", 3000))
print(f"Webhook listener: http://localhost:{port}/docspace/webhook")
app.run(host="0.0.0.0", port=port)
Step 1: Receive and validate the webhook request
When an event happens in DocSpace (for example, a user is created), DocSpace sends a request to your webhook URL.
At this step the script simply:
- receives the request from DocSpace,
- reads the event type (
event.trigger), - reads the main data object (
payload).
If the event is not one you are interested in, the script stops and does nothing.
- Node.js
- Python
app.post("/docspace/webhook", async (req, res) => {
// Step 1: Receive and validate the webhook request
const raw_body = req.rawBody;
const signature_header = req.header("x-docspace-signature-256");
if (!verify_signature(WEBHOOK_SECRET, raw_body, signature_header)) {
console.warn("[WARN] Invalid webhook signature.");
return res.status(401).send("Invalid signature");
}
const body = req.body;
if (!body || typeof body !== "object") return res.json({ status: "ok" });
const { trigger, payload } = extract_trigger_and_payload(body);
if (!ALLOWED_TRIGGERS.has(trigger)) {
console.log(`[INFO] Trigger '${trigger}' is not handled. Skipping.`);
return res.json({ status: "ok" });
}
if (!payload) {
console.log("[INFO] No payload object. Skipping.");
return res.json({ status: "ok" });
}
const user_id = extract_user_id(payload);
if (user_id === null) {
console.log("[INFO] No user ID in payload. Skipping.");
return res.json({ status: "ok" });
}
try {
await handle_user_created(user_id, payload);
} catch (e) {
console.error("[ERROR] Handler failed:", e?.message || e);
return res.status(500).send("Internal error");
}
return res.json({ status: "ok" });
});
@app.route("/docspace/webhook", methods=["POST", "HEAD", "GET"])
def docspace_webhook():
if request.method == "HEAD":
return "", 200
if request.method == "GET":
return jsonify({"status": "ok"}), 200
# Step 1: Receive and validate the user.created webhook
raw_body = request.data
signature_header = request.headers.get("x-docspace-signature-256")
if not verify_signature(WEBHOOK_SECRET, raw_body, signature_header):
print("[WARN] Invalid webhook signature.")
return "Invalid signature", 401
body = request.get_json(silent=True)
if not isinstance(body, dict) or not body:
return jsonify({"status": "ok"}), 200
trigger, payload = extract_trigger_and_payload(body)
if trigger not in ALLOWED_TRIGGERS:
print(f"[INFO] Trigger '{trigger}' is not handled. Skipping.")
return jsonify({"status": "ok"}), 200
if not payload:
print("[INFO] No payload object. Skipping.")
return jsonify({"status": "ok"}), 200
user_id = extract_user_id(payload)
if user_id is None:
print("[INFO] No user ID in payload. Skipping.")
return jsonify({"status": "ok"}), 200
try:
handle_user_created(user_id, payload)
except Exception as e:
print("[ERROR] Handler failed:", e)
return "Internal error", 500
return jsonify({"status": "ok"}), 200
Step 2: Create or reuse the user's home folder
A folder name is generated from the user data:
firstName+lastName(fallback:userName, thenemail)
Then the script:
- lists items in the shared Employees location (
EMPLOYEES_ROOM_ID) - reuses an existing folder if a folder with the same title is found
- otherwise creates a new folder with the generated title.
- Node.js
- Python
async function list_children(folder_id) {
const data = await docspace_request(`/api/2.0/files/${folder_id}`, "GET", null);
const node = get_response_node(data);
if (Array.isArray(node)) {
return node.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
if (node && typeof node === "object" && !Array.isArray(node)) {
const items = node.files || node.folders || node.items || node.response || [];
if (Array.isArray(items)) {
return items.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
}
if (data && typeof data === "object" && !Array.isArray(data)) {
const resp = data.response;
if (Array.isArray(resp)) {
return resp.filter((x) => x && typeof x === "object" && !Array.isArray(x));
}
}
return [];
}
def list_children(folder_id):
data = docspace_request(f"/api/2.0/files/{folder_id}", "GET")
node = get_response_node(data)
if isinstance(node, list):
return [x for x in node if isinstance(x, dict)]
if isinstance(node, dict):
items = node.get("files") or node.get("folders") or node.get("items") or node.get("response") or []
if isinstance(items, list):
return [x for x in items if isinstance(x, dict)]
if isinstance(data, dict):
resp = data.get("response")
if isinstance(resp, list):
return [x for x in resp if isinstance(x, dict)]
return []
Step 3: Copy starter documents from the template folder
To populate the home folder with standard files, the script:
- lists files in
TEMPLATE_FOLDER_ID - copies each file into the home folder using the DocSpace copy endpoint.
- Node.js
- Python
function _is_folder_item(it) {
return (
it?.isFolder === true ||
it?.folder === true ||
String(it?.type || "").toLowerCase() === "folder"
);
}
async function list_files_in_folder(folder_id) {
const items = await list_children(folder_id);
const out = [];
for (const it of items) {
if (_is_folder_item(it)) continue;
if (it && typeof it === "object" && "id" in it) out.push(it);
}
return out;
}
def list_files_in_folder(folder_id):
items = list_children(folder_id)
out = []
for it in items:
is_folder = (
it.get("isFolder") is True
or it.get("folder") is True
or str(it.get("type") or "").lower() == "folder"
)
if is_folder:
continue
if "id" in it:
out.append(it)
return out
Step 4: Share the home folder with the user (and optional HR group)
Access permissions are applied using the folder share endpoint. In the example:
- the new user receives
ReadWrite HR_GROUP_ID(if configured) receivesRead.
- Node.js
- Python
async function share_home_folder(home_folder_id, user_id) {
const share = [{ shareTo: user_id, access: "ReadWrite" }];
if (HR_GROUP_ID) {
share.push({ shareTo: String(HR_GROUP_ID), access: "Read" });
}
const payload = { share };
const data = await docspace_request(`/api/2.0/files/folder/${home_folder_id}/share`, "PUT", payload);
return data !== null;
}
def share_home_folder(home_folder_id, user_id):
share = [
{"shareTo": user_id, "access": "ReadWrite"},
]
if HR_GROUP_ID:
share.append({"shareTo": str(HR_GROUP_ID), "access": "Read"})
payload = {"share": share}
data = docspace_request(f"/api/2.0/files/folder/{home_folder_id}/share", "PUT", json_body=payload)
return data is not None
Step 5: Send a welcome message (optional)
A placeholder welcome notification is generated containing:
- recipient email
- a link to the created home folder Replace this stub with SMTP/SendGrid/Slack/Teams depending on your integration.
- Node.js
- Python
function send_welcome_email(user_payload, home_folder_id) {
const email = String(user_payload?.email || "unknown");
const first_name = String(user_payload?.firstName || "there");
const folder_link = `${API_HOST}/products/files/#/files/folder/${home_folder_id}`;
console.log("[WELCOME EMAIL]");
console.log(`To: ${email}`);
console.log("Subject: Welcome to DocSpace");
console.log(`Body: Hello ${first_name}, your personal folder has been created in /Employees.`);
console.log(`Link: ${folder_link}`);
}
def send_welcome_email(user_payload, home_folder_id):
email = str(user_payload.get("email") or "unknown")
first_name = str(user_payload.get("firstName") or "there")
folder_link = f"{API_HOST}/products/files/#/files/folder/{home_folder_id}"
print("[WELCOME EMAIL]")
print(f"To: {email}")
print("Subject: Welcome to DocSpace")
print(f"Body: Hello {first_name}, your personal folder has been created in /Employees.")
print(f"Link: {folder_link}")