Activepieces Access & Isolation
This page is the reference for how AgentLane connects to Activepieces, what “an isolated instance for a partner” actually means in each tenancy mode, which endpoints drive provisioning and who may call them, and the exact pipeline each setup runs.
For the operational walkthrough, see Admin: Provision a Partner. For why Activepieces is the engine at all, see Workflow Engine.
Who can reach Activepieces
AgentLane itself reaches Activepieces only from inside the container network. Whether anyone else can is a deployment choice, and it defaults to no.
- AgentLane always talks to Activepieces internally.
ACTIVEPIECES_BASE_URLis a server-to-server address the API container resolves on the Docker network —http://activepieces-app:80in production,http://localhost:8090in development. Every admin call and every webhook trigger goes over that address. It is never a public URL. - The editor UI can optionally be published, and is not by default. Setting
ACTIVEPIECES_DOMAINrecords a hostname; the public Traefik router is written only whenACTIVEPIECES_PUBLIC_UI=trueas well (deploy/scripts/deploy.sh). This is deliberately two switches rather than one: every deployment that runs Activepieces at all setsACTIVEPIECES_ENABLED=true, and that alone used to publish the editor.
Publishing the editor puts a shared automation engine’s admin login on the public internet. In
the default shared tenancy mode one project holds every partner’s flows, and Community Edition
cannot scope a human user to one project — so anyone who reaches that login holding the
platform-admin credential sees every tenant. If you turn it on, put an IP allowlist or basic auth
in front of it (deploy/nginx/conf.d/activepieces.conf.template). The original design kept it
internal-only, reached over an SSH tunnel.
- Partners and clients never get Activepieces UI access. The Portal does not proxy the
Activepieces editor. Instead,
GET /partners/:id/automation/workflows/:workflowId/viewerreturns a native, read-only view: the workflow’s template definition plus recent run history. - Every trigger originates from AgentLane.
AutomationWebhookResolvercomposes the absolute webhook URL against the internal base URL at trigger time, so inbound business events hit the AgentLane API first and are dispatched inward from there.
The read-only viewer is a deliberate substitute, not a limitation we haven’t gotten to. Activepieces Community Edition cannot scope a human user’s access to a single project — in shared tenancy, handing a partner the real Activepieces UI would expose every other partner’s flows.
What isolation a partner actually gets
AUTOMATION_TENANCY_MODE selects between two models. Neither gives a partner their own
Activepieces deployment — resolve_endpoint returns the same global ACTIVEPIECES_BASE_URL for
every partner in both modes.
shared (default) | project | |
|---|---|---|
| Activepieces deployment | One, shared by all partners | One, shared by all partners |
| Activepieces project | One, shared by all partners — the value of ACTIVEPIECES_PROJECT_ID | One project per partner, created on demand |
| Where flows live | All partners’ flows in the same project | Partner’s own project |
| Isolation boundary | AgentLane’s database, exclusively | AgentLane’s database, plus a project boundary |
| Name collisions prevented by | Namespaced deterministic externalIds | Namespaced externalIds + separate project |
deleteTenant on teardown | No-op (the shared project must survive) | Deletes the partner’s project |
In shared mode ensureTenant does not create anything — it returns the single configured
ACTIVEPIECES_PROJECT_ID, the platform admin’s own default project. Activepieces CE has no
“current user’s default project” concept, and every project-scoped endpoint requires an explicit
project ID, so this value must be set or provisioning fails immediately with a clear error.
In shared mode, AgentLane’s authorization checks are the only thing separating one partner’s
automation resources from another’s. There is no engine-level tenant boundary to fall back on.
Treat any change to partner scoping in the API as a tenant-isolation change.
project mode is technically supported and exercised by ensureTenant (list by display name,
create if absent), but shared remains the default. Before switching, run a capability matrix
against the exact candidate image — a project ID alone does not establish tenant isolation.
How AgentLane authenticates
Not with a static API key. There isn’t one to use.
Activepieces CE has no durable server-to-server API key: POST /v1/api-keys is gated behind
platform.plan.apiKeysEnabled, which defaults to false and requires a paid Enterprise license.
Confirmed live against the pinned activepieces/activepieces:0.82.0 image — the route 404s even
for the platform-admin user on an unlicensed instance.
The only Bearer token that authenticates /v1/projects, /v1/flows and /v1/app-connections on CE
is a user login JWT. So ActivepiecesSessionAuthService:
- Signs in via
POST /v1/authentication/sign-inusingACTIVEPIECES_ADMIN_EMAIL/ACTIVEPIECES_ADMIN_PASSWORD— a real Activepieces user created through that instance’s own first-run signup, which grants platform-admin rights to the first user automatically. - Caches the resulting JWT (roughly a 7-day lifetime on the pinned build).
- Refreshes it proactively 10 minutes ahead of expiry, and reactively whenever the HTTP client
sees a
401and callsinvalidate().
No static key is ever stored on the instance row: apiKeyEncrypted is written as null during
provisioning. These admin credentials are full platform-admin access to every partner’s automation
resources — keep them only in trusted infrastructure config, never in partner-visible settings.
Endpoint surface
All provisioning routes live under partners/:id/automation and are ADMIN-only, guarded by
RolesGuard. The one exception is the viewer, which also accepts the owning PARTNER.
| Method | Route | Role | Purpose |
|---|---|---|---|
GET | /partners/:id/automation | ADMIN | Instance snapshot — tenancy mode, project ID, base URL, status |
GET | /partners/:id/automation/attempts | ADMIN | Provisioning attempt history |
GET | /partners/:id/automation/steps | ADMIN | Per-step breakdown of an attempt (defaults to the latest) |
POST | /partners/:id/automation/provision | ADMIN | Run the provision pipeline. Body: { templateNames?, clientId? }. Returns 202 |
POST | /partners/:id/automation/reprovision | ADMIN | Body: { mode?: "repair" | "recreate", templateNames?, clientId?, refreshTemplates? }. Returns 202 |
DELETE | /partners/:id/automation | ADMIN | Deprovision. Destructive |
GET | /partners/:id/automation/workflows/:workflowId/viewer | ADMIN, owning PARTNER | Read-only template definition + recent runs |
GET | /partners/me/automation | ADMIN, PARTNER | The only provisioning route a partner may call. View-only, redacted |
provision, reprovision and the deprovision route each write an audit log entry
(automation_instance.provision_requested and siblings) recording the acting user.
What a partner is allowed to see
GET /partners/me/automation is deliberately redacted. A PARTNER caller never receives
baseUrl, projectId, or lastError — those are infrastructure detail and, in shared mode, would
describe resources shared with other partners. Instead the twelve internal steps collapse into four
coarse stages:
| Stage key | Label | Internal steps it covers |
|---|---|---|
preparing | Preparing | feature_gate, partner_lookup, claim_instance |
connecting | Connecting to your automation platform | resolve_endpoint, verify_connectivity, ensure_tenant |
configuring_access | Configuring access | resolve_credentials, bind_credentials |
installing_workflows | Installing workflows | import_flows, verify_flows, enable_flows, mark_ready |
The response carries errorPublic — a client-safe message — never the internal error. That split is
what lets a partner see a real “provisioning failed, here’s what to do” state without leaking
infrastructure shape.
The setup pipeline
Provisioning runs synchronously within the request and is recorded step by step by
AutomationProvisionStepRecorder. Each step is individually timed and records success, failure,
warnings, and both an internal and a client-safe error.
feature_gate
Refuses to proceed unless ACTIVEPIECES_ENABLED=true. This is the documented rollback switch —
setting it to false disables provisioning entirely.
partner_lookup
Loads the partner record and resolves the effective provider from Partner.automationProvider,
falling back to AUTOMATION_PROVIDER.
claim_instance
atomicClaim takes exclusive ownership of the instance row for this attempt ID. This is what stops
two concurrent provision requests from both proceeding.
resolve_endpoint
Returns ACTIVEPIECES_BASE_URL and writes it to the instance row. The same value for every
partner, in both tenancy modes.
verify_connectivity
Health-checks the platform and fails the attempt unless the state is healthy — so a network or
auth problem surfaces here rather than halfway through creating resources.
ensure_tenant
shared: returns the configured ACTIVEPIECES_PROJECT_ID, creating nothing.
project: lists projects by display name and creates one if absent. The resolved project ID is
written to the instance row.
resolve_credentials
Pre-flight only. Fails fast on an obviously-missing credential before touching Activepieces at all. Authoritative resolution happens in the next step.
bind_credentials
For each required connection in each template, upserts an Activepieces app connection keyed by a
deterministic externalId. Idempotent by contract: retrying must reuse the existing provider
credential, never create a duplicate. An unresolved credential fails the whole attempt rather
than enabling a flow with a silent gap.
import_flows
Creates each catalog flow, then builds its action chain. Idempotent on re-run: if a providerFlowId
is already recorded for the workflow, it calls updateFlow against the existing flow instead of
importing a second copy.
Every imported flow also gets a terminal report step appended automatically, posting the run’s
outcome to /executions/webhook. Without it an execution never reported completion: the row stayed
RUNNING until the timeout job marked it FAILED, so a successful run showed as failed and its
usage was never metered. It is marked continue-on-failure, because the conversation saga triggers
these same flows without an executionId and reports through a different endpoint — that rejected
report must not fail an otherwise good run.
The step uses @activepieces/piece-http 0.11.19, which sends only body.data (with
body_type: "json"). Fields placed directly under body go out as an empty request, so the step
and every POST step in the catalog templates wrap their JSON in data.
Flows provisioned before this existed do not have the step, and flows provisioned before the
body.data fix send it with an empty body. Both are corrected on the next
Reprovision → repair, which rebuilds the flow in place.
verify_flows
Confirms each imported flow exists and matches the template.
enable_flows
Publishes and activates the flows.
mark_ready
Sets the instance status to ready and stamps provisionedAt.
If any step throws, the instance is marked failed with the recorded error. Missing credentials
produce a targeted message naming the offending steps: Could not activate: missing credential for step(s) ….
Repair, recreate and teardown
POST .../reprovision takes a mode:
repair (default)
Re-runs six steps against the existing resources, with no teardown:
feature_gate → resolve_credentials → bind_credentials → update_flows → verify_flows → enable_flows
Use this when credentials were rotated or a flow definition changed.
Repair re-reads every requested template from the global catalog and replaces the partner’s
stored copy (refreshTemplates defaults to true for repair). Pass refreshTemplates: false to
keep the stored copies; a stored copy is still replaced whenever the catalog holds a newer
templateVersion. Provisioning and recreate apply that version rule too. The import_flows
step’s metadata lists the version imported for each template.
Deprovision is destructive and irreversible. Prefer recreate when the goal is simply “start over”.
Instance states
unprovisioned ──Provision──► provisioning ──► ready
│
└──failure──► failed ──Provision (retry)──► provisioning
ready ──Reprovision (repair)───► provisioning (rebuilds in place, no teardown)
ready ──Reprovision (recreate)─► provisioning (full teardown, then full provision)
ready ──Deprovision────────────► unprovisioned (permanent)There is no separate “retry” action — provisioning a failed instance re-runs the same pipeline.
A run that crashes mid-flight would otherwise leave the row stuck in provisioning or
deprovisioning forever, so AutomationProvisionTimeoutJob reaps anything older than
AUTOMATION_PROVISION_TIMEOUT_MINUTES (default 15).
Configuration
| Variable | Default | Notes |
|---|---|---|
ACTIVEPIECES_ENABLED | false | Master switch. Must be true to provision. Only the exact string true enables it |
ACTIVEPIECES_BASE_URL | — | Internal container address. Never a public URL, no https:// requirement |
ACTIVEPIECES_ADMIN_EMAIL | — | Platform-admin login used for session auth |
ACTIVEPIECES_ADMIN_PASSWORD | — | As above. Full platform-admin access — trusted infrastructure only |
ACTIVEPIECES_PROJECT_ID | — | Required in shared mode. Provisioning fails without it |
ACTIVEPIECES_EDITION | ce | Informational only today |
AUTOMATION_PROVIDER | activepieces | Global default when Partner.automationProvider is unset |
AUTOMATION_TENANCY_MODE | shared | shared or project |
AUTOMATION_CALLBACK_SECRET | — | Signs execution callbacks back into AgentLane, and derives each agent’s public ingest token |
AUTOMATION_INTERNAL_API_URL | http://api:4000 | Internal address a flow calls AgentLane back on. Never public |
AUTOMATION_HEALTH_TIMEOUT_MS | 5000 | verify_connectivity budget |
AUTOMATION_PROVISION_TIMEOUT_MINUTES | 15 | Stuck-attempt reaper threshold |
Rolling back to a non-Activepieces posture is ACTIVEPIECES_ENABLED=false, which trips
feature_gate on every subsequent attempt.