Help center

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.

Business plan Updated
On this page
  1. Before you start
  2. The plan
  3. The code
  4. Notify the new hire
  5. Variations

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 id from GET /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_admin if that person is removed or demoted.

The plan

Run a job on a schedule, for example every hour:

  1. GET /people and pick members who joined recently (joined_at).
  2. GET /sops?library_id=<onboarding library> for the onboarding SOPs.
  3. For each new member, assign every SOP whose departments overlap theirs, plus SOPs with no departments (for everyone).
  4. Treat 422 already_assigned as 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 /people by 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 example onboarding.
  • Role-specific due dates. Give policy SOPs a 3-day due_on and the rest 14 days.
  • Track progress. GET /assignments?assignee_email=<email> shows each assignment’s status; GET /assignments?status=overdue lists everything late.