Skip to main content

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.

A SCRIPT step runs JavaScript inside an Activity. It is the right tool when the workflow needs custom logic that is too specific for a declarative step. Script steps are commonly used to inspect documents, normalize extracted data, assign knowledge features, call Service Bridges, and decide which branch of the Activity Plan should run next.

When to Use a SCRIPT Step

Use a SCRIPT step when you need to:
  • Route the Activity based on document content, metadata, extraction results, or task status
  • Make several checks before deciding whether human review is needed
  • Update document metadata, labels, tags, data objects, or attributes
  • Assign knowledge features to document families
  • Call one or more Service Bridges and combine their responses
  • Emit a named action for downstream dependencies
Do not use a script just to make one external API call. Use a Service Bridge step for that so the external call has its own step status, logs, retry behavior, and result.

Step Configuration

{
  "slug": "route-invoice",
  "kind": "SCRIPT",
  "dependsOn": ["extract"],
  "config": {
    "scriptActions": [
      { "name": "review" },
      { "name": "post" },
      { "name": "reject" }
    ],
    "scriptSidecars": ["acme-finance/invoice-script-helpers"],
    "scriptBody": "return { action: 'review' };"
  }
}
Config keyDescription
scriptBodyInline JavaScript source
scriptActionsNamed actions the script can return
scriptSidecarsJavaScript module refs loaded before the script runs
The script must return an object with an action property when downstream routing depends on the step.
return { action: "review" };
Downstream steps can depend on that action:
{
  "slug": "analyst-review",
  "kind": "CREATE_TASK",
  "dependsOn": ["route-invoice:review"]
}

Return Value

The full return value can carry three optional pieces alongside action:
return {
  action: "approve",
  features: [
    { documentFamilyId: families[0].id, featureId: "fc_billing_ready" }
  ],
  nextActivity: {
    activityPlanRef: "activity-plan://acme-finance/billing-extraction",
    inputs: { reviewedBy: org.userEmail }
  }
};
FieldDescription
actionRequired. Must match one of the scriptActions names declared on the step.
featuresOptional. Attaches knowledge features to document families on the current Activity.
nextActivityOptional. Requests a follow-up Activity Plan to start when the current Activity completes.
features and nextActivity are independent — return either, both, or neither.

Spawning a Follow-Up Activity

A SCRIPT step can request another Activity Plan to start automatically when the current Activity completes. Use this to chain related work, e.g. “after intake classifies an invoice, kick off the extraction plan.” The spawned Activity:
  • Starts only after the current Activity reaches COMPLETED (not on individual step completion).
  • Inherits the current Activity’s project. The target plan must already be bound to that project.
  • Inherits the current Activity’s document families when documentFamilyIds is omitted.
  • Records its source in triggerMetadata (sourceActivityId, sourceStepId, sourceActionUuid, sourceProjectId) so audit trails work in both directions.
  • Has triggerKind set to ACTIVITY_COMPLETED.

nextActivity shape

return {
  action: "approve",
  nextActivity: {
    activityPlanRef: "activity-plan://acme-finance/billing-extraction",
    inputs: { reviewedBy: org.userEmail, source: "intake" },
    title: "Billing for " + families[0].name,
    description: "Auto-spawned from invoice intake",
    documentFamilyIds: [families[0].id],
    features: [
      { documentFamilyId: families[0].id, featureId: "fc_billing_ready" }
    ],
    priority: 5,
    triggerMetadata: { batchId: task.data.batchId }
  }
};
FieldRequiredDescription
activityPlanRefYesSlug or activity-plan://orgSlug/planSlug of the plan to start. Must be bound to the current project.
inputsNoObject validated against the target plan’s inputOptions at spawn time.
titleNoOverride the spawned Activity’s title. Defaults to the plan’s defaultTitleTemplate.
descriptionNoOverride the description. Defaults to the plan’s defaultDescriptionTemplate.
documentFamilyIdsNoDocument families to link to the spawned Activity. Defaults to inheriting the current Activity’s families.
featuresNoKnowledge features to attach to document families before the spawn fires, so the new plan’s templates and scripts see them.
priorityNoPriority for the spawned Activity.
triggerMetadataNoFree-form metadata. Server-controlled fields (sourceActivityId, etc.) always win over anything you set here.

Failure handling

Spawn failures are soft. If the target plan doesn’t exist, FGAC denies, or inputs fail validation, the source Activity still finishes cleanly. The failure is recorded on the source step in script_result.nextActivityError. On success, script_result.nextActivityId is set to the new Activity’s ID.

Multiple spawns

Each SCRIPT step in a plan may emit its own nextActivity. They fan out at the source Activity’s completion in step insertion order. Within a single script return, only one nextActivity is supported.

Same-project only

A script can only spawn Activity Plans bound to the current project. Cross-project spawns are rejected. If you need fan-out across projects, model it through a Service Bridge call instead.

Activity SCRIPT Runtime

Activity Plan SCRIPT steps run in the server-side JavaScript runtime. The runtime exposes the same business objects the Activity is working on, plus helper namespaces for task state, document families, document content, Service Bridges, LLM calls, and knowledge features.
ObjectUse
taskSnapshot of the Task that started the Activity, when the Activity is task-backed
familiesSnapshot array of document families attached to the Task
orgCurrent organization { id, slug }
tasksQuery and mutate task records in the current project
documentsQuery and mutate document family records in the current project
loadDocument(familyId) / documents.load(familyId)Load a KDDB document for content nodes, tags, data objects, metadata, labels, validations, and exceptions
lookupFeature*Find or create knowledge features for classification and routing
serviceBridgeCall project-scoped Service Bridges from custom decision logic
llmInvoke configured LLM services with platform cost tracking
log / consoleWrite script logs visible from the Activity step details
The current Activity Plan script context is centered on the Task and its document families. Use prior step outputs through declarative step mappings, then pass the needed values into document metadata, task data, or downstream step configuration instead of assuming a generic inputs object exists in the script runtime.

