Documentation Index
Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
Use this file to discover all available pages before exploring further.
This page is the runtime reference for SCRIPT steps inside Activity Plans. Use it when you need the exact JavaScript objects, helper methods, and limits available while an Activity is running custom logic.
Overview
A script step runs inline JavaScript in a sandboxed runtime. It has access to:
- The current task and its properties
- Document families attached to the task
- The ability to load KDDB documents and inspect or modify their content
- Knowledge feature lookups for classification workflows
- Logging for diagnostics
- Service bridge calls to external APIs (project-scoped)
- LLM invocations with automatic cost tracking
Scripts return an action that determines which downstream path the Activity follows.
Adding a Script Step
Open the Activity Plan
Open the Activity Plan that models the business workflow.
Add a SCRIPT Step
Add a SCRIPT step to the Activity Plan graph.
Place It in the Flow
Connect dependencies from upstream steps, then connect action outcomes to downstream steps.
Write Your Script
Select the script step and write JavaScript in the editor. Use the full-screen editor and snippets for larger scripts.
Define Actions
Add one or more actions in the Script Actions section. These are the possible outcomes your script can return.
Connect Dependencies
Use dependencies such as route:review or route:post so downstream steps only run for the matching action.
Script Structure
Every script must return an object with an action property that matches one of the declared action names:
// Inspect task data, documents, or knowledge features
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true);
// Make a routing decision
var isInvoice = content.indexOf("INVOICE") !== -1;
// Return the action to follow
return { action: isInvoice ? "invoice" : "other" };
Optionally, the return value can include a features array to assign knowledge features to document families:
return {
action: "classified",
features: [
{ documentFamilyId: families[0].id, featureId: "feature-uuid" }
]
};
Context Objects
Scripts have access to several pre-bound context objects. These are available as global variables in your script.
The Activity Plan runtime is Task-centered. If an Activity was started from a Task, scripts receive the Task snapshot and document families attached to it. The runtime does not expose arbitrary browser state; scripts work through the bound Task, document family, document, Service Bridge, LLM, and knowledge helper objects.
task
The current task being processed.
| Property | Type | Description |
|---|
id | string | Task ID |
title | string | Task title |
status | string | Current task status slug |
metadata | object | Task metadata (from the template) |
data | object | Task properties/data (custom key-value pairs) |
families
An array of document families attached to the task.
| Property | Type | Description |
|---|
id | string | Document family ID |
name | string | File path / name |
metadata | object | Family metadata |
mixins | string[] | Mixin slugs applied to this family |
featureIds | string[] | Knowledge feature IDs assigned to this family |
contentObjects | array | Content objects (documents and native files) |
Each content object has:
| Property | Type | Description |
|---|
id | string | Content object ID |
contentType | number | 0 = KDDB document, 1 = native file |
mimeType | string | MIME type (e.g., application/pdf) |
size | number | File size in bytes |
org
The current organization.
| Property | Type | Description |
|---|
id | string | Organization ID |
slug | string | Organization slug |
Complete Runtime Objects
Global Helpers
| Object or function | Description |
|---|
log.debug/info/warn/error(...args) | Structured logs captured with the Activity step |
console.log/warn/error(...args) | Console-compatible logging aliases |
loadDocument(familyId) | Load a KDDB document by document family ID |
serviceBridge.call(bridgeSlug, endpointName, body?) | Call a project-scoped Service Bridge endpoint |
serviceBridge.list() | List Service Bridges available to the current project context |
llm.invoke(prompt, options?) | Invoke the configured LLM service |
llm.invokeWithPromptRef(promptSlug, params?, options?) | Render a platform prompt template, then invoke the LLM |
lookupFeature(slug) | Find a knowledge feature by slug |
lookupFeatureType(slug) | Find a knowledge feature type by slug |
lookupFeaturesByType(typeSlug) | List features under a feature type |
lookupFeatureByProperties(featureTypeSlug, properties) | Compute the feature slug from typed properties and return it if it exists |
lookupOrCreateFeature(featureTypeSlug, properties) | Compute the feature slug, return it if present, or create it |
tasks
The tasks namespace reads and updates Task records in the same project as the current Activity.
| Method | Description |
|---|
tasks.current() | Current Task |
tasks.parent() | Parent Task, or null |
tasks.subTasks() | Subtasks of the current Task |
tasks.subTasksOf(taskId) | Subtasks of a specific Task |
tasks.get(taskId) | Task by ID |
tasks.query(filter) | Query Tasks by status, statusType, parentTaskId, templateSlug, and limit |
tasks.lookupStatus(slug) | Project Task status object |
tasks.listStatuses() | All project Task statuses |
tasks.setStatus(taskId, statusSlug) | Update status. A target status type of DONE triggers Activity advancement. |
tasks.setProperties(taskId, props) | Merge values into Task data |
tasks.setMetadata(taskId, metadata) | Merge Task metadata |
tasks.setTitle(taskId, title) | Update title |
tasks.setDescription(taskId, description) | Update description |
tasks.lock(taskId) / tasks.unlock(taskId) | Lock or unlock the Task |
Task objects returned from tasks include id, title, description, status, statusType, statusLabel, metadata, data, parentTaskId, templateId, assigneeId, teamId, priority, locked, createdOn, and updatedOn.
documents
The documents namespace reads and updates document family records in the current project. Use it for family-level state. Use loadDocument() when you need content nodes, tags, data objects, validations, exceptions, or KDDB metadata.
| Method | Description |
|---|
documents.taskFamilies() | Families attached to the current Task |
documents.get(familyId) | Family summary by ID |
documents.getByPath(storeSlug, path) | Family summary by store/path |
documents.query(filter) | Query families by storeSlug, path, pathContains, label, mixin, status, featureSlug, locked, and limit |
documents.contentObjects(familyId) | Content object history |
documents.latestContentObject(familyId, contentType?) | Latest content object. contentType can be DOCUMENT, NATIVE, or a numeric value. |
documents.load(familyId) | Load the KDDB document |
documents.listStatuses() / documents.lookupStatus(slug) | Project document statuses |
documents.setMetadata(familyId, metadata) | Merge metadata |
documents.addLabel(familyId, label) / documents.removeLabel(familyId, label) | Manage labels |
documents.setStatus(familyId, statusSlug) | Update document status |
documents.lock(familyId) / documents.unlock(familyId) | Lock or unlock the family |
documents.attachToTask(familyId, taskId?) / documents.detachFromTask(familyId, taskId?) | Manage Task-family links |
Document family summaries include id, path, summary, metadata, labels, mixins, features, featureIds, status, statusLabel, locked, pendingProcessing, storeSlug, storeRef, contentObjects, createdOn, and updatedOn.
Helper Functions
log.{debug,info,warn,error}(...args)
Write a log entry via leveled methods. Each method is variadic and joins arguments with spaces.
log.info("Processing document:", families[0].name);
log.warn("No content found in document");
loadDocument(familyId)
Load a KDDB document by its family ID. Returns a document object with methods for reading and modifying content. Limited to 5 calls per script execution.
var doc = loadDocument(families[0].id);
// ... inspect or modify the document ...
// Do NOT call doc.close(); the orchestrator handles persistence automatically
Do not call doc.close() in your scripts. The orchestrator automatically persists any document modifications as new content object versions after the script completes. Calling close() prematurely closes the underlying database, which prevents the orchestrator from saving your changes.
lookupFeature(slug)
Look up a single knowledge feature by its slug. Returns the feature object or null.
var feature = lookupFeature("doc-type-invoice");
if (feature) {
log.info("Found feature:", feature.id);
}
lookupFeatureType(slug)
Look up a knowledge feature type by its slug. Returns { id, name, slug } or null.
var featureType = lookupFeatureType("document-classification");
if (featureType) {
log.info("Feature type:", featureType.name);
}
lookupFeaturesByType(typeSlug)
Get all knowledge features belonging to a feature type. Returns an array of { id, slug } objects.
var features = lookupFeaturesByType("document-classification");
for (var i = 0; i < features.length; i++) {
log.info("Feature:", features[i].slug);
}
lookupFeatureByProperties(featureTypeSlug, properties)
Find a feature by the deterministic slug generated from its feature type and properties.
var feature = lookupFeatureByProperties("supplier-risk", {
supplierId: task.data.supplierId,
riskLevel: "high"
});
lookupOrCreateFeature(featureTypeSlug, properties)
Find or create a feature using the deterministic slug generated from the feature type and properties.
var feature = lookupOrCreateFeature("document-classification", {
category: "invoice",
source: "script"
});
return {
action: "classified",
features: [{ documentFamilyId: families[0].id, featureId: feature.id }]
};
Working with Documents
When you call loadDocument(), you receive a document object with both read and write capabilities.
Reading Document Content
var doc = loadDocument(families[0].id);
// Get the root content node
var root = doc.getRootNode();
// Get all text content joined with spaces
var fullText = root.getAllContent(" ", true);
// Get document metadata
var metadata = doc.getMetadata();
log.info("Document type:", metadata.documentType);
// Get document labels
var labels = doc.getLabels();
log.info("Labels:", labels.join(", "));
Selecting Content Nodes
Use selector expressions to find specific content nodes in the document tree:
var doc = loadDocument(families[0].id);
// Find all page nodes
var pages = doc.select("//page");
log.info("Document has", pages.length, "pages");
// Find lines containing specific text
var matches = doc.select("//line[contains(@content, 'TOTAL')]");
// Find the first matching node
var header = doc.selectFirst("//line[position() = 1]");
if (header) {
log.info("First line:", header.getContent());
}
Navigating the Content Tree
Content nodes form a tree structure (document > page > line > word). You can navigate it programmatically:
var doc = loadDocument(families[0].id);
var root = doc.getRootNode();
// Get child nodes
var pages = root.getChildren();
for (var i = 0; i < pages.length; i++) {
var page = pages[i];
log.info("Page", page.getPage(), ":", page.getNodeType());
// Get lines on this page
var lines = page.getChildren();
for (var j = 0; j < lines.length; j++) {
log.debug(" Line:", lines[j].getContent());
}
}
Content Node Properties
Each content node provides:
| Method | Returns | Description |
|---|
getContent() | string | Text content of this node |
getNodeType() | string | Node type (page, line, word, etc.) |
getChildren() | node[] | Child content nodes |
getParent() | node | Parent content node |
getDescendants() | node[] | All descendant nodes |
getAllContent(sep, strip) | string | Join all descendant text |
getPage() | number | Page number |
getBoundingBox() | object | { x, y, width, height } or null |
getConfidence() | number | OCR confidence score |
getTags() | array | Tags on this node |
hasTag(name) | boolean | Check for a specific tag |
getFeatures() | array | Features on this node |
hasFeature(type, name) | boolean | Check for a specific feature |
getFeatureValue(type, name) | any | Get a feature value |
Modifying Documents
Script steps can modify documents. Changes are automatically persisted as new content object versions after the script completes.
var doc = loadDocument(families[0].id);
// Set metadata values
doc.setMetadata("processedAt", new Date().toISOString());
doc.setMetadata("documentType", "invoice");
// Add and remove labels
doc.addLabel("reviewed");
doc.removeLabel("pending");
Tagging Content Nodes
Tags mark content nodes for downstream processing or data extraction:
var doc = loadDocument(families[0].id);
// Find and tag specific content
var nodes = doc.select("//line[contains(@content, 'TOTAL')]");
for (var i = 0; i < nodes.length; i++) {
nodes[i].tag("financial/total-line", {
confidence: 0.95,
value: nodes[i].getContent()
});
}
Tag options:
| Option | Type | Description |
|---|
value | string | Tag value |
confidence | number | Confidence score (0.0–1.0) |
groupUuid | string | Group UUID for data object grouping |
parentGroupUuid | string | Parent group UUID for hierarchy |
cellIndex | number | Instance index within a group |
index | number | Ordering index |
note | string | User note |
status | string | Tag status |
ownerUri | string | Provenance URI |
Adding Features to Content Nodes
var doc = loadDocument(families[0].id);
var pages = doc.select("//page");
for (var i = 0; i < pages.length; i++) {
var page = pages[i];
var content = page.getAllContent(" ", true);
// Classify page type based on content
var pageType = "body";
if (content.indexOf("Table of Contents") !== -1) {
pageType = "toc";
} else if (content.match(/appendix\s+[a-z]/i)) {
pageType = "appendix";
}
page.setFeature("classification", "page_type", pageType);
}
Creating Data Objects
Data objects represent structured, extracted data:
var doc = loadDocument(families[0].id);
// Find or create a top-level data object (idempotent)
var invoice = doc.getOrCreate("invoice", {
taxonomyRef: "taxonomy://acme/invoice-taxonomy"
});
// Add attributes — path defaults to parent.path + "/" + tag,
// and ownerUri defaults from the script context.
invoice.addAttribute({
tag: "vendor_name",
value: "Acme Corp"
});
invoice.addAttribute({
tag: "total_amount",
decimalValue: 1234.56
});
Nested Data Objects
Build hierarchical data structures with parent/child relationships:
var doc = loadDocument(families[0].id);
var order = doc.getOrCreate("purchase_order", {
taxonomyRef: "taxonomy://acme/po-taxonomy"
});
order.addAttribute({ tag: "po_number", value: "PO-2026-0042" });
var items = [
{ sku: "WDG-100", qty: 50, price: 12.99 },
{ sku: "WDG-200", qty: 25, price: 24.50 }
];
for (var i = 0; i < items.length; i++) {
var lineItem = order.addChild({
path: "purchase_order/line_item",
sourceOrdering: String(i)
});
lineItem.addAttribute({ tag: "sku", value: items[i].sku });
lineItem.addAttribute({ tag: "unit_price", decimalValue: items[i].price });
}
Reading Data Objects
var doc = loadDocument(families[0].id);
// Get all data objects
var allObjects = doc.getAllDataObjects();
log.info("Total data objects:", allObjects.length);
// Find objects by path
for (var i = 0; i < allObjects.length; i++) {
var obj = allObjects[i];
if (obj.getPath() === "invoice") {
// List attributes
var attrs = obj.getAttributes();
for (var j = 0; j < attrs.length; j++) {
log.info(" ", attrs[j].getValue());
}
// Find an attribute value directly
var amount = obj.getFirstAttributeValue("total_amount");
if (amount !== undefined) {
log.info("Total:", amount);
}
// Find children by path
var lineItems = obj.getChildrenByPath("invoice/line_item");
log.info("Line items:", lineItems.length);
}
}
Attribute Data Types
When adding attributes, specify the type to control how the value is stored and displayed:
| Type | Description | Typed Value Property |
|---|
STRING | Free text | stringValue |
CURRENCY | Monetary amount | decimalValue |
NUMBER | Numeric value | decimalValue |
PERCENTAGE | Percentage | decimalValue |
DECIMAL | Decimal number | decimalValue |
INTEGER | Whole number | decimalValue |
DATE | Date | dateValue (ISO string) |
DATETIME | Date and time | dateValue (ISO string) |
BOOLEAN | True/false | booleanValue |
SELECTION | Enumerated value | stringValue |
URL | Web address | stringValue |
EMAIL | Email address | stringValue |
PHONE | Phone number | stringValue |
Knowledge Feature Assignment
Scripts can assign knowledge features to document families as part of their return value. This is useful for classification workflows where the script analyzes document content and assigns the appropriate features.
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true).toLowerCase();
var features = [];
// Check for document characteristics
if (content.indexOf("invoice") !== -1) {
var feature = lookupFeature("doc-type-invoice");
if (feature) {
features.push({
documentFamilyId: families[0].id,
featureId: feature.id
});
}
}
// Add a label for tracking
if (features.length > 0) {
doc.addLabel("classified");
doc.setMetadata("classifiedAt", new Date().toISOString());
}
return {
action: features.length > 0 ? "classified" : "unclassified",
features: features
};
Calling Service Bridges
Script steps can call external APIs through service bridges that are configured as project resources. Only bridges that have been added to the task’s project are accessible — scripts cannot call arbitrary bridges in the organization.
Calling an Endpoint
Use serviceBridge.call(bridgeSlug, endpointName, body?) to make HTTP requests through a bridge:
// GET request (no body)
var data = serviceBridge.call("my-api-bridge", "get-status");
log.info("Status:", data.status);
// POST request with body
var result = serviceBridge.call("my-api-bridge", "submit", {
documentId: families[0].id,
metadata: task.metadata
});
The response is automatically parsed as JSON if possible, otherwise returned as a string. Limited to 10 calls per script execution with a 10-second timeout per call.
Use serviceBridge.list() when you need to inspect the project-scoped bridges available to the script:
var bridges = serviceBridge.list();
for (var i = 0; i < bridges.length; i++) {
log.info("Bridge:", bridges[i].slug);
}
Example: Enriching Documents via External API
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true);
// Send text to external classification API
var classification = serviceBridge.call("classifier-bridge", "classify", {
text: content.substring(0, 5000)
});
doc.setMetadata("externalClassification", classification.category);
doc.setMetadata("classificationConfidence", classification.confidence);
if (classification.confidence > 0.8) {
doc.addLabel("auto-classified");
}
return { action: classification.category };
Service bridges must be added as project resources before they can be used in script steps. If a bridge exists in the organization but is not linked to the project, scripts will receive a “bridge not found” error.
Making LLM Calls
Script steps can invoke the platform’s LLM (Large Language Model) service directly. All calls are automatically tracked in model costs for billing and reporting.
Basic LLM Call
Use llm.invoke(prompt, options?) to send a prompt and get a response:
var response = llm.invoke(
"Classify this document title into one of: invoice, receipt, " +
"contract, other. Title: " + families[0].name +
". Reply with just the category name."
);
log.info("LLM classified as:", response);
var category = response.trim().toLowerCase();
return { action: category };
The optional options parameter accepts:
| Option | Type | Description |
|---|
note | string | Descriptive label stored in the model cost record for billing visibility |
var response = llm.invoke(
"Summarize this document metadata: " + JSON.stringify(task.metadata),
{ note: "Document summary for task " + task.id }
);
Using Prompt Templates
If you have prompt templates configured as project resources, use llm.invokeWithPromptRef(promptSlug, parameters?, options?) to resolve and render them:
var response = llm.invokeWithPromptRef(
"document-classifier", // prompt template slug
{ // template parameter values
documentName: families[0].name,
documentType: families[0].metadata.mimeType || "unknown",
taskTitle: task.title
},
{ note: "Classify " + families[0].name }
);
log.info("Classification:", response);
return { action: response.trim().toLowerCase() };
Prompt templates support {variable} placeholders that are replaced with the provided parameter values.
Analyzing Document Content
Combine document loading with LLM calls for AI-powered document analysis:
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true);
var analysis = llm.invoke(
"Analyze this document and return a JSON object with:\n" +
"- documentType: the type of document\n" +
"- keyEntities: array of important entity names\n" +
"- summary: one-sentence summary\n\n" +
"Document text:\n" + content.substring(0, 3000),
{ note: "Document analysis: " + families[0].name }
);
try {
var result = JSON.parse(analysis);
doc.setMetadata("aiDocumentType", result.documentType);
doc.setMetadata("aiSummary", result.summary);
doc.setMetadata("aiEntities", result.keyEntities);
} catch (e) {
log.warn("Could not parse LLM response as JSON");
}
return { action: "analyzed" };
LLM calls are limited to 5 per script execution and count against your organization’s model costs. Each call records the input and output token counts in the platform’s billing system.If the LLM service is not configured on your environment, calls will throw an error with a clear message.
Full Workflow Example
This example combines document inspection, tagging, data extraction, and knowledge feature assignment into a single script:
var doc = loadDocument(families[0].id);
var features = [];
// 1. Classify document type
var root = doc.getRootNode();
var fullText = root.getAllContent(" ", true);
var isInvoice = fullText.indexOf("INVOICE") !== -1;
// 2. Set metadata and labels
doc.setMetadata("documentType", isInvoice ? "invoice" : "unknown");
doc.addLabel(isInvoice ? "invoice" : "unclassified");
// 3. Assign knowledge feature
if (isInvoice) {
var feat = lookupFeature("doc-type-invoice");
if (feat) {
features.push({
documentFamilyId: families[0].id,
featureId: feat.id
});
}
}
// 4. Tag header lines
var headerNodes = doc.select("//line[position() <= 5]");
for (var i = 0; i < headerNodes.length; i++) {
headerNodes[i].tag("document/header-line");
headerNodes[i].setFeature("layout", "section", "header");
}
// 5. Extract data if invoice
if (isInvoice) {
var invoice = doc.getOrCreate("invoice", {
taxonomyRef: "taxonomy://acme/invoice"
});
var invNumNode = doc.selectFirst(
"//line[contains(@content, 'Invoice #')]"
);
if (invNumNode) {
var match = invNumNode.getContent().match(/Invoice #\s*(\S+)/);
if (match) {
invoice.addAttribute({
tag: "invoice_number",
value: match[1],
confidence: 0.85
});
}
}
}
return {
action: isInvoice ? "invoice-processed" : "skipped",
features: features
};
The Script Editor
The plan flow editor provides two ways to edit script code:
Inline Editor
When you select a script node, the properties panel on the right shows a Monaco code editor with:
- Syntax highlighting for JavaScript
- IntelliSense auto-completion for all context objects, helper functions, and document APIs
- Script Actions management below the editor
Full-Screen Editor
Click the Expand button to open the full-screen script editor, which provides:
- A large code editing area
- A Snippet Browser panel on the right with categorized code templates
- Insert or Replace buttons to use snippets as starting points
- Script action management in the footer
Snippet Categories
The snippet browser includes ready-to-use templates organized by category:
| Category | What’s Included |
|---|
| Getting Started | Basic script skeleton with document loading |
| Service Bridges | List bridges, call GET/POST endpoints, enrich documents via external APIs |
| LLM Calls | Basic prompts, cost-tracked calls, prompt templates, document analysis |
| Tagging Nodes | Tag nodes by selector or by ID list |
| Data Objects | Create, nest, and look up data objects and attributes |
| Knowledge Features | Classify documents and assign features |
| Content Analysis | Page classification by content heuristics |
| Full Workflows | End-to-end examples combining all capabilities |
Viewing Script Logs
Script step logs are captured in CloudWatch and viewable from the Activity step details.
How It Works
Every script execution automatically records:
- A start log entry when the script begins
- All
log.* and console.* calls made during execution
- An end log entry when the script completes (with success or error status)
Viewing Logs in the UI
To view logs for a completed script step:
- Open the Activity run or parent Task that owns the Activity
- Open the completed SCRIPT step details
- The Logs section displays all log entries with timestamps
Logs are paginated for scripts that produce large amounts of output. Use the search field to filter log entries by message content.
Add log.info(...) calls at key decision points in your scripts. The logs persist after execution, making them invaluable for debugging routing decisions and understanding why a script chose a particular action.
Loading Shared Modules
Script steps can pre-load reusable JavaScript modules so that their functions and variables are available in global scope within your script. This lets you build a library of shared utilities, such as string helpers, validation logic, and API wrappers, and reuse them across multiple script steps without duplicating code.
To configure module pre-loading, use the Module Refs picker in the script step’s properties panel. Select one or more JavaScript modules from your organization. The selected modules are fetched and executed in order before your script runs.
// Assuming a module "my-org/string-utils" is loaded via Module Refs,
// its exported functions are available directly:
var cleaned = cleanText(families[0].name);
var emails = extractEmails(cleaned);
log.info("Found", emails.length, "emails in filename");
return { action: "processed" };
Shared modules must be JavaScript modules deployed with scriptLanguage: "javascript" and an inline script field. A maximum of 10 modules can be pre-loaded per script step.
Execution Details
- Runtime: JavaScript (ES5+) via the Goja engine
- Timeout: 15 seconds per script execution (all operations, including document loads, bridge calls, and LLM calls, count against this limit)
- Document loads: Maximum 5 per script execution
- Service bridge calls: Maximum 10 per script execution, 10-second timeout per call, project-scoped
- LLM calls: Maximum 5 per script execution, token usage recorded in model costs
- Persistence: Any document modifications (tags, metadata, labels, data objects) are automatically saved as new content object versions after the script completes. Do not call
doc.close() yourself.
- Action matching: The returned action name is matched case-insensitively against the declared script actions