Guide: auto-assign onboarding SOPs to new hires
When a new hire joins your workspace, assign every SOP in your onboarding library that fits their departments, with a due date, and notify them yourself.
This guide assigns onboarding SOPs to new members automatically. It needs a read and write key.
Before you start
- Put onboarding SOPs in one library, for example “Onboarding”. Set the departments each SOP is for in the app, or set the library’s default departments. Note the library’s
idfromGET /libraries. - Invite new hires in the app. The API can only assign SOPs to members, people who have accepted their invite and joined. Invited people aren’t members yet, and assigning to them answers
422 not_a_member. - Create the key from an admin account that will stay an admin. Assignments are recorded as assigned by the key’s creator, and the key stops working with
401 creator_not_adminif that person is removed or demoted.
The plan
Run a job on a schedule, for example every hour:
GET /peopleand pick members who joined recently (joined_at).GET /sops?library_id=<onboarding library>for the onboarding SOPs.- For each new member, assign every SOP whose
departmentsoverlap theirs, plus SOPs with no departments (for everyone). - Treat
422 already_assignedas done. That makes the job safe to run again and again: it never assigns anything twice.
The code
JavaScript
// Node 18+. Run on a schedule, e.g. hourly. Needs a read and write key.
const BASE = "https://app.sopcompass.com/api/v1";
const ONBOARDING_LIBRARY_ID = "3f6b2c1e-8a4d-4c7e-9b1a-2d5e7f8a9b01";
const LOOKBACK_DAYS = 3;
const DUE_IN_DAYS = 14;
async function api(path, init = {}) {
for (let attempt = 0; attempt < 6; attempt++) {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.SOPCOMPASS_API_KEY}`,
...(init.body ? { "Content-Type": "application/json" } : {}),
},
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") || 5) * 1000));
continue;
}
return { status: res.status, body: await res.json() };
}
throw new Error(`Gave up on ${path}`);
}
function isoDate(daysFromNow) {
return new Date(Date.now() + daysFromNow * 86400000).toISOString().slice(0, 10);
}
const since = new Date(Date.now() - LOOKBACK_DAYS * 86400000).toISOString();
const people = (await api("/people")).body.data.filter((p) => p.joined_at >= since);
const sops = [];
let cursor = null;
do {
const qs = new URLSearchParams({ library_id: ONBOARDING_LIBRARY_ID, limit: "100", ...(cursor ? { cursor } : {}) });
const page = (await api(`/sops?${qs}`)).body;
sops.push(...page.data);
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
for (const person of people) {
const theirs = sops.filter(
(s) => s.departments.length === 0 || s.departments.some((d) => person.departments.includes(d)),
);
for (const sop of theirs) {
const { status, body } = await api("/assignments", {
method: "POST",
body: JSON.stringify({
sop_id: sop.id,
email: person.email,
due_on: isoDate(DUE_IN_DAYS),
note: "Part of your onboarding. Welcome aboard!",
}),
});
if (status === 201) console.log(`Assigned "${sop.title}" to ${person.email}`);
else if (body.error.code === "already_assigned") continue;
else console.error(`${person.email} / ${sop.title}: ${status} ${body.error.code}`);
}
}The lookback window is several days wider than the schedule so a run that fails is covered by the next one; already_assigned keeps the overlap harmless.
Notify the new hire
The API doesn’t send the assignment email the app sends. Send your own welcome message from the same job, for example a Slack DM or an email from your HR system, listing the SOPs and linking to SOPCompass.
Variations
- Trigger from your HR system instead of a schedule. If your HRIS sends a webhook when someone starts, look the person up in
GET /peopleby email and assign straight away. If they haven’t joined SOPCompass yet (not_a_member), queue a retry. - Pick SOPs by tag instead of library. List SOPs and filter on
tags, for exampleonboarding. - Role-specific due dates. Give policy SOPs a 3-day
due_onand the rest 14 days. - Track progress.
GET /assignments?assignee_email=<email>shows each assignment’sstatus;GET /assignments?status=overduelists everything late.