Process sandbox tf API reference

Document information
  • Canonical URL: /docs/02_automations-and-processflow/41_process-sandbox-tf-api
  • Version: 2026-06-17
  • Tags: processflow, sandbox, reference, tf

JavaScript and TypeScript process steps run in the new sandbox (sandbox_mode: new-sandbox). The platform injects a namespaced helper object, tf, alongside the legacy root globals (tenantDb, integration, api, …) used by older PHP-style snippets.

Use tf.* in new steps. Root globals remain available for migrated snippets and are mirrored on tf where noted below.

Process sandbox tf API showing capability-gated services, context helpers, and legacy global aliases on one execution surface.

Two ways to call the same services

StyleExampleWhen to use
tf namespacedtf.log("info", "started"), tf.connector.execute(...), tf.datapool.query(...)Preferred for new TypeScript/JavaScript steps
Root globalslog_message("info", "started"), integration.executeSync(...), datapool.query(...)Migrated PHP snippets; same objects are aliased on tf

Both styles respect the same sandbox capabilities. If a capability is disabled, the matching method throws a clear error (for example Connector capability is not enabled).

Sandbox capabilities

Each step run carries a capability list. Methods that need a capability are blocked when that capability is not granted.

CapabilityUnlocks
contexttf.context.get, tf.context.set, tf.context.snapshot
loggingtf.log, log_message
data-accesstf.dataAccess, tf.tenantDb, tenantDb, SQL helpers
connectortf.connector, tf.integration (execute, executeSync, executeAsync, …)
apitf.api, tf.datapool, tf.email, tf.notification, OCR, document review
llmtf.llm (callLLM, complete)
filetf.files, tenant file helpers (read, write, file_get_contents, …)
keystoretf.keystore (get, set, list)

When sandbox_capabilities is omitted from a run, the platform applies a default set controlled by the worker setting SANDBOX_LEGACY_DEFAULTS (typically the full legacy helper surface: context, logging, file, data-access, connector, keystore, api, llm).

To restrict a step deliberately, pass an explicit sandbox_capabilities array when executing the process (see ProcessFlow API documentation).

HTTP-backed helpers (api, datapool, integration.executeSync, …) also require internal HTTP context on the execution payload: app_url, user_id, execution_auth_key, and execution_auth_source_id. Without that context you see internal HTTP is not configured. Normal process executions set these automatically; custom test harnesses must supply them.

Core tf methods

These methods exist only on tf (there is no root-global alias).

tf.capabilities

Array of enabled capability names for the current run. Use it to branch when a step is deployed with different profiles:

if (!tf.capabilities.includes("connector")) {
  return { success: false, error: { code: "NO_CONNECTOR", message: "connector capability is disabled" } };
}

tf.log(level, message?, context?)

Writes a structured line to the step execution log. Requires the logging capability.

tf.log("info", "Normalizing order", { customer_id: process_input.customer_id });
tf.log("Order received"); // level defaults to info

log_message(level, message, context?) is the legacy alias with the same behavior.

tf.context

Read and write values in the mutable execution context bag. Requires the context capability.

MethodDescription
tf.context.get(path)Read a dotted path (for example "order.id")
tf.context.set(path, value)Write a value; throws if the path is invalid
tf.context.snapshot()Returns a shallow copy of the full context object
const prior = tf.context.get("enrichment.vendor");
tf.context.set("enrichment.vendor", "acme");
const all = tf.context.snapshot();

tf.utils

Small deterministic helpers that do not require extra capabilities:

MethodDescription
tf.utils.nowIso()Current UTC timestamp as ISO-8601 string
tf.utils.sha256(value)SHA-256 hex digest of a string or JSON-serialized value
tf.utils.hmacSha256(message, secret)HMAC-SHA256 hex digest
tf.utils.deterministicId(value)Stable hash-based ID from arbitrary input
const signature = tf.utils.hmacSha256(JSON.stringify(process_input), keystore.get("webhook_secret"));
const id = tf.utils.deterministicId({ tenant_id, customer_id: process_input.customer_id });

tf.dataAccess (read-only)

A read-only subset of tenant database access. Requires data-access. For inserts and updates, use tf.tenantDb instead.

MethodDescription
tf.dataAccess.getRecord(collection, id)Fetch one record by ID
tf.dataAccess.findRecords(collection, filter?)Query records with an optional filter object
const row = await tf.dataAccess.getRecord("Customers", process_input.customer_id);
const open = await tf.dataAccess.findRecords("Orders", { status: "open" });

tf.connector

Execute a configured integration connector synchronously. Requires connector. This is the recommended entry point for connector calls in new code.

const result = await tf.connector.execute(
  "slack-api-v1-0-0",
  "post_message",
  { channel: "#alerts", text: "Order processed" }
);

if (!result?.success) {
  return { success: false, error: { code: "CONNECTOR_ERROR", message: String(result?.error ?? "failed") } };
}

return { success: true, data: result.result ?? result.data };

Arguments: (integrationId, operation, payload?). The operation can also be supplied inside payload.operation when migrating older snippets.

See Integration connector usage for retries, batching, and response normalization patterns.

Services aliased on tf

These root globals are copied onto tf when available (for example tf.tenantDb === tenantDb).