Runtime Limits

LimitValue
Script timeout15 seconds
Document loads5 per script execution
Task reads / document family reads50 per namespace
Task mutations / document family mutations20 per namespace
Task/document mutation payload256 KB per call
Document changes are saved after the script completes. Modified KDDB documents are persisted as new content object versions with the SCRIPT_MODIFICATION transition type.

Task Namespace

Use tasks when a script needs to inspect or update human review work inside the current project.
MethodDescription
tasks.current()Load the current Task
tasks.parent()Load the current Task’s parent, if any
tasks.subTasks()List subtasks of the current Task
tasks.subTasksOf(taskId)List subtasks for another Task in the same project
tasks.get(taskId)Load a Task by ID
tasks.query(filter)Query Tasks by status, statusType, parentTaskId, templateSlug, and limit
tasks.lookupStatus(slug)Resolve a project Task status
tasks.listStatuses()List project Task statuses
tasks.setStatus(taskId, statusSlug)Change Task status and trigger Activity advancement when the status type is DONE
tasks.setProperties(taskId, props)Merge properties into Task data
tasks.setMetadata(taskId, metadata)Merge Task metadata
tasks.setTitle(taskId, title)Update Task title
tasks.setDescription(taskId, description)Update Task description
tasks.lock(taskId) / tasks.unlock(taskId)Lock or unlock a Task

Document Family Namespace

Use documents when a script needs to manage document family state without loading the full KDDB document.
MethodDescription
documents.taskFamilies()List document families attached to the current Task
documents.get(familyId)Load a document family summary
documents.getByPath(storeSlug, path)Find a document family by store and path
documents.query(filter)Query families by storeSlug, path, pathContains, label, mixin, status, featureSlug, locked, and limit
documents.contentObjects(familyId)List content object versions
documents.latestContentObject(familyId, contentType?)Get the latest content object, optionally DOCUMENT or NATIVE
documents.load(familyId)Load the KDDB document, equivalent to loadDocument(familyId)
documents.lookupStatus(slug) / documents.listStatuses()Resolve document statuses
documents.setMetadata(familyId, metadata)Merge metadata into the family
documents.addLabel(familyId, label) / documents.removeLabel(familyId, label)Manage family 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

Routing Example

This script loads the first Task document, inspects its text, and routes the Activity to review, rejection, or straight-through posting.
var family = families[0];
var doc = loadDocument(family.id);
var text = doc.getRootNode().getAllContent(" ", true).toLowerCase();

if (text.indexOf("duplicate invoice") !== -1) {
  log.warn("Duplicate invoice language detected");
  return { action: "reject" };
}

var invoice = doc.findFirstDataObjectByPath("invoice");
var amount = invoice ? Number(invoice.getFirstAttributeValue("total_amount")) : 0;

if (!amount || amount > 10000) {
  return { action: "review" };
}

return { action: "post" };

Updating Documents

Document changes made by a script are persisted after the step completes.
var family = families[0];
var doc = loadDocument(family.id);
doc.setMetadata("scriptStep", "tag-total-lines");
doc.addLabel("invoice-intake");

var root = doc.getRootNode();
var matches = root.select("//line[contains(@content, 'TOTAL')]");

for (var i = 0; i < matches.length; i++) {
  matches[i].tag("invoice/total-line", {
    confidence: 0.95,
    value: matches[i].getContent()
  });
}

return { action: "tagged" };
Do not call doc.close() in Activity Plan scripts. Kodexa persists document changes after the script completes.

Calling Service Bridges from a Script

Use serviceBridge.call() inside a SCRIPT step when the decision requires more than a single declarative bridge call.
var supplier = serviceBridge.call(
  "finance-reference-data",
  "lookup-supplier",
  { supplierId: task.data.supplierId }
);

if (!supplier || supplier.status !== "active") {
  log.warn("Supplier is not active:", task.data.supplierId);
  return { action: "review" };
}

var duplicate = serviceBridge.call(
  "finance-reference-data",
  "check-duplicate-invoice",
  {
    supplierId: task.data.supplierId,
    invoiceNumber: task.data.invoiceNumber
  }
);

return { action: duplicate && duplicate.found ? "review" : "post" };
Service bridge credentials stay in the platform. Scripts reference the configured bridge and endpoint; they do not handle secrets directly.

Shared Script Sidecars

Use scriptSidecars to pre-load reusable JavaScript helpers.
{
  "slug": "normalize-fields",
  "kind": "SCRIPT",
  "config": {
    "scriptSidecars": ["acme-finance/invoice-script-helpers"],
    "scriptActions": [{ "name": "normalized" }],
    "scriptBody": "normalizeInvoiceFields(families[0].id); return { action: 'normalized' };"
  }
}
Sidecars are useful for shared validation functions, string cleanup, request mapping, and common routing decisions. Keep sidecars small and version them like any other project resource.

Design Guidance

  • Declare every returned action in scriptActions.
  • Keep scripts focused on one decision or one transformation.
  • Prefer BRIDGE_CALL for a single external API call that operators should see as its own step.
  • Use logs at important decision points.
  • Make scripts idempotent when they can be retried.
  • Avoid long loops and repeated document loads.

Next Steps

Script API Reference

See the full JavaScript context, document helpers, LLM calls, and logging reference.

Service Bridge Steps

Learn when to model an external API call as its own Activity Plan step.