type Query { me: User! """ Returns employees in the caller's permitted scope. All supplied filter fields are ANDed together; values within a filter field are ORed. Office and team values are Shapes IDs. Empty arrays do not restrict results; a non-empty array containing no usable values returns no matches. If a requested employee field is unavailable or not visible to the caller, the query returns no matches. """ employees(ids: [ID], filters: EmployeeFilters): [Employee]! """Returns every active office configured in the calling account.""" offices: [Office!]! """Returns every active team configured in the calling account.""" teams: [Team!]! employeeFieldValues(ids: [ID], filters: EmployeeFieldValueFilters): [EmployeeFieldValue]! employeeFieldTypes(ids: [ID!], filters: EmployeeFieldTypeFilters): [EmployeeFieldType!]! timeAwayReasons(ids: [ID]): [TimeAwayReason]! timeAwayBookings(ids: [ID], filters: TimeAwayBookingFilters): [TimeAwayBooking]! employeeAnniversaries(filters: EmployeeAnniversaryFilters): [EmployeeAnniversary]! """ Read attendance (clock-in / clock-out) records. Results are scoped to the caller's permitted employee set — records for employees the caller is not permitted to manage are silently excluded. All filters are optional and ANDed together; omitting all filters returns all permitted records. """ attendances(ids: [ID], filters: AttendanceFilters): [Attendance]! """ List workflows visible to the authenticated user. Pass `filters: { isTemplate: true }` to list the templates available to assign — that is where a `workflowTemplateId` for `createWorkflowEmployeeAssignments` comes from. Workflows belonging to an approval-flow cycle are internal plumbing and are never returned. """ workflows(ids: [ID], filters: WorkflowFilters): [Workflow]! """ List workflow assignments (runs) visible to the authenticated user. Use this to follow up on a run started with `createWorkflowEmployeeAssignments`. Note `filters.workflowId` matches the *run* — the copy created by the assignment — not the template it came from. To tell which template a run came from, read `workflow { workflowTemplateId }` off the result. """ workflowEmployeeAssignments(ids: [ID], filters: WorkflowEmployeeAssignmentFilters): [WorkflowEmployeeAssignment]! } type Mutation { """ The request should contain a *Refresh-Token* header with the old refresh token. """ refreshToken: AuthenticationResponse! """ Requires a Super Admin or Admin (with the *Add employees* permission) permission levels. """ createEmployees(arrayOfValues: [EmployeeCreate]!): [Employee]! """ Update existing employees. Partial update — only the supplied *customFields* are written; everything else is left untouched. Field values target EXISTING fields only (set fields up in *Settings → Employee Fields*); an unknown *employeeFieldTypeId* is rejected. Effective-dated *status* sections (Compensation, Role: job / team / office / reports to) ARE supported — pass *effectiveDate* on the field to write its history row for that date (a new date adds a row; an existing date updates it). *effectiveDate* is required for these fields and must be omitted for ordinary fields. *Table* sections (Bonuses, Equity) hold many rows per date and are NOT supported here — targeting one is rejected. Requires a Super Admin, or an Admin with *edit* permission on every targeted field type (and access to the targeted employees). """ updateEmployees(arrayOfValues: [EmployeeUpdate!]!): [Employee]! """Allowed for Super Admins.""" terminateEmployees(arrayOfValues: [EmployeeTerminate]!): [Employee]! deleteTimeAwayBookingAttachments(timeAwayBookingIds: [ID]!): [ProtectedAsset]! """ Create or update complete attendance records. Use this when you have full attendance data (employee, day, clock-in and/or clock-out times). Behavior: - If an 'id' is provided, the existing attendance record is updated with the given fields. Times are validated against the existing record's 'day'. - If no 'id' is provided, a new attendance record is created. 'employeeId' and 'day' are required for creation. - clockInAt and clockOutAt must fall on the same calendar day (in UTC) as 'day'. - Overlapping attendance records for the same employee and day will cause an error. - All dates should be provided in ISO 8601 format with timezone (e.g., "2025-06-10T09:00:00+03:00"). For discrete clock events from time-tracking devices, use submitAttendanceEvents instead. """ createOrUpdateAttendances(arrayOfValues: [AttendanceCreateOrUpdate]!): [Attendance]! """ Submit individual clock-in/out events that are automatically paired and merged with existing attendance records. Use this when you have discrete clock events (e.g., from a time clock device) rather than complete attendance records. For complete attendance records with known day/id, use createOrUpdateAttendances instead. Behavior: - A clock-out event is merged into the latest open attendance (has clockInAt, no clockOutAt) for the same employee and day. - A clock-in event creates a new attendance record, or fills in a record missing clockInAt. - Multiple events in the same request for the same employee+day are paired before merging with existing records. """ submitAttendanceEvents(events: [AttendanceEvent]!): [Attendance]! """ Send a message to the Shapes AI assistant and receive its response. Returns a list of two messages: the persisted `user` message followed by the `assistant`'s response. Both share the same `conversationId`. Behavior: - If `conversationId` is omitted, a new conversation is created and its title is auto-generated from the first message. - If `conversationId` is provided, the message is appended to that conversation. Only conversations owned by the calling user are accepted. - The agent call is bounded by an extended socket timeout (350s) — long reasoning chains will not be cut off at the default 120s ceiling. """ createChatMessage( """ The user's message text. Persisted as the fallback text on the resulting `user` message and forwarded to the agent. """ message: String! """ Client-supplied timestamp for the `user` message. ISO 8601 with timezone. """ timestamp: Date! """ Existing conversation to append to. Omit (or pass null) to start a new conversation. """ conversationId: ID ): [ChatMessage]! """ Assign workflow templates to employees — this is how you trigger a workflow run. For each entry, Shapes copies the template identified by `workflowTemplateId` into a new workflow, links it to `employeeId`, resolves the run's anchor dates and dynamic values against that employee, and then starts the workflow's automations. Behavior: - `workflowTemplateId` must be a workflow with `isTemplate: true`. Find one with the `workflows(filters: { isTemplate: true })` query. - One entry produces one run. Assigning the same template to five employees means five entries, and returns five assignments. - Requires permission to manage workflows for every employee in the request; the whole request is rejected if any one of them is out of scope. - At most 200 entries per call. A larger batch is rejected outright, before anything is created — split it and send the parts. **This mutation is not idempotent — do not retry it blindly.** Assignments are committed before their automations start, so an error can be returned after the runs already exist. Retrying then starts a *second* set of runs rather than resuming the first. If a call fails, query `workflowEmployeeAssignments(filters: { employeeId: ... })` to see what was created and retry only the entries that are missing. Returns the created assignments, in request order. """ createWorkflowEmployeeAssignments(arrayOfValues: [WorkflowEmployeeAssignmentCreate!]!): [WorkflowEmployeeAssignment]! } scalar Date scalar JSON input DateInterval { start: Date end: Date } type AuthenticationResponse { accessToken: String! refreshToken: String! } type User { id: ID! email: String! } type Employee { id: ID! firstName: String! lastName: String! email: String! """Requires Super Admin or Admin permission level to view.""" workStatus: String employeeFieldValues: [EmployeeFieldValue]! } """ An active office configured in the calling account. Use its id with EmployeeFilters.office. """ type Office { id: ID! name: String! } """ An active team configured in the calling account. Use its id with EmployeeFilters.team. """ type Team { id: ID! name: String! } input EmployeeFilters { email: [String] office: [ID] team: [ID] } input EmployeeCreate { firstName: String! lastName: String! email: String! startDate: Date """ A mapping of extra fields to be added to the employee. The key is the context and the value is the field value. Example: { "office": ["office_id"], "team": ["team_id"], "job": ["job_id"], ... } """ extraFields: JSON """ If set to true, provide the *permissionRoleId* to assign to the employee. Requires Super Admin or Admin (with the *Invite* permission) permission levels. """ shouldInvite: Boolean """Must be provided if *shouldInvite* is true.""" permissionRoleId: ID } """ A single employee update: identify the employee by *id* (the Shapes internal id), then supply the *customFields* to write. """ input EmployeeUpdate { """Shapes internal employee id, as returned by the *employees* query.""" id: ID! """ Field values to write, addressed by *employeeFieldTypeId* (from the *employeeFieldTypes* query) — standard fields (first name, start date, …) and custom fields alike. """ customFields: [EmployeeCustomFieldUpdate!]! } input EmployeeCustomFieldUpdate { """ The EXISTING field type to write, from the *employeeFieldTypes* query. Account-scoped; an unknown id is rejected (this API never creates fields). """ employeeFieldTypeId: ID! """ The value to write: a JSON object with a single *value* key. Use a scalar for text / number / date / phone / link fields, or an array of Shapes ids for entity-reference (job / office / team), people / manager, and select fields — pass ids, not names. Set *value* to null (or to an empty array for list fields) to clear the field. """ fieldValue: JSON! """ The effective date of this value, for effective-dated *status* sections (Compensation, Role). REQUIRED for those fields and forbidden for ordinary fields. A new date adds a history row; an existing date updates that date's row. """ effectiveDate: Date } input EmployeeTerminate { id: ID! terminationDate: Date! """ IDs of the reasons for termination. These IDs are defined in the *termination_reason* employeeFieldType *valueOptions* """ terminationReason: [ID!] } """ A single value of an employee field — the per-employee row that pairs an `EmployeeFieldType` (the schema/column) with the actual value for that employee. One employee usually has many `EmployeeFieldValue` rows, one per field defined in the account. """ type EmployeeFieldValue { id: ID! employeeId: ID! """ The actual value. Shape depends on the parent field's `employeeFieldType.fieldType`: - Primitive kinds (`text`, `long_text`, `link`, `number`, `date`, `phone`, `checkbox`, `rating`, `employee_email`, `start_date`, `termination_date`, `workflow_progress`) — the raw value (string / number / boolean / ISO date string). - Select-style kinds (`single_select`, `multi_select` and their derived subtypes `city`, `state`, `currency`, `termination_reason`, `first_day_of_working_week`) — the `EmployeeFieldValueOption.id`. - Structured-JSON kinds — `country` stores `{ countryCode, countryName }` directly. - Entity-reference kinds (`team`, `job`, `office`, `people`, `reports_to`, `file`, `profile_picture`) — the FK ID of the referenced row. Branch on `employeeFieldType.fieldType` (typed enum) before reading. """ fieldValue: JSON! """ Pre-flattened text rendering of `fieldValue` for search and display (e.g. for an entity reference, the referenced row's display name). Server-computed; null when no flattening exists for the kind. """ textValue: String employeeFieldTypeId: ID! employeeFieldType: EmployeeFieldType! """When the field value was last updated. Expected to be in UTC timezone.""" updatedAt: Date! """No effective date means the field value is effective immediately.""" effectiveDate: Date } input EmployeeFieldValueFilters { fieldTypeContext: [EmployeeFieldTypeContext!] employeeFieldTypeId: [ID!] employeeId: [ID!] updatedAt: DateInterval } """ Schema metadata for a single employee field — the column-like definition, not a value. Each `EmployeeFieldType` describes one slot on the employee record (built-in like first name / start date, or custom like a tenure rating). Pair with `EmployeeFieldValue` to read the actual per-employee values. """ type EmployeeFieldType { id: ID! """ Storage and rendering kind for this field. Use this rather than pattern-matching on `fieldName` — `fieldName` is account- customisable while `fieldType` is the canonical server-side discriminator. """ fieldType: EmployeeFieldTypeKind! """ Where this field surfaces in the product (e.g. profile, capacity, onboarding). Independent of `fieldType`: two fields with the same `fieldType` can live in different contexts. """ context: EmployeeFieldTypeContext! """ Per-kind configuration blob. Shape varies by `fieldType`: `single_select` / `multi_select` and their derived subtypes hold `{ valueOptions: [{ id, value }, ...] }`; `number` / `currency` may hold validation hints; many primitive kinds leave this null. Treat unknown keys as ignorable. """ data: JSON """ Human-readable label rendered in the profile UI and on the value column (e.g. "First name", "Tenure rating"). Account-customisable — use `fieldType` (typed) for branching logic; use `fieldName` for rendering and search. """ fieldName: String! } enum EmployeeFieldTypeContext { address city contract_documents contract_type country custom date_of_birth department email emergency_contact_full_name emergency_contact_phone emergency_contact_relationship employee_capacity employee_id employee_phone employee_profile_picture first_name gender job last_name level national_id nationality ni_number office passport_number personal_email reports_to salaryType salary_amount salary_amount_currency ssn start_date state_province team termination_date termination_reason unique_identifier zip_code } """ Storage / rendering kind of an employee field type. Four families: direct primitives stored on `EmployeeFieldValue.value` (`text`, `long_text`, `link`, `number`, `date`, `phone`, `checkbox`, `rating`, `employee_email`, `start_date`, `termination_date`, `workflow_progress`); select-style values referencing the option set in `EmployeeFieldType.valueOptions` (`single_select`, `multi_select`, and their derived subtypes `city`, `state`, `currency`, `termination_reason`, `first_day_of_working_week`); structured-JSON kinds with a fixed shape (`country` stores `{ countryCode, countryName }`); and entity references to a separate table (`team`, `job`, `office`, `people`, `reports_to`, `file`, `profile_picture`). Closed set — clients should treat unrecognised values as fail-closed. """ enum EmployeeFieldTypeKind { checkbox city country currency date employee_email file first_day_of_working_week job link long_text multi_select number office people phone profile_picture rating reports_to single_select start_date state team termination_date termination_reason text workflow_progress } input EmployeeFieldTypeFilters { fieldType: [EmployeeFieldTypeKind!] context: [EmployeeFieldTypeContext!] } """ A category of leave (vacation, sick, holiday, etc.) that employees can book against via `TimeAwayBooking`. Each reason carries one or more `TimeAwayReasonPolicy` variants that define balance / approval rules for different employee populations. """ type TimeAwayReason { id: ID! """ Lineage of this reason. Use this rather than pattern-matching on `name` (`name` is account-customisable, `type` is canonical). """ type: TimeAwayReasonType! """ Display name (e.g. "Vacation", "Sick leave"). Account-customisable — use this for human rendering; use `type` when you need to branch on lineage. Not unique across reasons in the same account. """ name: String! """ The default policy applied when an employee not assigned to a specific policy books against this reason. Null when the reason has no default policy (employees must be explicitly assigned to book at all). """ defaultTimeAwayReasonPolicyId: ID """Display order in the booking-reason picker. Lower is rendered first.""" position: Int status: Int! """ True when individual bookings of this reason are visible only to the booker, their manager, and HR — used for medical / sensitive leave. False (the default) means bookings of this reason are visible to teammates for coverage planning. """ isPrivate: Boolean! } """ Lineage of a time-away reason — drives policy semantics, balance accounting, and UI grouping. `time_off` is the standard paid/unpaid leave bucket (vacation, personal, etc.); `working_away` covers business travel and remote-from-elsewhere days; `holiday` is the public / observed-holiday calendar; `non_working_day` marks days the employee is contractually not expected to work; `extended_leave` is for long-form absences (parental, sabbatical, sick leave) that need their own balance + policy; `special_leave` is account-defined leave types that don't fit the other lineages. """ enum TimeAwayReasonType { extended_leave holiday non_working_day special_leave time_off working_away } """ A single time-away request — vacation, sick leave, or any other absence. Created by an employee (or by an admin/manager on their behalf), then transitioned through an approval lifecycle via `bookingStatus`. The date range is exclusive of unrelated weekends/holidays per the underlying `TimeAwayReasonPolicy` — see `TimeAwayBooking.timeAwayReason`. """ type TimeAwayBooking { id: ID! """ The employee taking the time off (the absentee). Distinct from `bookedByEmployeeId`, which is whoever filed the request — the two are usually the same but can differ when an admin or manager books on behalf of someone else. """ employeeId: ID! """FK to the underlying `TimeAwayReason` (vacation, sick, etc.).""" timeAwayReasonId: ID! """ Start of the absence as a JSON object: `{ date: "YYYY-MM-DD", halfDayPart: "morning" | "afternoon" | null }`. The `halfDayPart` key is null for full-day starts; populated only when the booking's underlying reason policy enables half-day tracking. Always paired with `toDate` — the full absence is `[fromDate, toDate]` inclusive. """ fromDate: JSON! """ End of the absence as a JSON object: `{ date: "YYYY-MM-DD", halfDayPart: "morning" | "afternoon" | null }`. Same shape as `fromDate`. `toDate.date` is always >= `fromDate.date`; for a single-day booking the two share the same `date`. """ toDate: JSON! """Approval state of the booking.""" bookingStatus: TimeAwayBookingStatus! """ The employee who filed the request (admin, manager, or the absentee themselves). Used for audit + permissioning; pair with `employeeId` when you need to disambiguate "self-booked vs booked-on-behalf". """ bookedByEmployeeId: ID! """ Free-text reason / note attached by the booking author. Stored as a rich-text JSON document (Slate-style), nullable. """ explanation: JSON status: Int! """ Whoever last transitioned `bookingStatus` (typically the approver who flipped `pending` → `approved` / `denied`, or the booking owner on cancel). Null until the first status transition after creation. """ bookingStatusUpdatedByEmployeeId: ID """ Timestamp of the last `bookingStatus` transition. Null until the first transition after creation. Pair with `bookingStatusUpdatedByEmployeeId` for an audit trail of who approved / denied / canceled the booking and when. """ bookingStatusUpdatedAt: Date """ Files (signed sick notes, permits, travel docs) uploaded by the booking author. Empty list when nothing was attached. """ attachments: [ProtectedAsset]! } """ Approval state of a time-away booking. Lifecycle: `pending` → (`approved` | `denied` | `canceled`). `canceled` is the owner-initiated retraction; `denied` is the approver-initiated rejection. Note the canonical wire spelling is single-l `canceled` — clients passing `cancelled` will be rejected by this enum. """ enum TimeAwayBookingStatus { approved canceled denied pending } input TimeAwayBookingFilters { timeAwayReasonId: [ID!] employeeId: [ID!] fromDate: DateInterval toDate: DateInterval } type ProtectedAsset { id: ID! targetId: ID! targetType: String! status: Int! name: String! protectedAssetType: String! } type EmployeeAnniversary { employeeId: ID! """ The type of the anniversary. This can be any of the following examples: - birthday - work """ type: String! """The month of the anniversary. Zero-indexed.""" month: Int! """The day of the anniversary.""" day: Int! """The date from which the anniversary is valid.""" validFrom: Date! employee: Employee! } input EmployeeAnniversaryFilters { employeeIds: [ID!] } type Attendance { id: ID! accountId: ID! employeeId: ID! """The day of the attendance.""" day: Date! clockInAt: Date clockOutAt: Date """ Whether this record was written from outside the allowed clocking IPs of the employee it belongs to (configured on their time-tracking policy). `true` only ever means a manual timesheet entry made off-network — clocking is refused outright from a disallowed IP. `false` means the write came from an allowed IP, or the employee's policy sets no restriction. `null` means unknown: the record predates the field, or was written through a path that carries no client IP — **including this API**, whose writes are attributed to an integration token rather than a person at a location. Read-only, and `null` is not `false`: treat only `true` as "entered off-network". """ isEnteredOutsideAllowedIps: Boolean status: Int! } """ Filters for the `attendances` query. All fields are optional and ANDed together. """ input AttendanceFilters { """ Restrict results to the given employee IDs. Pass multiple values to fetch records for a team in one round-trip. Callers are still scoped to their permitted employee set — IDs outside that set are silently excluded. """ employeeId: [ID] """ `DateInterval` range filter on `clockInAt`. Matches records whose `clockInAt` falls within `[start, end]` (inclusive). Records with `clockInAt = null` are never matched by this filter. """ clockInAt: DateInterval """ `DateInterval` range filter on the `day` field. Use this (rather than `clockInAt`) when you want all attendance rows for a calendar span regardless of whether the clock-in time falls within the range. """ day: DateInterval } input AttendanceEvent { employeeId: ID! """ ClockInAt must be before ClockOutAt. At least one of clockInAt or clockOutAt must be provided. """ clockInAt: Date """ ClockOutAt must be after ClockInAt. At least one of clockInAt or clockOutAt must be provided. """ clockOutAt: Date } input AttendanceCreateOrUpdate { id: ID employeeId: ID """ The day of the attendance. clockInAt and clockOutAt must fall on the same calendar day (in UTC) as this value. """ day: Date """ClockInAt must be before ClockOutAt.""" clockInAt: Date """ClockOutAt must be after ClockInAt.""" clockOutAt: Date } """ A single message exchanged with the Shapes AI assistant. Each `createChatMessage` call produces two messages: the persisted user message followed by the assistant's response. """ type ChatMessage { """Unique identifier of the message.""" id: ID! """ ID of the user the message belongs to. The same user owns both the `user` message and the `assistant` response in a turn. """ userId: ID! """ Author of the message — `user` for caller-sent messages, `assistant` for the AI response. """ role: String! """ When the message was sent. For `user` messages this is the client-supplied timestamp; for `assistant` messages it is set when the response is persisted. """ timestamp: Date! """ Optional feedback flag set by the caller on an `assistant` message (thumbs-up / thumbs-down equivalent). Always null for `user` messages. """ isHelpful: Boolean """ ID of the conversation this message belongs to. Multiple messages in the same conversation share this ID. """ conversationId: ID """ Structured content of the message, ordered by sequence. Always non-empty for messages persisted by `createChatMessage`. Each block has a typed payload (e.g. text) — see `ChatMessageBlockType` for the variants. """ blocks: [ChatMessageBlock!]! } """ A single structured block within a `ChatMessage`. A message is composed of one or more blocks rendered in sequence; each block carries a typed `payload` that varies by `type`. """ type ChatMessageBlock { """ Discriminator selecting the concrete payload variant. Match this against the `__typename` on `payload` when handling each variant. """ type: ChatMessageBlockType! """ Variant payload for this block. Use a GraphQL inline fragment per variant (e.g. `... on ChatMessageTextBlockPayload { text }`) to read its fields. """ payload: ChatMessageBlockPayload! } """ Discriminator for `ChatMessageBlock` payload variants. Generated from the block-services registry — adding a new block type extends this enum automatically. """ enum ChatMessageBlockType { TEXT ARTIFACT_REF REASONING TOOL_CALL NOTIFICATION_DRAFT } """ Variant payload of a `ChatMessageBlock`. The concrete type aligns with the block's `type` discriminator (e.g. `TEXT` → `ChatMessageTextBlockPayload`). """ union ChatMessageBlockPayload = ChatMessageTextBlockPayload | ChatMessageArtifactRefBlockPayload | ChatMessageReasoningBlockPayload | ChatMessageToolCallBlockPayload | ChatMessageNotificationDraftBlockPayload type ChatMessageTextBlockPayload { text: String! } type ChatMessageArtifactRefBlockPayload { artifactId: ID! """ Hydrated artifact referenced by this block. Resolved via `artifactsAPI.loadArtifact({ id: payload.artifactId })`. Null when the artifact has been soft-deleted or is unreachable to the viewer. """ artifact: Artifact } type ChatMessageReasoningBlockPayload { text: String! } type ChatMessageToolCallBlockPayload { toolCallId: String! toolName: String! input: JSON output: JSON outcome: String! errorText: String } type ChatMessageNotificationDraftRecipient { employeeId: ID! name: String email: String resolvedChannel: String! fallback: Boolean! } type ChatMessageNotificationDraftBlockPayload { channel: String! emailType: String subject: String body: String! fromDisplay: String recipients: [ChatMessageNotificationDraftRecipient!]! } """ A typed payload produced by the assistant — e.g. a downloadable table — that is rendered alongside its surrounding chat message. Artifacts persist independently of the message that emitted them so the same artifact can be referenced from multiple turns or surfaces. """ type Artifact { """ Stable identifier — the same id resolves to the same artifact across requests. """ id: ID! """ Soft-delete flag (1 = active, 2 = deleted). """ status: Int! """ Discriminator selecting the artifact variant; matches the variant of `config`. """ type: ArtifactType! """ Human-readable label rendered in the artifact chrome. Nullable when the artifact has no title. """ title: String """ Per-variant render configuration. Selects `TableConfig` for `TABLE`, etc. """ config: ArtifactConfig! """ Underlying data payload reachable via a time-limited URL. Null while the asset is still being uploaded (transiently null inside the in-flight materialisation transaction; never visible to other readers). """ protectedAsset: ProtectedAsset """ Creation timestamp. """ createdAt: Date! } """ Discriminator for `Artifact` variants. Generated from `artifactTypes` — adding a new variant extends this enum and requires a sibling entry in `ArtifactServices` so `Artifact.config` resolves to a concrete shape. """ enum ArtifactType { TABLE CHART DECK } """ Variant render-config of an `Artifact`. The concrete type aligns with the artifact's `type` discriminator (e.g. `TABLE` → `TableConfig`). Use a GraphQL inline fragment per variant (e.g. `... on TableConfig { columns { key label } }`) to read its fields. """ union ArtifactConfig = TableConfig | ChartConfig | DeckConfig """ Column descriptor for a `TableConfig`. Carries the render-time ORDER and LABEL for one column — `key` selects a column from the underlying CSV header; `label` overrides the rendered header text. """ type TableColumn { """ Header key from the source CSV. Required — used to project + order CSV columns. """ key: String! """ Human-readable header rendered above the column. """ label: String! """ Hint for downstream consumers; the renderer does not coerce cells. Format conversion stays a server concern — if a column needs "Yes/No" instead of "true/false", the CSV should already carry it that way. """ type: String """ Alignment hint for downstream consumers; the renderer left-aligns every column today. Kept for forward-compat with custom per-column alignment. """ align: String } """ Optional summary stats persisted with the artifact so clients can render loading shimmers before the full CSV is fetched. Backward-compatible: artifacts persisted before this field shipped still validate; renderers fall back to a single status-message line when `stats` is absent. """ type TableStats { """ Total row count in the materialised CSV. """ rowCount: Int! } """ `ArtifactConfig` variant when `artifact.type === TABLE`. Columns define the render order and header labels; the CSV is reachable via the artifact's `protectedAsset` (the public surface intentionally does not expose a presigned URL field — fetch through the standard asset-download endpoint). """ type TableConfig { """ Column descriptors in render order. """ columns: [TableColumn!]! """ Optional metadata describing the underlying data. """ stats: TableStats } """ Variant discriminator within a `ChartConfig`. Selects the per-variant renderer on the client; ALSO drives the variant ↔ required-field check on the server. """ enum ChartVariant { LINE BAR PIE } """ Per-series descriptor for `LINE` and `BAR` chart variants. Empty for `PIE` (which uses `categoryKey` + `valueKey` instead). """ type ChartSeries { """ CSV column key for this series — must match a header in the materialised CSV. """ key: String! """ Human-readable label rendered in legend / tooltip. """ label: String! } """ Optional summary stats persisted with the artifact so clients can render loading shimmers before the full CSV is fetched. Populated server-side by `ChartArtifactService.buildAsset`; the agent never emits stats. """ type ChartStats { """ Total row count in the materialised CSV. """ rowCount: Int! } """ `ArtifactConfig` variant when `artifact.type === CHART`. Loose at the field level (variant-specific fields are nullable in the schema), strict at the server boundary — `ChartArtifactService.normaliseConfig` enforces the variant ↔ required-field matrix. """ type ChartConfig { """ Variant discriminator; drives the per-variant renderer. """ variant: ChartVariant! """ CSV column key for the x-axis. Required for `LINE` / `BAR`; null for `PIE`. """ xKey: String """ CSV column key for slice category. Required for `PIE`; null for `LINE` / `BAR`. """ categoryKey: String """ CSV column key for slice value. Required for `PIE`; null for `LINE` / `BAR`. """ valueKey: String """ Series descriptors in render order. Non-empty for `LINE` / `BAR`; empty for `PIE`. """ series: [ChartSeries!]! """ `BAR` only — stacked vs grouped bars. Null for other variants. """ stacked: Boolean """ `LINE` only — smoothed curve vs straight segments. Null for other variants. """ smooth: Boolean """ Optional metadata describing the underlying data. """ stats: ChartStats } """ Discriminator for the kind of content a `DeckBlock` carries. UPPER_SNAKE_CASE to match the persisted JSONB value and clay's `deckBlockTypes` — the discriminator is identical end-to-end (agent → io-server → clay) with no case mapping at any boundary, mirroring the `ChartVariant` precedent. """ enum DeckBlockType { TEXT CHART TABLE } """ Discriminator for the structural role of a `DeckSlide`. UPPER_SNAKE_CASE to match the persisted JSONB value and clay's renderer — identical end-to-end with no case mapping, mirroring the `DeckBlockType` precedent. When absent from a persisted slide, `DeckArtifactService.normaliseConfig` defaults to `CONTENT`. """ enum DeckSlideKind { """ Opening slide — rendered with title, subtitle, description, and date from `DeckMeta`. """ TITLE """ Agenda slide — renders `items` as a numbered list. """ AGENDA """ Divider / section-break slide — full indigo background, title reversed to white. """ SECTION """ Default content slide — title band + ordered `content` blocks. """ CONTENT """ Closing slide — renders "Thank you / Questions?" sign-off. """ CLOSING } """ A single content block on a `DeckSlide`. A "fat" type discriminated by `type` (NOT a GraphQL union) — loose at the field level (every variant field is nullable), strict at the server boundary, where `DeckArtifactService.normaliseConfig` enforces which fields each `type` carries. `chartConfig` / `tableConfig` reuse the standalone `ChartConfig` / `TableConfig` types; a deck carries the block's data inline in `rows`. """ type DeckBlock { """ Discriminator selecting which of the fields below are populated. """ type: DeckBlockType! """ `TEXT` only — markdown source rendered as the block body. """ markdown: String """ `CHART` only — chart render config, reusing the standalone `ChartConfig`. """ chartConfig: ChartConfig """ `TABLE` only — table render config, reusing the standalone `TableConfig`. """ tableConfig: TableConfig """ `CHART` / `TABLE` only — inline data rows the block plots/renders. """ rows: [JSON!] } """ A single slide within a `DeckConfig`. Carries a title and an ordered list of content blocks (text / chart / table) rendered top-to-bottom. The `kind` discriminator drives themed layout; additional fields (`subtitle`, `description`, `items`) are used by specific kinds only and are nullable for backward-compat with slides persisted before this field shipped. """ type DeckSlide { """ Short heading displayed at the top of the slide. """ title: String! """ Structural role of this slide. Defaults to `CONTENT` when absent from persisted JSONB. Clay and the PPTX exporter both branch on this value. """ kind: DeckSlideKind """ `TITLE` / `SECTION` — secondary heading rendered below `title`. """ subtitle: String """ `TITLE` — body copy rendered below `subtitle` on the opening slide. Not used by other kinds. """ description: String """ `AGENDA` only — ordered agenda entries rendered as a numbered list. """ items: [String!] """ Ordered content blocks rendered top-to-bottom. May be empty. """ content: [DeckBlock!]! } """ Optional summary stats persisted with the deck artifact. Populated server-side by `DeckArtifactService.normaliseConfig`. """ type DeckStats { """ Total number of slides in the deck. """ slideCount: Int! } """ Deck-level metadata carried in `DeckConfig.meta`. All fields are optional — the agent may emit any subset; missing fields are silently dropped. `logoUrl` is intentionally absent: logo injection is client-side only (the server never owns account branding assets at the config level). """ type DeckMeta { """ Short label rendered above the title on the opening slide (e.g. "Q3 2025 Review"). """ eyebrow: String """ ISO date string or human-readable date rendered on the title slide. """ date: String """ Theme token forwarded to clay's renderer (e.g. "default", "indigo"). """ theme: String } """ `ArtifactConfig` variant when `artifact.type === DECK`. A deck is a slide presentation whose slides — and their content blocks' data — live entirely inline in this config. Unlike TABLE and CHART there is no underlying CSV protected-asset for *rendering* — but a materialised deck mints a downloadable `.pptx` export as its DOWNLOAD asset, so `artifact.protectedAsset` is non-null for DECK artifacts (fetch it via the standard asset-download endpoint). """ type DeckConfig { """ Ordered list of slides. At least one slide is always present. """ slides: [DeckSlide!]! """ Optional metadata describing the deck. """ stats: DeckStats """ Optional deck-level metadata (eyebrow, date, theme) used by themed renderers. """ meta: DeckMeta } """ A workflow. A workflow with `isTemplate: true` is a reusable template — the thing you assign to an employee to start a run. A workflow with `isTemplate: false` is a single run: the copy that was made from a template when it was assigned. """ type Workflow { """ Unique identifier of the workflow. For a template, this is the id you pass as `workflowTemplateId` to `createWorkflowEmployeeAssignments`. """ id: ID! """Display name of the workflow.""" name: String! """ `true` for a reusable template, `false` for a single run created from one. """ isTemplate: Boolean! """ The workflow type (Onboarding, Offboarding, …) this workflow belongs to, if any. """ workflowTypeId: ID """ For a run, the id of the template it was created from. `null` on a template itself. """ workflowTemplateId: ID } input WorkflowFilters { isTemplate: Boolean workflowTypeId: [ID] } """ The assignment of a workflow to an employee — one workflow run. Created by `createWorkflowEmployeeAssignments`, which copies a template into a fresh run and starts it. """ type WorkflowEmployeeAssignment { """Unique identifier of the assignment.""" id: ID! """When the assignment was created — i.e. when the run started.""" createdAt: Date! """The employee this workflow was assigned to.""" employeeId: ID! """ The workflow run created for this assignment. This is the *copy* of the template, not the template itself. """ workflowId: ID! """ The workflow run created for this assignment. Nullable on purpose: the run is resolved through the same permission-scoped read as the `workflows` query, so a workflow deleted or filtered out between fetching the assignment and resolving this field comes back as `null`. Non-null here would instead null the entire assignment — losing `id`, `employeeId` and `createdAt` over a missing nested row. """ workflow: Workflow } input WorkflowEmployeeAssignmentFilters { employeeId: ID """ The id of the *run* this assignment created, not the template it came from. """ workflowId: ID } input WorkflowEmployeeAssignmentCreate { """The employee to assign the workflow to.""" employeeId: ID! """The template to run — a `Workflow` id with `isTemplate: true`.""" workflowTemplateId: ID! }