FlowTruxFlowTrux/Docs
Docsapps

JavaScript API

flowtrux.js - run workflows, submit forms, read the latest result and call declared hosts from an app's own code.

Every app can load the FlowTrux runtime, a small script with no dependencies. The starter templates already include it; the assistant uses it when it writes the app. This page is for authors who edit an app's code by hand.

<script src="/apps-runtime/v1/flowtrux.js"></script>
<script src="app.js"></script>

Your own code goes in files (app.js above) - inline scripts and on…= attributes do not run in an app.

All functions return promises. A failed call rejects with { code, message }; the message is safe to show a visitor.

FlowTrux.app

{ id, slug } of the current app.

FlowTrux.run(workflowId, fields, { wait })

Starts a workflow whose App trigger names this app and passes fields as trigger.data.fields.

const run = await FlowTrux.run("WORKFLOW_ID", { vehicle: "AB-123" });
// → { executionId, status, link }
  • workflowId is the workflow's id (shown in the workflow's address, and in the card when the assistant creates it).
  • fields is a flat object of simple values (text, numbers, booleans), up to 50 keys.
  • With wait: true (the default) the call waits a short while for the run to finish; otherwise it returns at once. Either way, pass link to FlowTrux.result.

Polls a run until it completes or fails.

const res = await FlowTrux.result(run.link, { onUpdate: (r) => showStatus(r.status) });
if (res.status === "COMPLETED") render(res.result);

result is the output of the run's last completed step. A failed run returns status: "FAILED" with a generic error. The link is valid for 24 hours.

FlowTrux.submit(fieldsOrFormData, { wait, workflowRef })

Sends a submission to an app that declares fields (see Forms & Submissions). It is checked against the declared fields, stored in the Submissions tab, and handed to every workflow bound to the app.

const answer = await FlowTrux.submit(new FormData(formEl), { wait: true });
// → { submissionId, started, link?, executionId?, status? }
  • A FormData may carry files; a plain object is sent as JSON.
  • wait: true runs the answering workflow (the form's Result workflow, or workflowRef) and returns its link for FlowTrux.result.
  • validation_failed carries errors: [{ field, message }].

FlowTrux.form.bind(formEl, { wait, workflowRef, onSuccess, onError })

The easiest way to wire a <form>: it submits with FlowTrux.submit and handles the states for you.

  • Sets data-state="submitting" | "success" | "error" on the form.
  • Writes field errors into elements marked [data-error-for="<name>"] and marks the control data-invalid.
  • Writes a general error into [data-flowtrux-form-error].
  • On success calls onSuccess(result), or, without it, hides the form and shows [data-flowtrux-success].

Returns a function that unbinds the form.

FlowTrux.latest()

Reads the latest result of the app's source workflow (Settings > Source workflow).

const { executedAt, data } = await FlowTrux.latest();
if (!executedAt) showEmpty(); else render(data);

data is what the page may read: the names listed under What the page may read, or the output of the source run's last step.

FlowTrux.autoRefresh(fn, seconds, { immediate })

Calls fn(latest) now and then every seconds (10–3600). It pauses while the tab is hidden and refreshes when it is shown again; an error skips one tick. Returns a stop() function. Pass { immediate: false } when the page has already called latest() itself.

FlowTrux.fetch(url, init)

Calls an outside service that does not accept requests from browsers, on behalf of the visitor - for example with a token the visitor typed or got by signing in to that service. It works like the browser's fetch and resolves to a normal Response with the service's status and body.

try {
  const res = await FlowTrux.fetch("https://api.example.com/v1/items", {
    headers: { Authorization: "Bearer " + visitorToken },
  });
  const items = await res.json();
} catch (err) {
  if (err.code === "host_not_declared") console.warn("Declare", err.host, "in Settings");
}
  • Only hosts listed in Settings > External hosts can be called, over https.
  • Request bodies are text: a string, URLSearchParams, FormData without files, or a plain object (sent as JSON).
  • It rejects with an Error whose code is one of host_not_declared (with err.host), response_too_large (with err.limitBytes), upstream_timeout, upstream_failed, blocked_url, rate_limited, invalid_request, request_too_large, unsupported_media_type, not_found. A service's own error (a 401 or 500) still resolves as a Response - check res.ok.

Use FlowTrux.fetch for the visitor's own accounts. For your organization's data and connected accounts, use a workflow and FlowTrux.run - nothing of your organization's is added to a FlowTrux.fetch request.

Error codes

Runtime calls reject with one of: not_found, invalid_body, validation_failed, not_bound (the workflow's App trigger does not name this app), run_limit (the app's runs-per-hour limit), execution_limit (your plan's monthly executions), busy, closed (the form is closed), payload_too_large, no_source (no source workflow), result_too_large, failed, timeout, network.

Limits you can set

In Settings:

SettingDefaultMeaning
Runs per hour60Runs and submissions of this app per hour, all visitors together (1–10 000).
Reuse an identical result (minutes, 0 = off)0Return the earlier result when the same input arrives again within this time.
Calls to declared hosts per minute120FlowTrux.fetch, all visitors together (10–1000).
Largest answer from a declared host (MB)10Up to 25. "Ask the service for smaller pages instead of raising this."

Runs started from an app count toward your plan's monthly executions like any other run.

External hosts and libraries

  • Settings > External hosts - "The app's browser code may call only its own address and these hosts. Bare hostnames, e.g. api.open-meteo.com." A subdomain is a different host and must be listed separately.
  • Libraries in the app - libraries the assistant added (for example a chart or map library) are copied into the app's own files, so the app loads nothing from outside CDNs.
  • Images may come from any https address.