uidudevelopers
Domains

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: read employee.user?.name (also firstName, lastName, email). Likewise the contract type is currentEmployment.employmentKind.name (e.g. Tempo indeterminato) — there is no contractType field on the API. It used to be a free string; uidu promoted it to an EmploymentKind record so each workspace configures its own, which is why writes take employmentKindId. Only the records listEmploymentHistory builds carry a flat contractType (a copy of employmentKind.name) for display.
  • Contract events are employment writes. A hire (assunzione) is createEmployment; a variation (variazione) is a new createEmployment, or updateEmployment on the current one; a termination (cessazione) is updateEmployment with endDate + endReason — which is exactly what terminateEmployment sends. deleteEmployment erases the record; to end a contract, terminate it. endReason is one of resignation, termination, contract_end, retirement, other, or the finer-grained resignation_just_cause, dismissal_just_cause, dismissal_subjective_reason, dismissal_objective_reason, collective_dismissal, mutual_agreement, probation_failed, death and business_closure.
  • Units. fteRatio is a fraction (1.0 full-time, 0.5 half-time). Dates (startDate, endDate, effectiveFrom, enrolledAt, weekStartingAt) are ISO8601Date strings (YYYY-MM-DD); punches (beginsAt, finishesAt) are ISO8601DateTime. Compensation and benefit amounts are plain Floats in the given currency — not cents.
  • Schedules live on the employee, not the workspace. getEmployee returns todayTimeClocks and weeklyShifts (first 20) for the week starting weekStartingAt — a required Monday date that weekStartFor(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's aasmState is e.g. planned, confirmed, completed.
  • Reference data. listOffices gives the ids for employmentOfficesAttributes ([{ officeId, isPrimary }]). listCircles returns two flavours of Circle told apart by kind: org units (an employment's circleId) and roles (kind === 'role', an employment's roleId) — listRoles returns only the roles. listKinds(client, { models }) reads the polymorphic Kind records; listCcnls is listKinds with models: ['Ccnl']. There is no list function for employment kinds: take an employmentKind.id from an existing employment.
  • Two kinds of CCNL record. listCcnls returns Kind-tagged rows (id, name, code, model). createCcnl creates the first-class Ccnl record (code, sector, contract levels via ccnlLevelsAttributes), and that is the type Employment.ccnl / ccnlId point at. When you create a CCNL, use the id it returns (ccnl.node.id) as ccnlId.
  • 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 weeklyShifts per employee. Time entries, approving time off, and the weeklyTimeOffs / monthlyTimeOffs fields exist in the schema but aren't wrapped; reach them through executeAction where 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; paginate with ListEmployeesDocument walks them all — see Pagination & field values.
  • The list carries only a summary of the current employment (jobTitle, employmentKind, isActive, startDate); contractLevel, classification, fteRatio, endDate and primaryOffice come from getEmployee.

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 createEmployment from the change date — which contractEvents will report as a variation — or updateEmployment ({ input: { id, attributes } }) on the current contract when you don't need the history.
  • createBenefit adds to the workspace's benefit catalog (name, category, provider, employerCostAmount, employeeCostAmount, …); its id is at benefit.node.id.
  • Punches can be written too: createTimeClock(uiduAdmin, { input: { attributes: { resourceId, beginsAt, finishesAt } } }), the new punch at timeClock.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})`);
  • listEmploymentHistory returns every employee with all their contracts (up to employmentsFirst, default 50), not just the current one. Each record is flattened: { id, name, employments: [{ id, startDate, endDate, endReason, jobTitle, employmentKind, contractType, ccnl, compensations }] }, with name falling back to the user's email, then 'Senza nome'. Compensations (first 20) come most recent effectiveFrom first.
  • contractEvents rules: per employee, contracts are sorted by startDate; the earliest is a hire, every later one a variation, both on their start date; any contract with an endDate also yields a termination on it, carrying endReason. Contracts without a startDate are ignored. Each event has date (YYYY-MM-DD) and month (YYYY-MM) for grouping.
  • first/after page the employees, but the flattened result carries no cursor: pass a first that 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.

FunctionKindAuthCLIDescription
contractEventshelper——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.
createBenefitwriteBearerbenefits createCreate 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.
createBenefitEnrollmentwriteBearerbenefit-enrollments createEnroll an employment in a benefit (benefitId, employmentId, enrolledAt). Returns the payload (with errors) or null; it is at benefitEnrollment.node.
createCcnlwriteBearerccnls createCreate 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).
createCompensationwriteBearercompensations createAdd 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.
createEmployeewriteBeareremployees createCreate the HR record for a workspace user (attributes.userId). Returns the payload (with errors) or null. The new employee is at employee.node.
createEmploymentwriteBeareremployments createHire: 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.
createTimeClockwriteBearertime-clocks createRecord a time-clock punch (beginsAt, optional finishesAt, resourceId). Returns the payload (with errors) or null. The new punch is at timeClock.node.
deleteCompensationwriteBearercompensations deleteDelete a pay item by { input: { id } }. Returns the payload (with errors) or null.
deleteEmploymentwriteBeareremployments deleteDelete an employment by { input: { id } } (to end a contract, use terminateEmployment instead). Returns the payload (with errors) or null.
getEmployeereadBearer—One employee with current employment, today's time clocks and the shifts of the week starting weekStartingAt (see weekStartFor), or null.
listCcnlsreadBearerccnls listList the CCNLs (national collective labor contracts) configured for the workspace — used for an employment's ccnlId.
listCirclesreadBearer—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.
listEmployeesreadBeareremployees listThe workspace's employees with their current employment, as a flat array; page with first/after.
listEmploymentHistoryreadBearer—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.
listKindsreadBearer—List Kind records for the given polymorphic models.
listOfficesreadBeareroffices listList the workspace offices — used for an employment's employmentOfficesAttributes.
listRolesreadBearerroles listList only the role-Circles (structured job titles) — an employment's roleId.
terminateEmploymentwriteBearer—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.
updateCompensationwriteBearercompensations updateUpdate a pay item: { input: { id, attributes } }. Returns the payload (with errors) or null.
updateEmployeewriteBeareremployees updateUpdate an employee's HR record (code, kiosk PIN, time-clock flags): { input: { id, attributes } }. Returns the payload (with errors) or null.
updateEmploymentwriteBeareremployments updateUpdate an employment: { input: { id, attributes } }; to end one, prefer terminateEmployment. Returns the payload (with errors) or null.
weekStartForhelper——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:

Entitylistgetcreateupdatedelete
employees✓—✓✓—
employments——✓✓✓
offices✓————
roles✓————
ccnls✓—✓——
time-clocks——✓——
compensations——✓✓✓
benefits——✓——
benefit-enrollments——✓——

On this page