People
Employees, contracts, pay and benefits, shifts and time clocks — private HR data for server-side tools and scripts.
- Use it for: Server integration
- Reads: Bearer (
apiKey) — server-side only - Writes: Bearer (
apiKey) — server-side only - CLI:
uidu employees,uidu employments,uidu offices,uidu roles,uidu ccnls,uidu time-clocks,uidu compensations,uidu benefits,uidu benefit-enrollments - Auth: what each token can do and where it may run — Authentication
People is uidu's HR module: employees, their contracts (employments) with pay and benefits,
scheduled shifts and time-clock punches. Use it for internal dashboards, payroll exports and
scripts that hire, change and end contracts. Everything here is private data, so every call —
read or write — runs server-side with an apiKey client.
Mental model
Employee HR record of a workspace user (userId) — employeeCode, kioskPin
├─→ Employment a contract: startDate / endDate / endReason, jobTitle, employmentKind,
│ │ ccnl, contractLevel, fteRatio, roleId, circleId, offices, working hours
│ ├─→ Compensation pay item: amount, currency, frequency, componentType, effectiveFrom
│ └─→ BenefitEnrollment the contract enrolled in a Benefit (workspace catalog)
├─→ Shifts scheduled work — beginsAt / finishesAt / aasmState
├─→ TimeClocks actual punches — beginsAt / finishesAt (null while still clocked in)
└─→ TimeEntries attributed hours, anchored to a Shift + TimeClock (schema only, not in the SDK)
Reference data an employment points at:
Office (listOffices) · role Circle (listRoles) · org-unit Circle (listCircles) · CCNL (listCcnls)- The name is on the user. An employee has no top-level
name: reademployee.user?.name(alsofirstName,lastName,email). Likewise the contract type iscurrentEmployment.employmentKind.name(e.g.Tempo indeterminato) — there is nocontractTypefield on the API. It used to be a free string; uidu promoted it to anEmploymentKindrecord so each workspace configures its own, which is why writes takeemploymentKindId. Only the recordslistEmploymentHistorybuilds carry a flatcontractType(a copy ofemploymentKind.name) for display. - Contract events are employment writes. A hire (assunzione) is
createEmployment; a variation (variazione) is a newcreateEmployment, orupdateEmploymenton the current one; a termination (cessazione) isupdateEmploymentwithendDate+endReason— which is exactly whatterminateEmploymentsends.deleteEmploymenterases the record; to end a contract, terminate it.endReasonis one ofresignation,termination,contract_end,retirement,other, or the finer-grainedresignation_just_cause,dismissal_just_cause,dismissal_subjective_reason,dismissal_objective_reason,collective_dismissal,mutual_agreement,probation_failed,deathandbusiness_closure. - Units.
fteRatiois a fraction (1.0full-time,0.5half-time). Dates (startDate,endDate,effectiveFrom,enrolledAt,weekStartingAt) areISO8601Datestrings (YYYY-MM-DD); punches (beginsAt,finishesAt) areISO8601DateTime. Compensation and benefit amounts are plainFloats in the givencurrency— not cents. - Schedules live on the employee, not the workspace.
getEmployeereturnstodayTimeClocksandweeklyShifts(first 20) for the week startingweekStartingAt— a required Monday date thatweekStartFor(date?)computes in UTC:weekStartFor(new Date('2026-05-15'))→'2026-05-11',weekStartFor(new Date('2026-01-01'))→'2025-12-29'; with no argument, the current week. A shift'saasmStateis e.g.planned,confirmed,completed. - Reference data.
listOfficesgives the ids foremploymentOfficesAttributes([{ officeId, isPrimary }]).listCirclesreturns two flavours of Circle told apart bykind: org units (an employment'scircleId) and roles (kind === 'role', an employment'sroleId) —listRolesreturns only the roles.listKinds(client, { models })reads the polymorphicKindrecords;listCcnlsislistKindswithmodels: ['Ccnl']. There is no list function for employment kinds: take anemploymentKind.idfrom an existing employment. - Two kinds of CCNL record.
listCcnlsreturnsKind-tagged rows (id,name,code,model).createCcnlcreates the first-classCcnlrecord (code, sector, contract levels viaccnlLevelsAttributes), and that is the typeEmployment.ccnl/ccnlIdpoint at. When you create a CCNL, use the id it returns (ccnl.node.id) asccnlId. - Payload shapes. Writes return
{ errors, … }and never throw on validation. Creates wrap the record in an edge —employee.node,employment.node,compensation.node,timeClock.node,ccnl.node,benefit.node,benefitEnrollment.node— while updates return it directly (employment,employee,compensation). - Not in the SDK yet. There is no workspace-wide list of shifts or time entries — iterate
employees and read
weeklyShiftsper employee. Time entries, approving time off, and theweeklyTimeOffs/monthlyTimeOffsfields exist in the schema but aren't wrapped; reach them throughexecuteActionwhere an action exists.
Use cases
HR dashboard of active contracts
Goal: a server-rendered table of everyone with an active contract, and a detail panel with an employee's punches today and shifts this week.
// app/hr/data.ts — server-only
import {
paginate,
ListEmployeesDocument,
getEmployee,
weekStartFor,
} from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin'; // createClient({ workspace, apiKey })
export async function activeContracts() {
const employees = await paginate(uiduAdmin, ListEmployeesDocument, {
variables: { first: 100 },
extract: (data) => data.currentWorkspace?.employees,
});
return employees
.filter((e) => e.currentEmployment?.isActive)
.map((e) => ({
id: e.id,
name: e.user?.name ?? e.user?.email, // the name is on the user
code: e.employeeCode,
jobTitle: e.currentEmployment?.jobTitle,
contract: e.currentEmployment?.employmentKind?.name, // 'Tempo indeterminato'
since: e.currentEmployment?.startDate,
}));
}
export async function employeeWeek(id: string) {
const employee = await getEmployee(uiduAdmin, {
id,
weekStartingAt: weekStartFor(), // Monday of this week, 'YYYY-MM-DD'
});
if (!employee) return null;
return {
name: employee.user?.name,
fte: employee.currentEmployment?.fteRatio, // 1.0 = full-time
level: employee.currentEmployment?.contractLevel,
office: employee.currentEmployment?.primaryOffice?.name,
clockedIn: employee.todayTimeClocks.some((tc) => !tc.finishesAt),
shifts: (employee.weeklyShifts?.edges ?? []).map((edge) => ({
begins: edge?.node?.beginsAt,
ends: edge?.node?.finishesAt,
state: edge?.node?.aasmState, // 'planned' | 'confirmed' | 'completed' | …
})),
};
}listEmployees(uiduAdmin, { first: 50 })returns one page as a flat array;paginatewithListEmployeesDocumentwalks them all — see Pagination & field values.- The list carries only a summary of the current employment (
jobTitle,employmentKind,isActive,startDate);contractLevel,classification,fteRatio,endDateandprimaryOfficecome fromgetEmployee.
Hire, change and terminate from a script
Goal: a payroll sync or an onboarding script that opens a contract for a new hire, adds the salary and a benefit, and later closes the contract.
// scripts/hire.ts — Node, server-side
import {
createEmployee,
createEmployment,
createCompensation,
createBenefitEnrollment,
terminateEmployment,
listOffices,
listRoles,
listEmployees,
} from '@uidu/client';
import { uiduAdmin } from './uidu-admin';
declare const userId: string; // an existing workspace member
declare const benefitId: string; // from createBenefit, or your catalog
// 1. Reference data for the contract form
const [offices, roles, employees] = await Promise.all([
listOffices(uiduAdmin),
listRoles(uiduAdmin), // role-Circles → roleId
listEmployees(uiduAdmin, { first: 50 }),
]);
// no list function for employment kinds: reuse one from an existing contract
const permanent = employees.find(
(e) => e.currentEmployment?.employmentKind?.name === 'Tempo indeterminato',
)?.currentEmployment?.employmentKind?.id;
// 2. The HR record for an existing workspace user
const created = await createEmployee(uiduAdmin, {
input: { attributes: { userId, employeeCode: 'E-0042' } },
});
if (created?.errors?.length) throw new Error(created.errors.map((e) => e.name).join(', '));
const employeeId = created!.employee!.node!.id; // creates wrap the record in `.node`
// 3. Hire (assunzione) = createEmployment
const hired = await createEmployment(uiduAdmin, {
input: {
attributes: {
employeeId,
startDate: '2026-11-01',
jobTitle: 'Educatore',
employmentKindId: permanent,
roleId: roles[0]?.id,
fteRatio: 0.5, // half-time
employmentOfficesAttributes: [{ officeId: offices[0]!.id, isPrimary: true }],
},
},
});
const employmentId = hired!.employment!.node!.id;
// 4. Pay and benefits hang off the employment, not the employee
await createCompensation(uiduAdmin, {
input: {
attributes: {
employmentId,
amount: 1850, // a plain amount in `currency`, not cents
currency: 'EUR',
frequency: 'monthly',
componentType: 'base',
effectiveFrom: '2026-11-01',
},
},
});
await createBenefitEnrollment(uiduAdmin, {
input: { attributes: { benefitId, employmentId, enrolledAt: '2026-11-01' } },
});
// 5. Later: termination (cessazione) closes the contract — it isn't deleted
const ended = await terminateEmployment(uiduAdmin, {
id: employmentId,
endDate: '2027-10-31',
endReason: 'contract_end',
});
ended?.employment?.endDate; // updates return the record directly, no `.node`- A variation (new level, new hours, new role) is either a new
createEmploymentfrom the change date — whichcontractEventswill report as avariation— orupdateEmployment({ input: { id, attributes } }) on the current contract when you don't need the history. createBenefitadds to the workspace's benefit catalog (name,category,provider,employerCostAmount,employeeCostAmount, …); its id is atbenefit.node.id.- Punches can be written too:
createTimeClock(uiduAdmin, { input: { attributes: { resourceId, beginsAt, finishesAt } } }), the new punch attimeClock.node. - The same writes from a terminal:
uidu employments create --attributes '{"employeeId",…}',uidu employments update <id> --attributes '{"endDate","endReason"}'— see CLI commands.
Monthly report of hires, variations and terminations
Goal: the Italian monthly HR summary — assunzioni, variazioni, cessazioni — from the full contract history.
import { listEmploymentHistory, contractEvents } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';
const history = await listEmploymentHistory(uiduAdmin, { first: 100 });
const events = contractEvents(history); // most recent first
const byMonth = events.reduce<Record<string, typeof events>>((acc, e) => {
(acc[e.month] ??= []).push(e); // 'YYYY-MM'
return acc;
}, {});
for (const [month, list] of Object.entries(byMonth)) {
const count = (t: string) => list.filter((e) => e.type === t).length;
console.log(month, {
hires: count('hire'),
variations: count('variation'),
terminations: count('termination'),
});
}
events
.filter((e) => e.type === 'termination')
.map((e) => `${e.date} ${e.employeeName} — ${e.contractType} (${e.endReason})`);listEmploymentHistoryreturns every employee with all their contracts (up toemploymentsFirst, default 50), not just the current one. Each record is flattened:{ id, name, employments: [{ id, startDate, endDate, endReason, jobTitle, employmentKind, contractType, ccnl, compensations }] }, withnamefalling back to the user's email, then'Senza nome'. Compensations (first 20) come most recenteffectiveFromfirst.contractEventsrules: per employee, contracts are sorted bystartDate; the earliest is ahire, every later one avariation, both on their start date; any contract with anendDatealso yields aterminationon it, carryingendReason. Contracts without astartDateare ignored. Each event hasdate(YYYY-MM-DD) andmonth(YYYY-MM) for grouping.first/afterpage the employees, but the flattened result carries no cursor: pass afirstthat covers the whole workforce.
API at a glance
Every @uidu/client function in People (HR), generated from the source — the full reference has the rest of the SDK.
| Function | Kind | Auth | CLI | Description |
|---|---|---|---|---|
contractEvents | helper | — | — | Flatten employment history into dated contract events, most recent first: - the employee's earliest contract is a hire (assunzione) on its start date; - every later contract is a variation (variazione) on its start date; - any contract with an end date yields a termination (cessazione) on it. |
createBenefit | write | Bearer | benefits create | Create a benefit in the workspace's catalog (name, category, provider, employer/employee costs). Returns the payload (with errors) or null; it is at benefit.node. |
createBenefitEnrollment | write | Bearer | benefit-enrollments create | Enroll an employment in a benefit (benefitId, employmentId, enrolledAt). Returns the payload (with errors) or null; it is at benefitEnrollment.node. |
createCcnl | write | Bearer | ccnls create | Create a CCNL (national collective labor contract) — the first-class Ccnl record (code, sector, contract levels), distinct from the Kind-tagged rows listCcnls returns above. Employment.ccnl / ccnlId point at this first-class type. Requires a Bearer token (apiKey). |
createCompensation | write | Bearer | compensations create | Add a pay item to an employment (attributes.employmentId, amount, currency, frequency, effectiveFrom). Returns the payload (with errors) or null. The new item is at compensation.node. |
createEmployee | write | Bearer | employees create | Create the HR record for a workspace user (attributes.userId). Returns the payload (with errors) or null. The new employee is at employee.node. |
createEmployment | write | Bearer | employments create | Hire: open a new employment (contract) for attributes.employeeId with its start date, kind, role and hours. Returns the payload (with errors) or null. The new employment is at employment.node. |
createTimeClock | write | Bearer | time-clocks create | Record a time-clock punch (beginsAt, optional finishesAt, resourceId). Returns the payload (with errors) or null. The new punch is at timeClock.node. |
deleteCompensation | write | Bearer | compensations delete | Delete a pay item by { input: { id } }. Returns the payload (with errors) or null. |
deleteEmployment | write | Bearer | employments delete | Delete an employment by { input: { id } } (to end a contract, use terminateEmployment instead). Returns the payload (with errors) or null. |
getEmployee | read | Bearer | — | One employee with current employment, today's time clocks and the shifts of the week starting weekStartingAt (see weekStartFor), or null. |
listCcnls | read | Bearer | ccnls list | List the CCNLs (national collective labor contracts) configured for the workspace — used for an employment's ccnlId. |
listCircles | read | Bearer | — | List the workspace circles. Circles come in two flavours distinguished by kind: org units (e.g. departments — used as an employment's circleId) and roles (kind === 'role' — used as an employment's roleId). Use listRoles when you only want the role-Circles. |
listEmployees | read | Bearer | employees list | The workspace's employees with their current employment, as a flat array; page with first/after. |
listEmploymentHistory | read | Bearer | — | List every employee with their full employment (contract) history — not just the current one. Use contractEvents to turn this into a flat, dated list of hires, terminations and contract variations. |
listKinds | read | Bearer | — | List Kind records for the given polymorphic models. |
listOffices | read | Bearer | offices list | List the workspace offices — used for an employment's employmentOfficesAttributes. |
listRoles | read | Bearer | roles list | List only the role-Circles (structured job titles) — an employment's roleId. |
terminateEmployment | write | Bearer | — | Terminate (cessazione) an employment by closing it with an end date and reason. Thin semantic wrapper over updateEmployment. endReason is one of: resignation, termination, contract_end, retirement, other, resignation_just_cause, dismissal_just_cause, dismissal_subjective_reason, dismissal_objective_reason, collective_dismissal, mutual_agreement, probation_failed, death, business_closure. |
updateCompensation | write | Bearer | compensations update | Update a pay item: { input: { id, attributes } }. Returns the payload (with errors) or null. |
updateEmployee | write | Bearer | employees update | Update an employee's HR record (code, kiosk PIN, time-clock flags): { input: { id, attributes } }. Returns the payload (with errors) or null. |
updateEmployment | write | Bearer | employments update | Update an employment: { input: { id, attributes } }; to end one, prefer terminateEmployment. Returns the payload (with errors) or null. |
weekStartFor | helper | — | — | Compute the Monday of the week containing date as an ISO8601 date string. Useful for the weekStartingAt argument required by getEmployee (and any other weekly-aggregated employee fields). |
Types: Circle, CompensationRecord, ContractEvent, ContractEventType, Employee, EmployeeContracts, EmploymentRecord, Kind, Office
CLI:
| Entity | list | get | create | update | delete |
|---|---|---|---|---|---|
employees | ✓ | — | ✓ | ✓ | — |
employments | — | — | ✓ | ✓ | ✓ |
offices | ✓ | — | — | — | — |
roles | ✓ | — | — | — | — |
ccnls | ✓ | — | ✓ | — | — |
time-clocks | — | — | ✓ | — | — |
compensations | — | — | ✓ | ✓ | ✓ |
benefits | — | — | ✓ | — | — |
benefit-enrollments | — | — | ✓ | — | — |