{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://kverna.com/schemas/pipeline.json", "title": "Kverna Pipeline", "description": "Schema for Kverna Excel Add-in pipeline definitions", "type": "object", "required": [ "id", "name", "nodes", "edges" ], "properties": { "id": { "type": "string", "description": "Unique pipeline identifier" }, "name": { "type": "string", "description": "Human-readable pipeline name" }, "description": { "type": "string", "description": "Pipeline description" }, "version": { "type": "string", "description": "Pipeline format version" }, "nodes": { "type": "array", "description": "Array of pipeline nodes", "items": { "type": "object", "required": [ "id", "type" ], "properties": { "id": { "type": "string", "description": "Unique node identifier" }, "label": { "type": "string", "description": "Optional display label" }, "type": { "type": "string", "enum": [ "credentials", "storage:config", "storage:read", "storage:write", "sheet:read", "sheet:write", "table:read", "table:write", "table:clear", "transform:python", "web:request", "ai:evaluate", "web:cors-proxy", "flow:branch", "keyvault:secret", "teams:message", "teams:message-read", "teams:channel-list", "teams:ensure-channel", "email:send", "sharepoint:list-read", "sharepoint:list-write", "sharepoint:list-manage", "drive:file-read", "drive:file-write", "drive:list", "drive:discovery", "data:to-array", "data:to-csv", "data:static", "flow:for-each", "excel:insert-image", "flow:manual-intervention", "flow:stop-pipeline", "mail:read", "data:project", "data:flatten", "data:spread", "presentation:present" ], "description": "Node type" }, "position": { "type": "object", "properties": { "x": { "type": "number" }, "y": { "type": "number" } } }, "config": { "type": "object", "description": "Node-specific configuration" } } } }, "edges": { "type": "array", "description": "Connections between nodes", "items": { "type": "object", "required": [ "id", "source", "target", "sourceHandle", "targetHandle" ], "properties": { "id": { "type": "string" }, "source": { "type": "string", "description": "Source node ID" }, "target": { "type": "string", "description": "Target node ID" }, "sourceHandle": { "type": "string", "description": "Output handle ID" }, "targetHandle": { "type": "string", "description": "Input handle ID" } } } }, "triggers": { "type": "array", "description": "Pipeline triggers", "items": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "manual", "workbookOpen", "cellChange", "schedule" ] }, "enabled": { "type": "boolean" } } } } }, "definitions": { "nodeTypes": { "credentials": { "type": "object", "description": "Authentication configuration for Azure, APIs, etc.", "properties": { "type": { "const": "credentials" }, "config": { "type": "object", "properties": { "authType": { "type": "string", "description": "Authentication Type", "default": "interactive" }, "clearCache": { "type": "string", "description": "Cached Credentials" }, "clientId": { "type": "string", "description": "Azure AD Application (client) ID. For interactive auth, provide this to override default and use your own app registration." }, "tenantId": { "type": "string", "description": "Azure AD Directory (tenant) ID or " }, "clientSecret": { "type": "string", "description": "Client Secret" }, "apiKey": { "type": "string", "description": "API Key" }, "headerName": { "type": "string", "description": "HTTP header name (e.g., Authorization, api-key, x-api-key)", "default": "Authorization" }, "headerPrefix": { "type": "string", "description": "Prefix before the key (e.g., ", "default": "Bearer " } } } } }, "storage:config": { "type": "object", "description": "Azure Storage account configuration", "properties": { "type": { "const": "storage:config" }, "config": { "type": "object", "properties": { "accountName": { "type": "string", "description": "Azure Storage account name (without .blob.core.windows.net)" } } } } }, "storage:read": { "type": "object", "description": "Read blob from Azure Storage", "properties": { "type": { "const": "storage:read" }, "config": { "type": "object", "properties": { "containerName": { "type": "string", "description": "Can be overridden by input handle" }, "blobName": { "type": "string", "description": "Can be overridden by input handle" }, "scope": { "type": "string", "description": "OAuth scope for Azure Storage", "default": "https://storage.azure.com/.default" } } } } }, "storage:write": { "type": "object", "description": "Write blob to Azure Storage", "properties": { "type": { "const": "storage:write" }, "config": { "type": "object", "properties": { "containerName": { "type": "string", "description": "Can be overridden by input handle" }, "blobName": { "type": "string", "description": "Can be overridden by input handle" }, "contentType": { "type": "string", "description": "MIME type of the blob content", "default": "application/json" }, "scope": { "type": "string", "description": "OAuth scope for Azure Storage", "default": "https://storage.azure.com/.default" } } } } }, "sheet:read": { "type": "object", "description": "Read data from Excel sheet range", "properties": { "type": { "const": "sheet:read" }, "config": { "type": "object", "properties": { "sheetName": { "type": "string", "description": "Can be overridden by input handle" }, "readUsedRange": { "type": "boolean", "description": "Read all populated cells in the sheet automatically, ignoring the range below", "default": false }, "range": { "type": "string", "description": "Can be overridden by input handle" }, "outputAsCsv": { "type": "boolean", "description": "If checked, output as CSV string instead of JSON array", "default": false }, "detectDates": { "type": "boolean", "description": "Convert Excel date serial numbers to ISO date strings (YYYY-MM-DD)", "default": true } } } } }, "sheet:write": { "type": "object", "description": "Write data to Excel sheet", "properties": { "type": { "const": "sheet:write" }, "config": { "type": "object", "properties": { "sheetName": { "type": "string", "description": "Can be overridden by input handle" }, "startCell": { "type": "string", "description": "Single cell (A1) or range (A1:D8). Range creates merged, formatted text area." }, "spreadOutput": { "type": "boolean", "description": "When checked, arrays/JSON spread across multiple cells. When unchecked, writes entire value to single cell.", "default": true }, "expectCsv": { "type": "boolean", "description": "If checked, parse input as CSV and spread across cells", "default": false } } } } }, "table:read": { "type": "object", "description": "Read data from Excel table", "properties": { "type": { "const": "table:read" }, "config": { "type": "object", "properties": { "tableName": { "type": "string", "description": "Excel table name. Can be overridden by input handle." }, "includeHeaders": { "type": "boolean", "description": "If unchecked, returns array of objects with column names as keys", "default": false }, "detectDates": { "type": "boolean", "description": "Convert Excel date serial numbers to ISO date strings (YYYY-MM-DD)", "default": true } } } } }, "table:write": { "type": "object", "description": "Write data to Excel table", "properties": { "type": { "const": "table:write" }, "config": { "type": "object", "properties": { "tableName": { "type": "string", "description": "Excel table name. Can be overridden by input handle." }, "writeMode": { "type": "string", "description": "Write Mode", "default": "overwrite" }, "createIfMissing": { "type": "boolean", "description": "Create a new table if it does not exist", "default": false }, "sheetName": { "type": "string", "description": "Sheet for new table (used when Create if Missing is enabled)", "default": "Sheet1" }, "startCell": { "type": "string", "description": "Cell position for new table (used when Create if Missing is enabled)", "default": "A1" } } } } }, "table:clear": { "type": "object", "description": "Clear all data rows from an Excel table", "properties": { "type": { "const": "table:clear" }, "config": { "type": "object", "properties": { "tableName": { "type": "string", "description": "Excel table name. If table does not exist, skips gracefully." } } } } }, "excel:insert-image": { "type": "object", "description": "Insert base64 image into Excel", "properties": { "type": { "const": "excel:insert-image" }, "config": { "type": "object", "properties": { "sheetName": { "type": "string", "description": "Target sheet. Can be overridden by input handle." }, "cell": { "type": "string", "description": "Cell to anchor the image. Can be overridden by input handle." }, "imageName": { "type": "string", "description": "Optional name for the image. If an image with this name exists, it will be replaced while keeping its position and size." }, "width": { "type": "number", "description": "Width (px)", "default": 200 }, "height": { "type": "number", "description": "Height (px)", "default": 200 } } } } }, "transform:python": { "type": "object", "description": "Transform data using Python (Pyodide)", "properties": { "type": { "const": "transform:python" }, "config": { "type": "object", "properties": { "packages": { "type": "string", "description": "Comma-separated list of packages to install (e.g., pandas, numpy)" }, "code": { "type": "string", "description": "Access inputs via input_0, input_1, etc. Return result with result object as last statement", "default": "# Python code here\\noutput = input_0 + - transformed\\noutput" }, "enableCache": { "type": "boolean", "description": "Return cached result if executed again within TTL. Cache persists until page refresh.", "default": false }, "cacheTTL": { "type": "number", "description": "How long to cache results (0 = cache until page refresh)", "default": 60 } } } } }, "web:request": { "type": "object", "description": "Make HTTP requests to APIs", "properties": { "type": { "const": "web:request" }, "config": { "type": "object", "properties": { "url": { "type": "string", "description": "URL" }, "method": { "type": "string", "description": "Method", "default": "GET" }, "authentication": { "type": "string", "description": "Authentication", "default": "none" }, "scope": { "type": "string", "description": "Required for OAuth authentication" }, "headers": { "type": "string", "description": "Default Headers (JSON)" }, "body": { "type": "string", "description": "Request Body" }, "timeout": { "type": "number", "description": "Timeout (ms)", "default": 30000 }, "enableCache": { "type": "boolean", "description": "Return cached response if called again within TTL. Cache persists until page refresh.", "default": false }, "cacheTTL": { "type": "number", "description": "How long to cache responses (0 = cache until page refresh)", "default": 60 } } } } }, "ai:evaluate": { "type": "object", "description": "Evaluate data using Azure OpenAI (OAuth)", "properties": { "type": { "const": "ai:evaluate" }, "config": { "type": "object", "properties": { "resourceName": { "type": "string", "description": "Resource name (e.g., " }, "deploymentName": { "type": "string", "description": "Model Deployment", "default": "gpt-4.1" }, "customDeployment": { "type": "string", "description": "Enter your custom deployment name" }, "systemPrompt": { "type": "string", "description": "System Prompt" }, "prompt": { "type": "string", "description": "User Prompt" }, "temperature": { "type": "number", "description": "0 = deterministic, 2 = creative. Not supported by reasoning models (gpt-5.1-chat, etc.)", "default": 0.7 }, "maxTokens": { "type": "number", "description": "Max Tokens", "default": 4000 } } } } }, "web:cors-proxy": { "type": "object", "description": "Configure CORS proxy for cross-origin requests", "properties": { "type": { "const": "web:cors-proxy" }, "config": { "type": "object", "properties": { "provider": { "type": "string", "description": "Proxy Provider", "default": "cloudflare" }, "localPort": { "type": "number", "description": "Port where the local Python proxy is running", "default": 9876 }, "customUrl": { "type": "string", "description": "Full URL to your CORS proxy (include trailing slash)" } } } } }, "flow:branch": { "type": "object", "description": "Conditional execution based on input value", "properties": { "type": { "const": "flow:branch" }, "config": { "type": "object", "properties": { "trueLabel": { "type": "string", "description": "Display label for the true path", "default": "True" }, "falseLabel": { "type": "string", "description": "Display label for the false path", "default": "False" } } } } }, "flow:for-each": { "type": "object", "description": "Loop over array items", "properties": { "type": { "const": "flow:for-each" }, "config": { "type": "object", "properties": {} } } }, "flow:manual-intervention": { "type": "object", "description": "Pause execution for manual review and editing of input values", "properties": { "type": { "const": "flow:manual-intervention" }, "config": { "type": "object", "properties": {} } } }, "flow:stop-pipeline": { "type": "object", "description": "Immediately halt pipeline execution", "properties": { "type": { "const": "flow:stop-pipeline" }, "config": { "type": "object", "properties": { "message": { "type": "string", "description": "Optional message explaining why execution stopped" } } } } }, "data:to-array": { "type": "object", "description": "Convert JSON or CSV to array", "properties": { "type": { "const": "data:to-array" }, "config": { "type": "object", "properties": { "inputFormat": { "type": "string", "description": "Input Format", "default": "json" }, "csvHasHeaders": { "type": "boolean", "description": "First row contains column names", "default": true } } } } }, "data:to-csv": { "type": "object", "description": "Convert array to CSV string", "properties": { "type": { "const": "data:to-csv" }, "config": { "type": "object", "properties": { "includeHeaders": { "type": "boolean", "description": "Add column names as first row", "default": true }, "delimiter": { "type": "string", "description": "Column separator character", "default": "" } } } } }, "data:static": { "type": "object", "description": "Output static text or data", "properties": { "type": { "const": "data:static" }, "config": { "type": "object", "properties": { "data": { "type": "string", "description": "Static text that will be output as-is. Supports JSON, CSV, or any text format." } } } } }, "data:project": { "type": "object", "description": "Select and rename fields from objects", "properties": { "type": { "const": "data:project" }, "config": { "type": "object", "properties": { "fieldsToInclude": { "type": "string", "description": "Comma-separated field names. Dot notation for nested (e.g. from.emailAddress.address)" }, "renames": { "type": "string", "description": "Field Renaming (optional)" }, "extractRawValues": { "type": "boolean", "description": "Strip field names and return just values", "default": false } } } } }, "data:flatten": { "type": "object", "description": "Flatten nested objects to single level", "properties": { "type": { "const": "data:flatten" }, "config": { "type": "object", "properties": { "maxDepth": { "type": "number", "description": "Maximum nesting depth to flatten", "default": 3 }, "separator": { "type": "string", "description": "Key Separator", "default": "." }, "arraysStrategy": { "type": "string", "description": "Arrays Handling", "default": "keep" } } } } }, "data:spread": { "type": "object", "description": "Expand inner arrays (flatMap/unnest)", "properties": { "type": { "const": "data:spread" }, "config": { "type": "object", "properties": { "pathToArray": { "type": "string", "description": "Dot path to the nested array (e.g., " }, "includeParentFields": { "type": "boolean", "description": "Carry parent context fields to each spread item", "default": false }, "parentFields": { "type": "string", "description": "Comma-separated parent fields to carry to each spread item" }, "flattenChildFields": { "type": "boolean", "description": "Merge child properties at root level (vs nested object)", "default": true } } } } }, "keyvault:secret": { "type": "object", "description": "Retrieve secret from Azure Key Vault", "properties": { "type": { "const": "keyvault:secret" }, "config": { "type": "object", "properties": { "vaultName": { "type": "string", "description": "Without .vault.azure.net" }, "secretName": { "type": "string", "description": "Can be overridden by input handle" }, "version": { "type": "string", "description": "Optional. Defaults to latest version." }, "corsProxyUrl": { "type": "string", "description": "Fallback if no CORS Proxy node connected" } } } } }, "teams:message": { "type": "object", "description": "Send message to Microsoft Teams", "properties": { "type": { "const": "teams:message" }, "config": { "type": "object", "properties": { "mode": { "type": "string", "description": "Message Mode", "default": "channel" }, "teamId": { "type": "string", "description": "Microsoft 365 Group ID (GUID)" }, "channelId": { "type": "string", "description": "Channel ID (format: 19:xxx@thread.tacv2)" }, "userId": { "type": "string", "description": "Entra Object ID (GUID) or UPN (email)" }, "contentType": { "type": "string", "description": "Content Type", "default": "text" } } } } }, "email:send": { "type": "object", "description": "Send email via Microsoft Graph", "properties": { "type": { "const": "email:send" }, "config": { "type": "object", "properties": { "to": { "type": "string", "description": "Recipient email(s), comma-separated. Can be overridden by input." }, "cc": { "type": "string", "description": "CC recipients, comma-separated" }, "bcc": { "type": "string", "description": "BCC recipients, comma-separated" }, "subject": { "type": "string", "description": "Can be overridden by input" }, "body": { "type": "string", "description": "Can be overridden by input" }, "contentType": { "type": "string", "description": "Content Type", "default": "text" }, "saveToSentItems": { "type": "boolean", "description": "Save to Sent Items", "default": true } } } } }, "mail:read": { "type": "object", "description": "Read emails from mailbox via Microsoft Graph", "properties": { "type": { "const": "mail:read" }, "config": { "type": "object", "properties": { "folder": { "type": "string", "description": "Folder", "default": "inbox" }, "maxMessages": { "type": "number", "description": "Maximum number of messages to retrieve", "default": 50 }, "onlyUnread": { "type": "boolean", "description": "Filter to only unread messages", "default": false }, "subjectContains": { "type": "string", "description": "Filter by subject text (optional)" }, "fromAddress": { "type": "string", "description": "Filter by exact sender email address" }, "hasAttachments": { "type": "boolean", "description": "Filter to only messages with attachments", "default": false }, "receivedSince": { "type": "string", "description": "ISO date or shorthand (7d, 30d) - optional" }, "selectFields": { "type": "string", "description": "Comma-separated fields for $select (advanced)" }, "markAsRead": { "type": "boolean", "description": "Mark retrieved messages as read", "default": false }, "includeBody": { "type": "boolean", "description": "Include full message body instead of preview (HTML or text)", "default": false }, "includeAttachments": { "type": "boolean", "description": "Fetch attachment content for each message (base64). Adds latency.", "default": false } } } } }, "sharepoint:list-read": { "type": "object", "description": "Read items from SharePoint list", "properties": { "type": { "const": "sharepoint:list-read" }, "config": { "type": "object", "properties": { "siteUrl": { "type": "string", "description": "SharePoint site URL. Can be overridden by input." }, "listName": { "type": "string", "description": "List Name" }, "columns": { "type": "string", "description": "Comma-separated column names to retrieve. Leave empty for all." }, "filter": { "type": "string", "description": "OData filter expression. Can be overridden by input." }, "top": { "type": "number", "description": "Max items to fetch (ignored if Fetch All is enabled)", "default": 100 }, "fetchAll": { "type": "boolean", "description": "Automatically fetch all pages. Limited by Max Limit below.", "default": false }, "maxLimit": { "type": "number", "description": "Safety limit when Fetch All is enabled", "default": 10000 }, "enableCache": { "type": "boolean", "description": "Return cached items if read again within TTL. Cache persists until page refresh.", "default": false }, "cacheTTL": { "type": "number", "description": "How long to cache items (0 = cache until page refresh)", "default": 60 } } } } }, "sharepoint:list-write": { "type": "object", "description": "Write items to SharePoint list", "properties": { "type": { "const": "sharepoint:list-write" }, "config": { "type": "object", "properties": { "siteUrl": { "type": "string", "description": "SharePoint site URL. Can be overridden by input." }, "listName": { "type": "string", "description": "List Name" }, "mode": { "type": "string", "description": "Write Mode", "default": "create" }, "keyColumn": { "type": "string", "description": "Column to match for update/upsert" } } } } }, "sharepoint:list-manage": { "type": "object", "description": "Create, configure, or sync SharePoint list schema", "properties": { "type": { "const": "sharepoint:list-manage" }, "config": { "type": "object", "properties": { "siteUrl": { "type": "string", "description": "SharePoint site URL. Can be overridden by input." }, "listName": { "type": "string", "description": "Display name for the list" }, "createIfMissing": { "type": "boolean", "description": "Create the list if it does not exist", "default": true }, "recreate": { "type": "boolean", "description": "Delete and recreate list. WARNING: All existing data will be lost!", "default": false }, "ensureColumns": { "type": "boolean", "description": "Add any missing columns from definition (non-destructive)", "default": false }, "syncColumns": { "type": "boolean", "description": "Sync columns to match definition exactly. WARNING: May remove existing columns!", "default": false }, "columns": { "type": "string", "description": "Columns Definition" }, "seedData": { "type": "string", "description": "Seed Data" } } } } }, "drive:file-read": { "type": "object", "description": "Read file from OneDrive or SharePoint", "properties": { "type": { "const": "drive:file-read" }, "config": { "type": "object", "properties": { "source": { "type": "string", "description": "Source", "default": "onedrive" }, "driveMode": { "type": "string", "description": "Drive Mode", "default": "personal" }, "driveId": { "type": "string", "description": "Drive ID from Drive Discovery node" }, "siteUrl": { "type": "string", "description": "SharePoint site URL" }, "filePath": { "type": "string", "description": "Relative path like /folder/file.xlsx" }, "shareUrl": { "type": "string", "description": "Sharing link (e.g. from SharePoint or OneDrive) — resolved via Microsoft Graph" }, "encodeAsBase64": { "type": "boolean", "description": "Return file content as base64 (use for binary files like images, PDFs, etc.)", "default": false } } } } }, "drive:file-write": { "type": "object", "description": "Write file to OneDrive or SharePoint", "properties": { "type": { "const": "drive:file-write" }, "config": { "type": "object", "properties": { "source": { "type": "string", "description": "Source", "default": "onedrive" }, "driveMode": { "type": "string", "description": "Drive Mode", "default": "personal" }, "driveId": { "type": "string", "description": "Drive ID from Drive Discovery node" }, "siteUrl": { "type": "string", "description": "SharePoint site URL" }, "filePath": { "type": "string", "description": "Relative path like /folder/file.xlsx" }, "inputFormat": { "type": "string", "description": "Text = UTF-8 encoded string, Base64 = binary file (decoded from base64)", "default": "text" }, "overwrite": { "type": "boolean", "description": "Replace existing file. If false, will fail if file exists.", "default": true } } } } }, "drive:list": { "type": "object", "description": "List files in a folder from OneDrive or SharePoint", "properties": { "type": { "const": "drive:list" }, "config": { "type": "object", "properties": { "source": { "type": "string", "description": "Source", "default": "onedrive" }, "driveMode": { "type": "string", "description": "Drive Mode", "default": "personal" }, "driveId": { "type": "string", "description": "Drive ID from Drive Discovery node" }, "siteUrl": { "type": "string", "description": "SharePoint site URL" }, "folderPath": { "type": "string", "description": "Relative folder path. / for root.", "default": "/" } } } } }, "drive:discovery": { "type": "object", "description": "Discover available drives (OneDrive + shared drives)", "properties": { "type": { "const": "drive:discovery" }, "config": { "type": "object", "properties": {} } } }, "teams:message-read": { "type": "object", "description": "Read messages from Teams channel", "properties": { "type": { "const": "teams:message-read" }, "config": { "type": "object", "properties": { "mode": { "type": "string", "description": "Message Mode", "default": "channel" }, "teamId": { "type": "string", "description": "Team ID from Teams Channel List node" }, "channelId": { "type": "string", "description": "Channel ID from Teams Channel List node" }, "limit": { "type": "number", "description": "Max messages to fetch (ignored if Fetch All is enabled)", "default": 50 }, "fetchAll": { "type": "boolean", "description": "Automatically fetch all pages. Limited by Max Limit below.", "default": false }, "maxLimit": { "type": "number", "description": "Safety limit when Fetch All is enabled", "default": 10000 }, "markAsRead": { "type": "boolean", "description": "Mark fetched messages as read (only for channel messages)", "default": false } } } } }, "teams:channel-list": { "type": "object", "description": "List Teams or channels", "properties": { "type": { "const": "teams:channel-list" }, "config": { "type": "object", "properties": { "listType": { "type": "string", "description": "List Type", "default": "teams" }, "teamId": { "type": "string", "description": "Team ID to list channels from" } } } } }, "teams:ensure-channel": { "type": "object", "description": "Ensure a channel exists in a Team (create if missing)", "properties": { "type": { "const": "teams:ensure-channel" }, "config": { "type": "object", "properties": { "teamName": { "type": "string", "description": "Display name of the team (must be a team you belong to). Can be overridden by input." }, "createTeamIfMissing": { "type": "boolean", "description": "Create the team if it does not exist (requires Team.Create permission)", "default": false }, "channelName": { "type": "string", "description": "Display name for the channel. Can be overridden by input." }, "channelDescription": { "type": "string", "description": "Optional description for new channel" }, "welcomeMessage": { "type": "string", "description": "Optional message to post when channel is created. Can be overridden by input." }, "welcomeContentType": { "type": "string", "description": "Message Format", "default": "text" } } } } }, "presentation:present": { "type": "object", "description": "LLM-scaffolded Excel dashboards and reports. Connect data outputs, describe how you want them presented, and use an LLM to generate the layout.", "properties": { "type": { "const": "presentation:present" }, "config": { "type": "object", "properties": { "sheetName": { "type": "string", "description": "The sheet where the presentation will be rendered. WARNING: This sheet will be cleared and rebuilt on each run.", "default": "Dashboard" }, "description": { "type": "string", "description": "Describe how you want the data presented. This is sent to the LLM to guide layout generation." }, "semanticLabels": { "type": "string", "description": "Give each connected input a semantic description for the LLM (e.g., " }, "updateMode": { "type": "string", "description": "Update Mode", "default": "full" }, "presentationSpec": { "type": "string", "description": "The JSON layout spec generated by an LLM. Use " } } } } } } } }