Reference
SDK and CLI changelog
What changed in each version of @shuffl/sdk, @shuffl/cli and @shuffl/api-contracts, and how versions and breaking changes work.
For Developers who use the SDK, CLI or API contracts
On this page
App links open your signed-in workspace. Check the selected workspace before making changes.
Versions and breaking changes
The SDK, the CLI and the API contracts share one version number and are released together. Upgrade the SDK and CLI at the same time, and pin an exact version in your project.
While the major version is 0, a minor release such as 0.2.0 to 0.3.0 can rename or remove operations and fields. A patch release such as 0.2.0 to 0.2.1 never does. Each version below lists breaking changes first, under Breaking. Read them before you upgrade.
The hosted API always runs the newest contract. An older SDK or CLI keeps working for every operation that hasn’t changed, but a call to an operation or field that was renamed or removed fails until you upgrade.
Release candidates, such as 0.4.0-rc.1, are published under the next tag. Install one with @shuffl/sdk@next. shuffl --version prints the version you have.
npm install @shuffl/sdk@0.7.6 @shuffl/cli@0.7.6
shuffl --version0.7.6 (2026-10-18)
Added
- Mentees choose their mentor:
mentorship.programs.savetakesmenteesChoose. In such a programmentorship.minereturns each waiting mentee'schoices(up to three mentors withtopicsandreasons) and the mentor theyrequested;mentorship.mentors.choose(mentorship.chooseMentor,programId,mentorId) asks that mentor. Mentors seerequestsinmentorship.mineand answer withmentorship.requests.answer(mentorship.answerRequest,decisionaccept or pass). Program summaries returnmenteesChooseandrequested; pairs can berequested. - Session notes:
mentorship.sessions.save(mentorship.saveSession,pairId,heldOn,notes,nextStep, optionalsessionId) andmentorship.sessions.remove(mentorship.removeSession). Each pair inmentorship.minereturns itssessions, aprep(the newestnextStep,openGoals,questionsandstage) and itsnextMeeting. Only the two people in the pair read notes. Keys withself:write. - Calendar targets take
mentorship_pair(a pair id) for Pick a time between a mentor and mentee.
0.7.5 (2026-10-18)
Changed
reports.playbooksandpraise.insightsreturnprevious: the same counts for the window before the one asked for (nullwhen there is nothing to compare), so a client can show the change since then without a second call.
0.7.4 (2026-10-18)
Added
- Requests Mello filed outside Shuffl:
actions.mine(self:read) lists what Mello filed for the asking person in a connected HR system and where each one stands (statussubmitted,approved,declined,cancelled,failedorunknown, withstatusLabel,provider,providerName,detailssuch as the time-off policy and dates,vendorUrl,submittedAt,settledAtandcancellable). Only the person's own; a key withself:read. sync.setWrites(integrations:write) turns a People Sync connection's write switch on or off: off, Mello files nothing there and sends the person's request to HR instead; reads carry on.sync.currentconnections now returnwrites(offered,enabled).
0.7.3 (2026-10-18)
Changed
- Mello's read as figures:
pulse.latestReadalso returnsdelta, the change since the question was last asked (change,directionup/down/flat/new,tonegood/bad/neutral/watch,textsuch as↓ 6 pts; moves under 5 points read neutral), andgap, the lowest and highest published group with thepointsbetween them, or null when the gap is under 15 points. Both are null when the result is withheld.
0.7.2 (2026-10-18)
Added
- What Mello can do:
capabilities.sheetreturns the rows Mello can answer (kind: 'ask') and do (kind: 'do') for the key's person in this workspace, each withsay(what to ask),result,system(Shuffl or the connected system's name) andstatus(available,needs_connection,needs_setuporcoming), plusneedwhen it doesn't work yet.capabilities.unlockslists what an HR system or document tool adds once connected.capabilities.requestConnection(self:write) asks HR to connect what aneeds_connectionrow needs; administrator keys read the open asks per capability withcapabilities.requests(integrations:read) and clear them withcapabilities.dismissRequests(integrations:write). Asks carry counts, never names.
0.7.1 (2026-10-18)
Added
- Celebrations insights:
reports.celebrations(reporting.celebrationInsights) returns, for the last 30, 90 or 180 days (days, default 90) and the same period before, celebrations sent (sent.current.total, withpeople,birthdays,anniversariesandsharedwith a channel or team), team cards delivered and the notes on them (cards), share answers (consent.share,consent.private,consent.waiting), messages that failed or were skipped (missed) and aweeklyseries of birthdays and anniversaries. Counts only. Administrator keys withreports:read.
0.7.0 (2026-10-18)
Changed
- The CLI reads a workspace key from
SHUFFL_API_KEYinstead ofSHUFFL_TOKEN(breaking), matching the SDK'sapiKeyoption.SHUFFL_COMPANY_KEYandSHUFFL_BASE_URLare unchanged. The README, guides, cookbook and API reference use the new name.
0.6.5 (2026-10-18)
Changed
- Playbook steps before day 1 go by email.
playbooks.enrollmentPreviewsteps returnbyEmail: true when the step goes to a hire whose Slack isn't ready yet, so it goes by email to their work email until it is. Meet steps and check-ins never go by email.
0.6.4 (2026-10-18)
Added
- Mentorship insights:
reports.mentorship(reporting.mentorshipInsights,days30, 90 or 180, optionalprogramId) returnscurrentandpreviousperiods withpairs(started,endedEarly,completed) andcheckIns(answered,met,asked,finals,helped),now(meeting,paused,suggested,waiting) andweeklypairsandcheckIns. Counts only, never goals. Administrator keys withreports:read. - Mentorship settings:
mentorship.settings.getreturns thesettingsa new program starts with (pairingWeeks,checkInWeeks,mentorCapacity), Shuffl'sdefaultsandupdatedAt;mentorship.settings.savechanges them (mentorship:write) and returnschangedandcustom.
Changed
mentorship.programs.savetakespairingWeeks,checkInWeeksandmentorCapacityas optional: a new program left without them starts with the workspace's mentorship settings, and an existing one keeps what it has.
0.6.3 (2026-10-18)
Added
- Automation insights:
groups.automationInsightscounts group automation runs finished in the lastdays(30, 90 or 180) against the period before:currentandpreviouswithdone,skipped,failedandpeoplereached,weekly,byKind, the top fivereasonsa run was skipped or failed (code,why,outcome,n), runs stillscheduledandautomationson of total.groupIdnarrows it. Administrator keys withusers:read.
0.6.2 (2026-10-18)
Changed
- The READMEs list what the SDK client covers, resource by resource, and say how the packages are licensed. Operation descriptions and release notes no longer cite internal tracker numbers.
0.6.1 (2026-10-18)
Added
- The SDK and CLI name their release on every request in an
x-shuffl-clientheader (shuffl-sdk/0.6.1, and for the CLIshuffl-cli/0.6.1 shuffl-sdk/0.6.1), so Shuffl can count requests by client and release.new Shuffl()takes an optionalclient, such asbilling-sync/1.2.0, which goes in front; Shuffl never stores that part. No operation changed.
0.6.0 (2026-10-18)
Changed
- One client for every key (breaking):
new Shuffl({ apiKey })replacescreateShufflandcreateShufflCompany, which are removed. A workspace key (shuffl_api_) calls the workspace methods at the top level (shuffl.users.list()); a company key (shuffl_company_) calls the company methods undershuffl.company(shuffl.company.members.list()). The client reads the key's prefix and refuses the other kind's methods withUNAUTHORIZEDbefore sending a request.tokenis nowapiKey(a string or a function that returns one), andbaseUrlis optional, defaulting to production. The CLI and HTTP API are unchanged.
0.5.12 (2026-10-18)
Added
- Automation runs, a page at a time:
groups.automationRunslists every group automation run in the workspace newest first, 50 at a time, each withpersonId,name,groupId,groupName,kind,trigger,outcome,why,atanddueAt.nextis the cursor for the next page (null on the last);countsgives every run by outcome (scheduled,done,skipped,failed).outcomeandgroupIdnarrow it. Administrator keys withusers:read.
0.5.11 (2026-10-18)
Changed
- Leaving playbooks start on their own:
groups.setPlaybookAutoStartnow takes a playbook counted from each person's last day (dayZero: 'last_day') as well as one counted from each start date, andplaybooks.savePlaybookProgramacceptsautoStarton one. When People Sync or People gives someone a last day ahead, they start it within ten minutes, with steps waiting for approval; someone removed is not added back for that last day.groups.allAutomationsplaybookStartsreturndayZero(start_dateorlast_day).
0.5.10 (2026-10-18)
Added
- Pulse group results over time:
pulse.groupTrends(pulse:read,days30, 90 or 180) returns each group's published, frozen totals period by period (unitweek,fortnightormonth, withperiods), foreveryoneand everydimensionsrow (department, role, tenure, group). Each row'sserieshas one entry permetric(favourable,enpsor a driver) withpoints(value, null when withheld;seen),latestandchangesince the first published point. eNPS points followenpsPeriods, the last eight eNPS questions.
0.5.9 (2026-10-18)
Added
- Playbook form steps: a written step in
playbooks.saveProgramcan bekind: 'form', going to the hire (recipient: 'hire', no to-dos) withformFields, any ofpreferredName,pronouns,bio,birthdayandphoto. The hire answers withplaybooks.answerForm(stepId, pluspreferredName,pronouns,bioandbirthday{ month, day }ornull); answers write to their profile and Celebrations birthday under their own privacy settings, and the step records only when it was answered.playbooks.minesteps returnform(fields,answeredAt) and the run returnsformValues. Profiles take and returnpreferredName.
0.5.8 (2026-10-18)
Added
- Slack channel automations:
groups.setAutomationtakeskindslack_invite(add each person to a channel) andslack_post(post that they joined, with an optionalconfig.note; joins only).config.channelIdmust be a channel Shuffl is in: the API reads it back from Slack and savesconfig.channelNameitself. Automations return the channel astarget(#name). The Slack call runs from a queue after the run is written; a refusal turns the runfailedwith Slack's reason ascode, such asnot_in_channel, and someone with no linked Slack account isskippedwithno_slack.
0.5.7 (2026-10-18)
Added
- Mentorship programs. Administrators (
mentorship:read,mentorship:write):mentorship.programs.listandmentorship.programs.get(programId: the program withmembers, each withrolementorormentee,topics,statusand openpairs, andpairswithmentor,mentee,reasons,status, dates,checkInscounts andfinalanswers; never goals).mentorship.programs.save(name,description,groupIdor null for everyone,pairingWeeks4 to 52,checkInWeeks1 to 8,mentorCapacity1 to 5) andmentorship.programs.setStatus(active,pausedorarchived, which ends open pairs).mentorship.pairs.suggestsuggests one mentor for every waiting mentee, never in their reporting line;mentorship.pairs.decide(pairId,decisionapproveorskip) starts a pair and introduces both people;mentorship.pairs.endends one early;mentorship.members.setStatustakes someone off a program (removed) or puts them back. Everyone (self:read,self:write):mentorship.minelists the programs open to the key's person and their pairings withgoalsandcheckIns;mentorship.join(programId,role,topics) andmentorship.leave;mentorship.pairs.setMine(pairId,statusactive,pausedorended);mentorship.goals.saveandmentorship.goals.removefor the mentee's goals (up to 5);mentorship.checkIns.answer(checkInId,answerhelped,metornot_yet). Notification kindmentorship.badges.listincludes the automaticMentorbadge for finishing a pairing as the mentor.
Changed
home.placescan returnmentorshipwhen a mentorship program is running or a check-in waits for the person.
0.5.6 (2026-10-18)
Added
- More group automations:
groups.setAutomationtakeskindask_pulse(config.questionId),give_badge(config.badgeId),add_to_groupandremove_from_group(config.targetGroupId, a Shuffl list) andtell_person(config.personId, optionalconfig.note), anddelayDays(0 to 365) to run that many days after the join or leave. Automations returndelayDays,whenandtargetfor the new kinds; runs can bescheduledwith adueAt, andgroups.allAutomationstotals addscheduled. A run that comes due after the person moved back isskippedwith codemoved_back.groups.automationChoiceslists the Pulsequestions(id,text,retired),badgesandgroupsan automation can use. Pulse questions an automation asks gather in a send of its own that stays open four weeks and closes a week later, under the reporting floor.
0.5.5 (2026-10-18)
Added
- Playbook meet steps: a written step in
playbooks.saveProgramcan bekind: 'meet', going to the hire (recipient: 'hire', no to-dos) withmeetset tomanager,skip_level(their manager's manager),person(withpersonId) orteammates(withmeetCount, 1 to 5: people with the same manager, longest here first). Who they meet is resolved from People when the steps are reviewed and approved:playbooks.enrollmentPreviewandplaybooks.previewAssteps returnmeets(names; empty when People has nobody, which skips the step at approval). The step sends one Slack group message with a card for each person and Pick a time. Calendar targets takekind: 'playbook_meet'withid<enrollment id>.<step id>, and bookings returnfrom(person,group,playbookorplaybook_meet).playbooks.minereturnsmet, the people meet steps introduced the hire to.
0.5.4 (2026-10-17)
Added
- Playbook tracks: written steps in
playbooks.saveProgramtakeonlyForandnotFor(up to 5 group ids each, fromgroups.list; empty is everyone). A step only for groups goes to people in any of them, and a step not for a group never goes to its people. Groups are checked against People when the steps are reviewed and at approval:playbooks.enrollmentPreviewsteps returntrack(why this person doesn't get it, such asOnly for Engineering, or null), a People change before approval failsplaybooks.approveuntil the steps are reviewed again, and approved steps a track leaves out are skipped with no to-dos.playbooks.programreturnstracks(each group the steps name, withid,name,kind,department,rulesin words or null,onlyandnotstep counts andhireson the playbook now in it).playbooks.previewAs(programId,personId) returns the steps one person would get, without enrolling them:dayZero,person,steps(each withref,title,kind,offsetDays,dueAt,recipient,trackandskipped, why they wouldn't get it),skippedandtracked.
0.5.3 (2026-10-17)
Added
- Connections for employees:
network.introductionsreturns the key's ownprograms(each Connections program that introduces them, withid,name,nextAt, null while paused, andbreakUntil),past(their latest introductions, each withid,at,programandwith: the other people,id,name,title,photoUrlandslackUserId) andtotal. Nothing about other people's introductions or matches not yet sent.home.placescan includeconnections.
0.5.2 (2026-10-17)
Added
- One list of join and leave rules:
groups.allAutomationsalso returnsplaybookStarts(each playbook that starts on each new hire's start date:playbookwithid,nameandstatus,groupor null for everyone,stateactiveorpaused,runs,runs30andlastRunAt) andprogramRosters(each Connections program whose people are a group:program,groupandstate).groups.setPlaybookAutoStart(programId,stateactive,pausedoroff, optionalgroupId, null for everyone) turns a playbook's start-date rule on, pauses it keeping its group, or removes it. Playbooks returnplaybook.autoStartPaused; a paused playbook starts nobody until it is turned back on.
0.5.1 (2026-10-17)
Added
- Playbook Hires view:
playbooks.programreturnshires(active,onTimepercent of to-dos done by a day after their day or null,overdue,askedForHelp) and each active row'sprogress(dayfrom day 0,done,total,nextsteptitleandat,overdueto-dos grouped by owner withownerId,ownerName,rolehire,manager,buddyorother,count,sinceandnudgedAt,unconfirmedread-and-confirm pages andhelp).playbooks.nudge(enrollmentId,ownerPersonId) sends that person one Slack reminder with their overdue to-dos for the hire and returnsstatussent,recent(already nudged today),nothing_overdue,off,no_slackorfailed.
0.5.0 (2026-10-17)
- playbooks: answerBuddyAsk, answerBuddyFollowUp, answerCheckIn, answerFirstDaysCheckIn, approve, askBuddy, assignBuddy, buddyCandidates, buddyFollowUps, buddyProgram, buddySuggestions, candidates, completeTodo, draftPlan, enroll, enrollmentPreview, firstDaysCheckIns, firstDaysResults, mine, person, plans, previewEnrollment, program, remove, respondBuddy, retry, run, savePlan, saveProgram, setBuddyVolunteer, setPlanGoal, sharePlan, update, volunteerAsBuddy - onboarding: answerBuddyAsk, answerCheckIn, approve, askBuddy, assignBuddy, buddySuggestions, candidates, completeTodo, mine, person, remove, respondBuddy, retry, update (removed)
Breaking
- Playbooks everywhere. The
onboarding.*operations are nowplaybooks.*, with the same inputs and outputs.onboarding.journey, a person's run through a playbook, isplaybooks.run, andreports.onboardingisreports.playbooks. Theonboarding:readandonboarding:writescopes areplaybooks:readandplaybooks:write; existing keys and connections keep their access under the new names. Webhook event typesonboarding.*areplaybooks.*(such asplaybooks.enrolledandplaybooks.step.sent), and subscribed endpoints were moved over. Values that said onboarding or journey say playbook: program and draftkindplaybook, theplaybookconfig onplaybooks.saveProgram,playbooks.programandprograms.*, group automation and Culture nudge kindstart_playbook, reminder kindplaybook_todo, notification kindplaybook, citation kindplaybook, person activity kindsplaybook_startedandplaybook_completed,playbookMessagesSent, and theplaybookslist onplaybooks.run. - Departments are groups. Every department is now a group (same id) marked
departmentingroups.*, and a person is in one department at most.units.saveandunits.archiveare gone: create a department withgroups.save(kindlist,departmenttrue, people inpersonIds), rename it withgroups.saveand remove it withgroups.remove; departments are no longer archived.units.listtakes noarchivedand returnsid,nameandmembersfor each department.groups.list,groups.getand the other group reads returndepartment. A department group is neveronProfileor the HR team and can't be a rules group.users.savestill sets a person's department withdepartmentId(a department group's id), andusers.page'sdepartmentIdfilter reads department groups. ProgramunitIds, knowledge audienceunitsand group rules ondepartmentkeep working with the same ids.exports.teamno longer returnsorganizationUnitsorunitMemberships: departments are inpeopleGroupsandpeopleGroupMemberswithdepartmenttrue, and an older export that carries them still imports. Thedirectory.unit.created,directory.unit.updatedanddirectory.unit.archivedwebhook events are gone, since nothing sends them; read departments withunits.listorgroups.list. An endpoint subscribed to them keeps its other events, and saving it drops them. - Teams are groups. Every team is now a group (same id) in
groups.*, andunits.*covers departments only:units.listandunits.saveno longer returnkind,hrTeamorslackUserGroupId, andunits.savetakes nokind. Groups returnsource(shuffl,slackordirectory: synced groups have read-only members),onProfile(shown on profiles and the org chart, as teams were) andhrTeam(the group that answers HR requests);groups.savetakesonProfile, and refuses member or rule changes on a synced group.users.detailsreturnsdepartmentandgroupsinstead ofunits, andusers.savetakesgroupIdsinstead ofteamIds.users.pagefilters bygroupIdinstead ofdirectoryTeamId;users.filterOptionsandprograms.optionsreturnunitsof kinddepartmentorgroup.profiles.getandnetwork.orgChartreturngroupsinstead ofteams. Pulse results no longer split by team:pulse.insightsandpulse.questionDetaildrop theteamdimension, and former team results are undergroup.digest.culturereturnsfocus.kinddepartmentorgroup. A group ruleteam is Xbecomessegment(In group).exports.teamreturnspeopleGroupsandpeopleGroupMembers. - Pulse questions go to a group, not an audience.
pulse.addQuestiontakesgroupId(one of the workspace's groups, or null for everyone) instead ofaudience, andpulse.setQuestionGroup(questionId,groupId) replacespulse.setQuestionAudience.pulse.audienceOptionsis gone: pick fromgroups.list.pulse.questions,pulse.questionDetail,pulse.actions,pulse.actionandpulse.insightsreturngroupIdandgroupLabelwhere they returnedaudienceandaudienceLabel. eNPS still goes to everyone.
Added
users.pagerows returndirectory:jobTitlefrom the directory for every key, andstartDate(a calendar date) for administrator keys only, null for everyone else.- New hire 30/60/90 plans:
playbooks.planslists the plans for the new hires the key's person manages (asManager: each new hire playbook episode withhire,startDate,programNameandplanor null) and their own plan once shared (asHire).playbooks.draftPlanhas Mello draft three goals per stage (draftedByai, orrulewithout a model);playbooks.savePlan(enrollmentId,expectedRevision,goalswithstage30, 60 or 90 andtext, at most 5 per stage) edits them;playbooks.sharePlanshows the plan to the hire;playbooks.setPlanGoal(goalId,done) ticks a goal off, for the manager or the hire. Only the hire and their manager in People change a plan.playbooks.programrows returnplan(statenone,draftorshared,done,total, andgoalswithstage,textanddonefor HR to read) on new hire playbooks. Mello now drafts each new hire's plan by itself when they join a playbook counted from their start date, and plans returnautoDrafted. - Buddy program health:
playbooks.buddyCandidates(personId) suggests up to five buddies for a new hire, never anyone in their reporting line, volunteers first, each withreasons,volunteerandload(active,total).playbooks.volunteerAsBuddy(volunteer) puts the key's person on the buddy volunteer list or takes them off;playbooks.setBuddyVolunteer(personId,statusvolunteer,removedornone) is HR's version, and only HR undoesremoved.playbooks.buddyProgramlists volunteers and recent buddies withstatus,load,hiresAnswered,hiresMet,hiresHelpedandneverMet.playbooks.buddyFollowUpsandplaybooks.answerBuddyFollowUp(feedbackId,answerhelped,metornever) are the "did you meet?" question sent to the hire and buddy on the playbook'sbuddyFollowUpOffsetDays(default 30). Onboarding playbooks also takebuddyTopics, the list sent to the buddy with the invitation. - Add people by department or community:
playbooks.previewEnrollmentandplaybooks.enrolltakedepartmentId(everyone in a department) orcommunityId(everyone who shows a community on their profile) besidegroupId; one at a time, and people already on the playbook are left out. - Playbooks that start automatically:
playbooks.saveProgramtakes, andplaybooks.programandprograms.*return,playbook.autoStart(defaultfalse) andplaybook.autoStartGroupId(a group, or null for everyone). Only a playbook whosedayZeroisstart_datecan start automatically. Once it is on, everyone People gives a start date ahead (People Sync or an edit) starts it within ten minutes, with steps waiting for approval asplaybooks.enrollleaves them. Each person starts it once per start date: someone removed is not added back, and a new start date is a new episode. - Every group automation in one list:
groups.allAutomationsreturns each automation of the workspace with itsgroup(id,name,kind,members) beside the fieldsgroups.automationsreturns,totals(active,paused,groups, anddone30,skipped30,failed30runs in the last 30 days) and the latest 20runsacross groups withgroupName.targetIdkeeps only the automations that start that playbook or program or invite to that community.groups.listandgroups.getreturnautomations, how many of the group's automations are on. - Read and confirm in playbooks: a playbook step can be
kindacknowledgeinplaybooks.saveProgram, naming one published Knowledge page inresourcesand going to thehire. Once it goes out, the person confirms the page in the app as for a policy confirmation;playbooks.programreturns each such step'sansweredAtandacknowledgedVersionId, andplaybook.journeyreturnspageandconfirmedAton its steps. - Praise in the network and badges:
network.minesuggestions carrypraised(true when the two people thanked each other, or one thanked the other, in public praise in the last year) and list the reason first (Praised you,You praised themorYou praised each other); it stays a suggestion until both say they know each other.badges.listincludes three automatic badges for being thanked in public praise:First thanks,Thanked across teams(from three departments) andLives our values(five thanks for one value). - Connections from communities:
programs.createandprograms.updatetakecommunityIds(1 to 20 of the workspace's communities) so a program's people come from those communities instead of everyone in the Slack channel or a group; a program uses one of the three, and choosing a group or communities in an update replaces the other. Only members who show the community on their profile take part, read again before each round; introductions are still posted in the program's channel.match.unitKindtakescommunity, socross-unitmixes people from different communities andwithin-unitkeeps each group inside one (within a program's own communities when it has them).programs.getandprograms.listreturncommunityNames,programs.optionsreturnscommunities(id,name,memberCountof members who show it) andprograms.communitieslists the same. - How people like to be thanked:
praise.setRecognition(recognition:publicorprivate) sets the key's own choice; choosing private hides praise they already received from their profile and the feed, and returns how many werehidden.praise.minereturnsrecognitionandnotes(receivedandgivenprivate notes, each withid,giver,recipient,text,valuesandsentAt).praise.sendtakesprivately(send each person a private note instead of posting) and sends a private note to anyone who prefers one; it returnsposted,privateNotes,notSent(names Slack refused a note to) and apraiseIdthat is null when nothing was posted.praise.insightsreturnsteamsandlocationsbesidedepartments, each row withlowwhen undercoverageLowpercent were recognized, and counts private notes as recognition.praise.settingsandpraise.saveSettingscarrymanagerNudges. - SCIM groups: a group's
sourcecan bescim, a synced group whose members follow the identity provider.sync.groupsandsync.setGrouptake a SCIM connection's id too (fromsync.current,providerscim,connection.sourcescim) when called by a signed-in company administrator:importedbrings the group into every workspace of the company,onProfileapplies to this workspace, andsyncingis always false because members arrive at once; like other synced groups, its members are read-only ingroups.saveandusers.save. - Outgoing webhooks:
webhooks.list,webhooks.get,webhooks.catalog,webhooks.create,webhooks.update,webhooks.delete,webhooks.rotateSecret,webhooks.sendTest,webhooks.deliveries.list,webhooks.deliveries.get,webhooks.deliveries.replayandwebhooks.deliveries.replayFailed, under the newwebhooks:readandwebhooks:writescopes.createtakes a publichttps://url, an optionaldescriptionandevents(catalog types; each group needs its read scope, such asknowledge:readforknowledge.*andhr:readforhr.*), and returns theendpoint, its signingsecret(whsec_…, shown only here and fromrotateSecret) and thetestDeliveryIdof thewebhook.testit queues.rotateSecrettakeskeepOldFor(now,1h,24hor7d); both secrets sign until then. Deliveries follow Standard Webhooks (webhook-id,webhook-timestamp,webhook-signature) with retries for a little over a day. The SDK exportsverifyWebhook({ body, headers, secret }),signWebhook,webhookEventCatalog,webhookEventTypesand theWebhookEventtype from@shuffl/sdk, and the catalog from@shuffl/api-contracts/webhooks. - Warm introductions:
network.requestIntro(personId,viaId) asks someone you and that person both know to introduce you. Shuffl sends them a Slack DM with Introduce and Not this time; Introduce starts a group DM with the three of you. It returnsid,created(false when the same request was already waiting) andstate(asked). The person asked must have a tie to both of you that you can see, and you can have 3 requests waiting at once.network.minereturns, on each suggestion,introVia(the first person in common Shuffl can ask, or null) andintro(your latest request about them:stateasked,introducedornot_this_time, withviaId). A decline only ever reads asnot_this_time.reminders.settings,reminders.saveSetting,reminders.mineandreminders.saveMinecover a new kind,network("Do you know this person?", choicesonce_a_weekandonce_a_month, off by default). - Choosing synced groups:
sync.groups(directory.providerGroups,connectionId) lists every group a People sync connection or the Slack workspace (its connection id is the Slack team id) offers, each withid,name,memberCount(people in it at the source),imported,groupId(the group in Shuffl, when brought in),onProfile,listed(seen by the latest sync) andseenAt, plus theconnection(id,label,provider,source).sync.setGroup(directory.setProviderGroup:connectionId,id,importedand/oronProfile) brings a group in, takes it out or shows it on profiles, and returnsgroupId,importedandsyncing(a sync started so its members arrive). New groups from a source no longer become groups by themselves: a sync lists them and only the ones brought in follow the source. Taken out, a group stays a Shuffl list with its members.sync.currentconnections returngroups(listed,imported). - Group stats:
groups.glance(id) returns a group at a glance in the same shape asusers.glance, counted over the group's current members:headcount,enough(5 people or more; below that the splits are empty),hiredThisYear,averageTenureYears,tenure,departments,timezones,me(where the key's person sits by start date, when they are in it) andtopics(what its people are asked about).leftThisYearis always null. Any key reads groups shown on profiles; an owner or administrator key reads any group. A group the key can't see isNOT_FOUND. digest.culturereturns the morning Culture page's figures:week(peoplethanked,introductionsand playbooksfinishedin the last seven days),playbooks(the four busiest active playbooks, each withid,name,leaving,activeand up to fourpeoplewithenrollmentId,name,photoUrl,day,progressPct,flag:help,no_buddyorlate, andlate),help(new hires who asked for help at a check-in in the last 14 days),wins(finishedplaybooks this week and shared workanniversariesin the next seven days),focus.company.previous(the company'sthankedPct,metPctandpulseover the 90 days before) and each outcome's shortstep.- Groups for everyone:
groups.directorylists the groups shown on profiles to any key in the workspace, the key's own groups first, each withid,name,description,source,hrTeam,memberCount,mineand up to fivefaces(id,name,title,photoUrl).groups.directoryGroup(id) reads one with everymemberthe same way; a group not shown on profiles isNOT_FOUND. People whose employment ended are left out of both. users.glancereturnsundated, how many people in the headcount have no start date, andleftThisYear, how many were marked as left in the last year: a number for an owner or administrator key, null for anyone else.celebrations:thisWeeklists who was celebrated in a channel in the last seven days (days, andpeople, each withpersonId,name,eventType:birthdayoranniversary,yearsandsentAt). Celebrations sent privately or to a person's own team are left out.- Automations on a group:
groups.setAutomationadds or changes what happens when someone joins or leaves a group (groupId,trigger:enteredorleft,kind:send_message,start_onboarding,invite_community,add_to_program,tell_hrortell_manager, and aconfigfor the kind: the message'ssubject,body,actionandchannel; aprogramId; acommunityId; or anote). A new automation starts paused;groups.setAutomationState(id,state:pausedoractive) turns it on or off,groups.removeAutomation(id) drops it with its runs, andgroups.automations(groupId) lists a group's automations with each one'ssummary,state,runsandlastRunAt, and its last 50 runs (personId,name,trigger,outcome:done,skippedorfailed, andwhya run did nothing). Runs happen from the groups refresh, once per person and entry;personActivity.personActivityreturns them asgroup_automationevents. - Message a group:
groups.prepareMessagewrites a draft to everyone in a group (groupId,subject,bodyas paragraphs, optionalactionwithlabeland apathstarting with/app, andchannel:preferred,slackoremail) and returns it withpreview(how it reads to its first recipient, with{{first_name}},{{manager}}and{{company}}filled in) andreach(recipients,reachable,alreadyToday,unreachable).groups.sendMessage(id,requestKey) queues it once per request key on each person's chosen channel, leaving out anyone HR already messaged that day, and returnsqueuedandskipped; the same key replays the result.groups.messages(groupId) lists a group's drafts and sends,groups.message(id) reads one with its preview and reach,groups.discardMessage(id) drops a draft, andgroups.export(id) returns the members ascsvwith afilename. People get the message under the newhr_messagenotification kind innotifications.preferences, which offers Slack and email and cannot be fully turned off; a Slack thread reply to one becomes an HR request. - A Connections program's people can come from a group:
programs.createandprograms.updatetakegroupId(null or omitted keeps the Slack channel roster),programs.getandprograms.listreturngroupIdandgroupName,programs.optionsreturnsgroupsto pick from, andprograms.syncRosterreads the group's members when one is set. Joining or leaving the channel no longer changes who takes part in such a program; the channel stays where introductions are posted. playbooks.previewEnrollmentandplaybooks.enrolltakegroupId: everyone in that group who is not on the journey yet is added afterrows, which may now be empty. The preview returns the expanded rows and its fingerprint covers them.groups:list,get,members,options,searchPeople,suggestions,preview,save,refresh,remove. A group is a live set of people in the workspace, kept by rules on People attributes (re-evaluated every ten minutes and on save) or a list picked by name;suggestionslists the starter groups not made yet andpreviewcounts who a set of rules would hold today.knowledge.templates:drafthas Mello research a policy template and write the whole page. Send the template id,title,body(the template text, prefilled or not), itschecks, and optionalnotesandwebsite. It returnstitle,body,choices(each value Mello chose, with itstopic,value,basis:company,lawortypical, and thesourcelink the web search returned, if any),sources,placeholders(how many are left) andresearched(whether the web search ran). Nothing is saved.knowledge.templates:factsreturns what Shuffl knows that policy templates ask for:companyName,hrContact(who answers HR requests),hrRequests(whether people can send one),countries(from the directory's time zones, most people first) andholidaysTitle(the published holidays page). The app fills a new template's placeholders with them.knowledge.handbook:exportreturns the pages published to everyone as one handbook:teamName,generatedAt,pages(each withtitle,effectiveAt,versionand its Markdownbody),leftOut(how many published pages are for particular groups or admins only) and the whole handbook asmarkdown. Each call is recorded as a download.knowledge.acknowledgements:status,request,remind,close,mine. HR asks the people who can read a published page to confirm they read it:requestasks the page's audience to confirm its published version (and messages them unlessnotifyis false),remindmessages the people who haven't confirmed,closestops asking, andstatuslists who is asked and who confirmed which version.minelists the pages the key's person is asked to confirm. Confirming itself happens only in the app.- Notification preferences cover a new kind,
policy_acknowledgement: the message asking someone to read and confirm a Knowledge page. digest:culturelists the Culture page's recommendations in Mello's order, each with itsarea(praise,connections,onboardingorpulse) andrank, andoutcomes: what happened after each recommendation acted on in the last 60 days. It also returnsfocus: each department (or team, in a smaller company) of 7 or more people with its share thanked, share introduced to another team, new hires without a buddy and Pulse score beside the company's, the areas where it is behind, and introductions between each pair of groups. Each group also carries its weakest Pulse driver (weakDriver),responsePct,joined,left,needHelp,missedCheckInsandpreviousfigures for the 90 days before, andfocus.praisecounts praise between groups.
Changed
- Badge offers when a project or event channel goes quiet:
badges.settingsreturns andbadges.saveSettingstakeschannelOffers(defaulttrue). Once a day Shuffl looks at the channels it is in whose names mark a project, launch or event and offers the channel's maker a badge for everyone who posted, once per channel; such offers havesourcechannel. - Celebration send days:
celebrations.saveandcelebrations.previewtakesendDays(ISO weekdays, 1 = Monday; Monday to Friday when left out) andoffDayShift(beforeorafter: where a birthday or anniversary on another day goes), andcelebrations.programreturns both for each celebration.celebrations.previewreturns the sample'ssendDateandshifted, andcelebrations.reviewrows carrysendDate, the local day each one goes out. - Journeys are now called playbooks. Operation descriptions, error messages and the app's links say playbook and point at
/app/playbooksand/app/playbooks/all; the old/journeysaddresses redirect there. Operation names and fields followed in a later release. - Slack user groups become synced groups with
sourceslack, kept in step with Slack's members. The Slack import also fillsjobTitle,timezone,startDateandmanagerIdindirectory.directoryPersonfrom Slack profiles for people no HR system manages. - Pulse results HR can share:
pulse.programreturns andpulse.saveProgramtakesshareResults(defaultfalse). When it is on,pulse.askedreturnsresulton each closed question asked to everyone whose reporting floor was met (unit:favourableoreNPS,value,respondents); otherwiseresultis null. - Journeys: onboarding is the first kind of journey.
playbooks.saveProgramtakes, andplaybooks.programandprograms.*return,playbook.dayZero(start_date,joinedordate: what each person's days count from),playbook.dayZeroDate(the one date whendayZeroisdate),playbook.buddyandplaybook.helpCheckIn(whether the buddy messages and the help check-in are part of the journey; both default to true). Ajoinedjourney has no steps before day 0.playbooks.enrollandplaybooks.previewEnrollmentfill a missingstartDatefrom the journey's day 0.playbook.journeytakes an optionalenrollmentIdand returnsenrollmentId,dayZero,buddyOnandjourneys(each journey the person is on, withcurrent).playbook.personOnboardingreturnsbuddyOnper enrollment. - Catch-up for people who join a journey after day 0:
playbooks.saveProgramtakes, andplaybooks.programandprograms.*return,playbook.catchUp(skip, the default,bundleorshift).playbooks.enrolltakes an optionalcatchUpfor that batch and returnscatchUpandlate, how many were added after day 0.playbooks.previewEnrollmentreturnsmissedper row andcatchUp(default,lateandsample, one person's steps under each choice).playbooks.enrollmentPreviewreturnsjoinedOnandcatchUpper step (planned,today,skippedorbundled). Steps can have the statusskippedand the kindcatch_up, the one What you missed message. - Check-ins on any day with a journey's own questions: a journey step of kind
checkpointcan havecheckpoint: 'custom'withquestions(1 to 6, each with anid,textandkind:scalewith ascaleofagree_5,rating_5oryes_no, oropen), on any day and as often as the journey likes, inplaybooks.saveProgram,playbooks.programandprograms.*.playbooks.firstDaysCheckInsreturns them withcheckpointset tostep:<journey id>:<step id>, plusintro,noteandjourneyon every check-in;playbooks.answerFirstDaysCheckIntakes thatcheckpointand answers the step's questions.playbooks.firstDaysResultsreturnsjourneys: each journey's own check-ins (programId,journey,stepKey,title,offsetDays,dayZero,sent,answered,publishedandquestions), combined only within that step and shown above the reporting floor like the built-in ones.playbook.journeyreturnskindper checkpoint (day_30,day_60,day_90,day_365orcustom) andcheckpointas the check-in's subject. - Leaving:
directory.saveDirectoryPersontakes, anddirectory.directoryPersonreturns,lastDay(the person's last day; null clears it, left out keeps it) andlastDayAnnounced(default false). Shuffl never takes or stores a reason. Until a last day is announced,directory.directoryPersonreturnslastDayas null to anyone but owners, admins and the person's manager. A journey'splaybook.dayZerocan belast_day: each person's days count from their last day in People, with steps up to 60 days before and after, and no buddy. Until the leaving is announced only steps to the manager and HR go; the others wait with statuswaitinganderrorCodeuntil_announced. A changed last day sends the run's unsent steps back for approval at the new dates, and a cleared one ends the run withremovalReasonleaving_cancelled.playbooks.previewEnrollmentreportsmissing_last_dayfor someone without one. Ending someone's employment or access no longer ends their leaving journey; only the steps to them stop. praise.feedreturnsvalues: on the first page, the five values praised most in the last 30 days (value,count), counted from the praise the key's person can read. Later pages return an empty list.- Pulse results by people group:
pulse.insightsandpulse.questionDetailreturn a fifth split,groups.dimensions.group(questionDetail:groups.group), one row per group with the samelabel,respondents,favourablePct,enpsanddriversas departments and teams.pulse.insightsalso lists every current group withseen: falseuntil a question closes after it was made. A group's figures are withheld under the reporting floor, when the people outside it are under the floor, and when it and any other published total would give fewer than the floor away by subtraction. - Praise in private Slack channels:
praise.feedandpraise.ofPersoninclude praise from private praise channels the key's person is in now, andpraise.mineleaves out private channels they have left.praise.composerreturnsprivateper channel and offers private channels only to their members;praise.sendrefuses a private channel for someone who isn't in it.praise.settingsreturnsmembersFreshandmembersErrorper channel. - Charts on the employee People page:
users.glancereturnsmonthly, the last twelve months of headcount and people who joined (empty below five people). - Onboarding to-do reminders:
reminders.settings,reminders.saveSetting,reminders.mineandreminders.saveMinecover a third kind,onboarding_todo, with the choicesone_day_lateandthree_days_late. It is on (one_day_late) unless a team turns it off. - Mello answers from the asker's onboarding journey: a citation's
kindcan beonboardinginassistant.history, linking to Your onboarding.knowledge.search,knowledge.contextandmanager.currentshare the citation shape but never return it. reports.onboardingreturnshires.withBuddy, the hires in onboarding now who have a buddy.digest.actOnNudgeanddigest.dismissNudgeacceptfrom: 'culture'for nudges acted on from the Culture page. Culture nudges have six more kinds:praise_low,pulse_low,start_praise,start_pulse,start_onboardingandstart_cross_team.
0.4.0 (2026-10-12)
The company API joins the SDK, the CLI and OpenAPI. It takes a company key (shuffl_company_) from Company settings → API keys, never a workspace key, so it has its own client and commands.
Breaking
GET /api/v1/company/members.listis paginated: it takeslimit(1 to 100, 25 by default) andcursor, and returns{ items, nextCursor }instead of an array of every member. Scripts that call it over HTTP readitemsand follownextCursor.- The first 90 days' check-ins are onboarding journey steps (
kind: 'checkpoint'withcheckpointofday_30,day_60,day_90orday_365).reminders.settings,reminders.saveSetting,reminders.mineandreminders.saveMinenow cover onlypraiseandpulse: themanager_check_inandnew_hire_check_inkinds and their choices are gone.onboarding.firstDaysResultsno longer returnsmanager, andonboarding.journeyno longer returnsmanagerCheckIns.
Added
9 operations, by resource:
company:current,details,renamecompany.emailDomains:listcompany.members:list,removecompany.slack:listcompany.workspaces:list,setDefaultonboarding:completeTodochecks off (or reopens) one of your own onboarding to-dos.
SDK, CLI and OpenAPI
- SDK:
createShufflCompany({ baseUrl, token })returns a typed client for these operations, such ascompany.members.list({ limit: 100 }). It calls the REST paths under/api/v1/companyand throwsORPCErrorlikecreateShuffl. - CLI:
shuffl companycommands, withshuffl login --company --token-stdinandshuffl logout --company, orSHUFFL_COMPANY_KEYin the environment. The company key is saved incompany.json, apart from the workspace key. - OpenAPI: the operations are under
/company, with thecompanyKeysecurity scheme andx-shuffl-credential: company.
Changed
- Company API responses return exactly the published fields:
company.detailsno longer includesinternal, andcompany.slack.listsendsrevokedAtas an ISO time. - Company API errors include
statusanddefined: false, like workspace API errors. - Onboarding journeys:
onboarding.saveProgram,onboarding.programandprograms.*carryonboarding.steps, the steps HR writes (message, to-do or check-in, day offset, recipient, Knowledge, link and community resources, to-dos).onboarding.enrollmentPreviewreturns each step'sref,title, recipient and to-dos;onboarding.programreturnsstepKeyandtitleper step;onboarding.journeyreturnstodos,gettingReadyandresources. reports.peoplereturnshistory: headcount, joins and people marked as left for each of the last 12 months, plus today's departments and tenure.exports.teamandreports.overviewreturn each program'sonboarding.stepstoo;credentials.api.create,credentials.api.listandpermissions.listinclude theonboarding.completeTodopermission.reports.onboardingreturnsmessages.weekly(sent and on time per week);praise.insightsreturnsgiversandrecognizedper week.
0.3.0 (2026-10-12)
Breaking: two fields were removed and one object was reshaped, so this is a minor release. Upgrade the SDK and CLI together.
Breaking
teams.setup:slack.connectedandslack.statusare replaced byslack.state,slack.liveandslack.reason.celebrations.previewno longer returnsfingerprint, andcelebrations.saveno longer takespreviewFingerprint.
Added
7 operations, by resource:
celebrations:saveAnniversaryDate,startDateshome:placeshr:mineonboarding:journeyteams:slackConnectionusers:glance
Changed
- Schedules take
days(1 to 7, Monday first) to pick days of the week: program schedules inprograms.*andreports.overview,reminders.saveSettingandreminders.settings.pulse.programandpulse.saveProgramaddsendEvery,sendUnit,sendStartandsendMonthWeek. - Notification kinds add
check_inandgetting_started(notifications.preferences,notifications.save). users.savetakesoriginalStartDate;exports.teamreturns it, andcelebrations.preferencesreturnsstartDate.communities.membersreturnsjoinedAt;praise.settingsreturnsslack;sync.currentreturnsslackper connection;digest.previewreturnstrendand aratioper section;operations.activityalways returnssummary.profiles.whoKnowstakesfrom;support.createtakesguides;praise.sendaccepts morefromvalues.credentials.api.create,credentials.api.listandpermissions.listinclude the new operations' permissions.
0.2.1 (2026-10-03)
No operation changes. shuffl --help and the OpenAPI summaries now describe all 303 operations in words. In 0.2.0, 133 of them showed only their operation id, such as access.get.
0.2.0 (2026-10-03)
Breaking: while the major version is 0, a minor release may rename or remove operations. Upgrade the SDK and CLI together.
Renamed
workspaces.*is nowteams.*:current,setup,status,initialize,saveAgentSetup,resendSlackWelcome,slackJoinAccess,saveSlackJoinAccess,slackProfileLink,saveSlackProfileLink,syncSlackProfileLinks.GET /api/v1/workspaceis nowGET /api/v1/team, and the/manage/workspaces/...paths are now/manage/teams/....exports.workspaceis nowexports.team.
Removed
billing.*: company billing moved to Company settings, and a team key never gains company scope.provisioning.currentandprovisioning.configure: SCIM connections are created in the browser, because creating one issues the identity provider's secret.
Added
97 operations, by resource:
badges:generatePicture,holders,saveSettings,settings,uploadPicturecelebrations:card,cards,removeCardNote,signCardcommunities:channelSuggestions,createCapabilities,createSlackChannel,fromChannels,generateCover,invite,revokeInvitation,uploadCoverdigest:actOnNudge,dismissNudge,nudge,nudges,preview,saveSettings,settingsexports:teamgetStarted:celebrate,celebration,dismiss,draftIntro,get,intro,wizardhome:preferences,react,storyhr:categories,rebuildCategories,saveCategoriesinvitations:candidates,createMany,inviteDirectoryknowledge:sources.writeonboarding:answerBuddyAsk,answerFirstDaysCheckIn,firstDaysCheckIns,firstDaysResultspraise:composer,sendprofiles:whoKnowspulse:actions,answer,audienceOptions,createAction,decideProposal,editProposal,latestRead,mine,proposals,proposeQuestions,questionDetail,requestProposals,setQuestionAudience,skip,updateActionreminders:mine,saveMine,saveSetting,settingsreports:melloTrial,melloValueruns:history.get,history.list,stopKnownFailuresteam:answerMinutes,dismissLaunchPost,launchPostteams:connectionPermissions,current,discardPairHistory,initialize,moveSlackChannels,rename,resendSlackWelcome,saveAgentSetup,saveSlackJoinAccess,saveSlackProfileLink,setup,slackChannels,slackJoinAccess,slackProfileLink,status,syncSlackProfileLinks,uploadPairHistoryusers:assignManagers,proposeReportingLines,reportingLineProposal,reviewReportingLines