Root globaltf aliasCapabilityNotes
tenantDbtf.tenantDbdata-accessquery, getOne, insert, update, delete, getRecord, findRecords
integrationtf.integrationconnectorFull async queue API (below)
connectorstf.connectorsconnectorDeprecated alias of integration
apitf.apiapiAuthenticated internal HTTP (get, post)
datapooltf.datapoolapiDataPool CRUD, schemas, timeseries, virtual tables
llmtf.llmllmcallLLM / complete; chat is not supported
emailtf.emailapisend
notificationtf.notificationapisend, dispatch, update, remove
jwttf.jwtdecodePayload, verify (stack-issued tokens only)
filestf.filesfileExecution-scoped file service; null when disabled
keystoretf.keystorekeystoreProcess-scoped secrets; null when disabled
pdfOcr / imageOcrtf.pdfOcr / tf.imageOcrapiextractText
documentReviewtf.documentReviewapicreateReviewFromProcessFlow
dbtf.dbdata-access/root tenant only — raw MySQL handle

tf.integration (queue-backed connectors)

tf.connector.execute is the simple synchronous path. tf.integration exposes the full integration worker API:

MethodDescription
execute / executeSyncRun connector synchronously (same backend as tf.connector.execute)
executeAsyncEnqueue to the integration worker; returns execution_id
getStatus(executionId)Poll queue status
getResult(executionId)Fetch completed output
cancel(executionId)Cancel a queued run
getPendingCount()Tenant integration queue depth
const executionId = await tf.integration.executeAsync("restapi-generic-v1-0-0", {
  operation: "send",
  method: "POST",
  endpoint: "/hooks/acme",
  body: process_input,
});

const status = await tf.integration.getStatus(executionId);
if (status.status === "completed") {
  const output = await tf.integration.getResult(executionId);
  return { success: true, data: output?.result ?? null };
}

tf.tenantDb (read/write SQL)

const row = await tf.tenantDb.getOne(
  "SELECT id, email FROM Users WHERE id = ?",
  [process_input.user_id]
);

await tf.tenantDb.insert("AuditLog", {
  action: "order_created",
  entity_id: process_input.order_id,
  created_at: tf.utils.nowIso(),
});

Use parameterized queries. Avoid string concatenation for user-controlled values. See ProcessFlow database guide.

tf.datapool

const schemas = await tf.datapool.listSchemas();
const hits = await tf.datapool.query("orders", "status:open", []);
await tf.datapool.insert("orders", { order_id: "O-100", status: "open" });
const builder = tf.datapool.table("orders").sort("created_at", "desc").limit(10);
const page = await builder.get();

See DataPool user guide.

tf.llm

const out = await tf.llm.callLLM("Summarize this ticket in three bullet points.", {
  model: "gpt-4o-mini",
  temperature: 0.2,
});
return { success: true, data: { summary: out.data?.text ?? out.text } };

llm.chat is blocked at save time and runtime. Use callLLM or complete.

/root only: process_db and tf.db

Platform maintenance steps running under tenant /root receive process_db — a PDO-shaped MySQL handle for global tables and ProcessStepLogs. tf.db and tenantDb.getRawConnection() expose the same handle in that context only. Tenant-scoped steps should use tf.tenantDb, not raw handles.

Complete step example

This pattern combines input validation, logging, connector execution, and a structured return:

type StepInput = { integration_id?: string; payload?: Record<string, unknown> };

async function run(input: StepInput): Promise<Record<string, unknown>> {
  const integrationId = String(input.integration_id ?? "").trim();
  if (!integrationId) {
    return { success: false, error: { code: "VALIDATION_ERROR", message: "integration_id is required" } };
  }

  tf.log("info", "Calling connector", { integration_id: integrationId, tenant_id });

  try {
    const result = await tf.connector.execute(
      integrationId,
      String(input.payload?.operation ?? "default"),
      input.payload ?? {}
    );

    return {
      success: true,
      data: {
        integration_id: integrationId,
        output: (result as { result?: unknown })?.result ?? result,
      },
    };
  } catch (err) {
    tf.log("error", "Connector call failed", { message: String(err) });
    return { success: false, error: { code: "CONNECTOR_ERROR", message: String(err) } };
  }
}

return run(process_input as StepInput);

Authoring guidelines

  1. Prefer tf.* over bare globals so capability boundaries are obvious in new code.
  2. Check capabilities when a step may run with restricted profiles (tf.capabilities.includes(...)).
  3. Return structured objects{ success, data } or { success: false, error: { code, message } } — not thrown exceptions for business failures.
  4. Use tf.connector.execute for simple connector calls; use tf.integration.executeAsync when the parent process should not wait for the external system.
  5. Never log execution_auth_key or keystore values.
  6. Do not use eval, dynamic import, raw database drivers, or direct fetch — these are blocked at save time. See Sandbox guardrails.
  7. PHP steps (language: php) still run through the legacy CLI executor; the tf object applies to JavaScript and TypeScript steps only.

Troubleshooting

SymptomLikely causeWhat to do
… capability is not enabledStep profile omits the required capabilityAdd capability to sandbox_capabilities or use a less restricted profile
internal HTTP is not configuredMissing app_url / auth context in a custom testRun through normal process execution or set execution auth fields
llm.chat rejected at saveUnsupported APIUse tf.llm.callLLM
tf.keystore is nullkeystore capability offEnable capability; store secrets in keystore, not source code
Connector timeoutLong external callUse tf.integration.executeAsync and poll getStatus

See also