Factorial Code — CLI
The
CLI develops and tests processes locally and syncs them with
Factorial Code Cloud. For the platform model see
.
Command flow
When making and testing changes:
- (optional, first) — if the cloud may have changed, sync
down so you work with the latest version.
- Edit local files (processes, modules, variables, dependencies).
- — only when you created NEW resources (new process,
module, dependency, or variable). Skip for edits to existing code.
fcode dependencies:install
— if you changed
dependencies/package.json
or dependencies/requirements.txt
.
- — execute the process locally to test.
- — deploy to cloud when ready.
Pushing updates the
current (unversioned) code only: consumers pinned to the
alias — which webhook URLs and form embeds should always be — keep
running the released version until the alias moves. Releases (publishing a
workspace version and re-pointing
) happen separately, normally from the
web UI (see below).
Gotchas
- Never run before when the process (or other
resource) was just created — you'll hit "Local process not found".
- is only for NEW resources. For edits to existing process/module
code or variables, go straight to .
- (on /) overwrites the other side. Both commands fail
when local and cloud diverge; only use with explicit user
confirmation.
- Never edit . It holds the parent workspaces'
variables, is regenerated on pull, and skips it with a warning.
Overriding an inherited value means adding the key to .
Commands
Registers
new local resources with the CLI so they can be run or deployed.
Run after creating a new process/module/dependency/variable, before
/
.
Not needed after only editing existing resources.
fcode dependencies:install
Installs dependencies into the local workspace. Run after changing
dependencies/package.json
or
dependencies/requirements.txt
.
fcode run <process-slug> --parameters <filepath | json>
Executes a process locally for development/testing. Uses
/
and the given parameters (or the process's
by default). Shows logs, results, and errors.
sh
fcode run my-process --parameters '{"key": "value"}'
fcode run my-process --parameters ./params.json
fcode run my-process # default parameters.json
fcode run my-process --locale pt-BR # resolve fcode.i18n in that locale
Prerequisite: run
first if the resource was just created.
resolves
against the local
files exactly as the
cloud does — see
(including why a typo in the flag silently
resolves every key to itself).
Starts a local HTTP server (
, default
) that replicates the cloud
webhook environment and also serves the workspace's form schemas, so
webhook-triggered processes and forms can be exercised without deploying.
/
protect the
whole local server with basic
auth. Per-process webhook auth is separate, and enforced exactly as the cloud
does it: the server reads
from the process's
,
resolves
against
in
, and requires the named
variable's value in the configured header —
in
,
the raw value in any other header. Values come from the workspace variables in
the precedence every local run uses —
, overridden by
, overridden by
(below). Header lookup is
case-insensitive, but a repeated header is rejected rather than joined. A missing
header, a malformed bearer value, an undefined variable, a mismatch, or a
webhook whose workspace configuration is absent all get a
, with the
reason printed to the console.
Two things to watch locally:
- A secret variable pulls down as the placeholder, so local auth
only accepts until the real value is in .
- / consume the header, so they
can't be combined with a webhook expecting its credential there. The server
warns about this at startup.
Downloads the latest processes, modules, variables, and dependencies from the
cloud, overwriting local files to match. Run before starting work if others may
have changed cloud resources, or to discard local changes.
only with
user confirmation.
Uploads local changes to the cloud. Run after local changes (run
first only if you created new resources); recommended to
first.
only with user confirmation.
/ /
Sync the workspace-level settings in
on their own:
writes
the cloud settings into the file,
applies the file, and
reports whether they changed locally, in the cloud, or both. Plain
/
include them too, running them
last so a referenced error-handler
process slug resolves against processes that already exist.
/
Workspace versioning publishes a version of the
whole workspace: every
process and module the team owns gets a version with the same tag, and bare
module imports are pinned to it inside the published snapshots (model in
). Releases normally happen from the web UI (team
settings →
Versions tab) — these commands are the scriptable equivalent.
Don't create versions or move unless explicitly asked.
sh
fcode team:versions:create v1.0.0 --comment "First stable release"
fcode team:versions:list
fcode team:versions:delete v1.0.0 # cascades; asks confirmation unless --force
fcode team:aliases:set stable v1.0.0 # create or re-point; rollback = older tag
fcode team:aliases:list
fcode team:aliases:delete stable
- skips entities already carrying the exact tag and
reports a per-entity summary (created / skipped / failed — the version's
manifest), then pulls so the folders and refresh
locally. Re-running the same tag after a partial failure only publishes what
is still missing.
- cascades: every owned process/module version with
the tag is deleted, together with the aliases, executions, and schedules
referencing them.
- upserts — it creates the alias or re-points an existing
one on every owned entity that has the target tag published (entities without
it are skipped and reported). Re-pointing at an older tag is the
rollback: every consumer pinned to switches in one operation.
/
/
/
/
/
sync the workspace's translation files —
, plus the read-only, gitignored
i18n/<locale>.inherited.yaml
that
writes. Aggregate
/
/
include locales already. File format, inheritance model, and
the internationalization workflow in
.
Process metadata —
Each process folder holds
processes/<slug>/metadata.json
— the source of
truth for the process's name, description, tags, triggers, and settings. It
round-trips with
/
: edit the file and
to change these
settings in the cloud, no dashboard needed. Changes show as 🔺 modified in
.
| Field | Type | Meaning |
|---|
| string | Display name (defaults to the slug) |
| string, optional | Process description |
| string[] | Tags (defaults to ) |
| object, optional | Webhook trigger: (boolean) turns the process's webhook endpoint on; ( | | ) says how callers authenticate — public, inheriting the workspace from , or its own; ({ headerName?, variableKey }
, only with ) names the header and the team variable holding the expected token |
| object, optional | Form settings: (boolean) is the Forms flag (see ); ( | ) restricts who may open the form; marks the process's role in a marketplace app: , , , or |
json
{
"name": "Order sync",
"description": "Syncs Shopify orders into Factorial",
"tags": ["integration", "shopify"],
"webhook": {
"enabled": true,
"authMode": "CUSTOM",
"auth": { "variableKey": "SHOPIFY_WEBHOOK_TOKEN" }
},
"form": { "enabled": false }
}
A webhook that inherits the workspace configuration carries
"webhook": { "enabled": true, "authMode": "TEAM" }
, and a public one only
"webhook": { "enabled": true }
.
json
{
"name": "Connect your account",
"tags": ["setup"],
"form": { "enabled": true, "authMode": "FACTORIAL", "appRole": "INSTALL" }
}
Notes:
- stores only the variable name, never a token —
so the file is safe to commit. The variable doesn't have to exist yet; until it
does, every call to the webhook is rejected with . Both plain and secret
variables work.
- inherits from . When that
configuration is missing, the webhook rejects every call — it never reads as
public. Through MCP this matters: an agent can set but there is
no team-settings tool, so the configuration has to exist already (set it in
and ).
- defaults to , whose value must be
; any other header carries the raw variable value. Valid names
are RFC 7230 token characters, at most 64 of them, and , and the
prefix are rejected. Prefer the default: a bespoke header loses the
redaction proxies and log pipelines give . Use one only when the
sender can't set — Factorial's own webhook sender, which puts
its token in , is the case in point.
- , and are omitted when they
are , as is when it is , so a
plain public form carries only
"form": { "enabled": true }
. To lift protection
from a protected form or webhook, write explicitly —
omitting the field leaves the stored mode untouched, and sending without
is rejected.
- Omit unless the process belongs to a marketplace app.
- If is missing, scaffolds
{ "name": "<slug>", "tags": [] }
; invalid JSON falls back to those
defaults with a warning.
Calling a webhook — pin the version in the URL
The webhook endpoint is
https://code.factorialhr.com/platform/api/<team-slug>/webhooks/<process-slug>
.
Always pin the version with the
query parameter, pointing at the
alias — subscription systems rarely let you set request headers:
sh
curl -X POST "https://code.factorialhr.com/platform/api/<team-slug>/webhooks/<process-slug>?version_tag=stable"
- takes a version tag () or an alias. Use : it
always exists, and releases/rollbacks then happen by moving the alias — the
external system is never touched. It is equivalent to the
header and takes precedence over it. , and are
reserved names, stripped before the parameters reach the process (
selects the execution's language — see ).
- An unknown or malformed version does not fail the call. The process runs
its current version and the platform only logs a server-side warning — a typo
runs the current version silently. When a run behaves unexpectedly, check the
execution's version.
Team settings —
A singleton file at the workspace root holding team-level settings. Synced by
/
/
, and included in plain
/
(pushed last, so a referenced error-handler process
exists first).
| Field | Type | Meaning |
|---|
| string[] | Teams this workspace inherits processes, modules and variables from (direct parents only, max 5) |
| string, optional | Team timezone (e.g. for schedules) |
| object, optional | { "processSlug": "<slug>", "tag": null }
— process invoked when an execution errors; pins it to a version tag or alias ( = current version) |
| object, optional | { headerName?, variableKey }
— the configuration every webhook inherits |
| string, optional | The workspace's main language: the locale used when a caller names none, and the key-level fallback for untranslated keys (see ) |
| array, pull-only | Workspace versions: { tag, comment, createdAt }
|
| array, pull-only | Workspace aliases: |
json
{
"parentTeamSlugs": ["base-app"],
"zoneId": "Europe/Madrid",
"errorHandlerConfig": { "processSlug": "error-handler", "tag": null },
"webhookAuth": {
"headerName": "x-factorial-wh-challenge",
"variableKey": "FACTORIAL_CHALLENGE_TOKEN"
},
"versions": [
{ "tag": "v1.0.0", "comment": "First stable release", "createdAt": "2026-08-01T10:00:00" }
],
"aliases": [{ "name": "stable", "tag": "v1.0.0" }]
}
The error handler is referenced by
slug (not id) so
is
portable across teams; the CLI resolves it to the cloud id on push.
and
are
pull-only:
writes them for
visibility and git history, they are excluded from the content hash, and
strips them — editing them locally does nothing. All writes go
through the
/
commands or the web UI.
is one shared configuration for the whole workspace, so a token used
by several webhooks is named — and rotated — in one place. Two things about it:
- It is per-workspace and not inherited through . Auth is
resolved against the workspace addressed in the webhook URL, not the one that
owns the code, so an app inheriting a webhook process from a base app still
needs its own entry.
- Removing the object and pushing clears the cloud configuration, which makes
every webhook inheriting it reject all calls.
The three variables files
Team variables live in three
files at the workspace root:
| File | Holds | Synced |
|---|
| The variables this workspace owns | Committed; pushed and pulled |
| The variables inherited from parent workspaces () | Pull-only; gitignored (the CLI adds the entry) |
| Local-only overrides | Never pushed, never pulled |
Resolution order for a local run (highest wins), matching what the cloud
does:
→
→
.
Precedence is decided by which file
declares a key, not by its value — so
blanking a key in
overrides the inherited variable with an empty
string rather than falling through to the parent.
Overriding an inherited variable
Adding the key to is the override. From that point the CLI
treats it as this workspace's own variable:
shows it as new,
creates it here, and
offers it.
- Don't edit — it is regenerated on every pull, and
skips inherited variables with a warning. Editing one only warns.
- Don't copy a parent's variables into a child workspace to "make them
available" — they already resolve. Only add a key when this workspace genuinely
needs a different value. (Workspaces provisioned before inheritance existed may
still hold such copies, which now shadow the parent — including untouched
placeholders shadowing a secret that would otherwise resolve. Flag
those to the user rather than deleting them.)
- Deleting your override (removing the key from and pushing)
brings the parent's value back.
grows an
inherited column showing the source
workspace (
) when any variable is inherited; the column is hidden
otherwise. Model and web-UI behaviour in
; the runtime
behaviour in
/
.
The file names are settings (
,
inheritedVariablesFileName
,
) — assume the defaults
above unless the workspace says otherwise.
Variable sensitivity —
A workspace-root file mapping each variable to its sensitivity flag:
json
{
"ACME_API_KEY": { "isSensitive": true },
"ACME_BASE_URL": { "isSensitive": false }
}
- Create a sensitive variable with
fcode variables:add --sensitive
(then set
its value and push); the flag lands here.
- Variables created at runtime with are sensitive by
default — pass (JS) / (Python) for
plain config. See / .
- Sensitive values never leave the cloud: writes the placeholder
into — and into for an
inherited secret. Don't replace the placeholder in either file — put the real
value in for local runs. Remotely, an inherited secret's
real value is available to executions (see ); only the
local copy is masked.
- is immutable once pushed. Editing it in
is rejected on push (🚫 in ) — revert to
match remote.
Getting secret values for local runs
When a local run (
, a discovery script) needs a real secret value
that isn't in
yet, ask the user to provide it. If they
prefer not to share the value with the agent, ask them to add the
line to
themselves — local runs pick it up
without the value ever appearing in the conversation.
- values are never pushed. Remind the user to also
create those secret variables manually in the remote demo environment —
won't carry the values.
- : needed locally only — the remote environment
populates it automatically, so don't create it there. To obtain it, the user
completes the OAuth flow in the Factorial Code app details page, then copies
the generated token with the copy dropdown option in the OAuth Dev app, and
puts it in (or shares it, per their preference).
- Once obtained, never echo secret values back in output or logs.
Examples
Development cycle (new process):
sh
fcode add
fcode dependencies:install # if dependencies changed
fcode run shopify-order-sync --parameters '{"dateFrom":"2024-01-01","dateTo":"2024-01-31"}'
fcode push # no need to re-run `add` if nothing new was created
Deploy an existing, tested process:
sh
fcode push # `add` not needed — process already registered