Skip to main content
All commands also accept the universal flags (--json, --quiet, --schema, and --dry-run on mutating commands) - see the overview.

Account

engini login

Store an Engini API key.
Without --api-key, the CLI offers to open the key-creation page and then prompts for the key. Input is masked (one * per character) and confirmed on success - ✓ Received 44 characters — eng_…wxyz - so you can tell a paste arrived, and arrived whole. A key must start with eng_. An empty, malformed, or line-broken value is rejected on both entry paths: the prompt re-asks up to three times, --api-key fails immediately, and neither writes anything. Ctrl-C or Ctrl-D aborts. Every rejection exits 2 and leaves your existing credentials untouched. Prompting needs a terminal on both stdin and stdout. If stdin is piped - CI, a wrapper script, engini login < /dev/null - pass --api-key; the CLI exits 2 rather than prompting into a stream nobody is typing into.

engini logout

Clear the stored credentials. Prints {"logged_out": true} (or "noop": true when nothing was stored).

engini whoami

Report the authenticated identity: full_name, email, current_company, role, user_type (apiuser for an API key, user for a JWT).

Tools

engini tools list

engini tools get <tool_id>

Full canonical detail for one tool - including input_schema, output_schema, and the supports_filters / supports_sort / supports_top_offset capability flags. (supports_top_offset means top and offset together; a tool can take SelectTop alone - the input_schema is the ground truth.)

engini tools call <tool_id>

Execute a tool against a connection.
Connection precedence: --connection > --toolset > the application’s default. Default output envelope: {output, history_id, execution_info}; results larger than --max-bytes are stored locally and replaced by a handle.

engini tools result [handle]

Drill into a stored (sandboxed) result.

Applications

engini applications list

--search <query>, --available (only apps you can use), --limit <n>.

engini applications get <slug>

Adds supports_object_selection, help_url, and authentication_methods - how you discover --auth <id> and credential field names for connecting.

Connect

engini connect [<app>]

The guided way to create a connection - interactive on a TTY, agent-resumable when piped (see the machine contract).
Flow: discover auth methods → collect credentials (or run OAuth) → create → check → refresh objects → select objects → {"connection_id", "is_alive", "objects_selected"}. A field with no static list of legal values may still have a dynamically-discovered one after an OAuth sign-in (e.g. Salesforce’s ApiVersionUrl) - a numbered picker in a TTY, or --field <name>=<value> validated against the discovered set (exit 2 on a value that isn’t one of them). See Field options in need:"fields" and need:"auth".

Connections

engini connections list | get

--kind filters on Regular, Key, or MCPClient (case-insensitive). Key is exactly what the Connections page’s Keys tab shows - see Keys vs Integrations. MCPClient is accepted but returns nothing: this endpoint doesn’t serve that kind. OPA is rejected (exit 5) - on-prem agents are not connections on this surface; use engini opa. Every row carries a kind field. Credential fields and metadata are deliberately omitted from get - they can carry secrets.

engini connections create <application>

Direct-credential connections (--auth <id>, --name <name>, repeatable --field key=value). OAuth2 apps use engini connect instead.

engini connections update <connection_id>

Change a connection’s name, description, credential fields, or metadata. Anything you don’t pass is left unchanged.
--oauth-state <state> re-authenticates an OAuth2 connection in place: the server derives fresh credentials from that sign-in’s record and applies them to the existing connection. Passing it alone - with no --field, --name, or --metadata - is still a valid edit, and is the only way to rotate an OAuth credential without minting a new connection id and re-pointing every workflow that references it.
--field and --metadata REPLACE the entire map - they do not merge. Passing one --field clears every other field on the connection. Read the current values with engini connections get <id> and pass them all, or preview with --dry-run first (secrets are masked in the preview).
The --dry-run output names the risk explicitly, so you can see what you are about to drop before you drop it:

engini connections replace --workflow <id> --from <id> --to <id>

Point one workflow at a different connection.
This is workflow-scoped, not a global swap: only workflow 903’s activities are rewired. Every other workflow using connection 481 is untouched. All three ids are required and named rather than positional - three same-typed integers are easy to transpose, and swapping --from and --to would point the workflow at the wrong credential. Fails with exit 5 if an activity cannot be remapped, i.e. the target connection doesn’t expose the activity or object the workflow needs.

engini connections defaults | set-default | clear-default

The default connection is what a tool call resolves to when it doesn’t name one.
The asymmetry is deliberate: set-default identifies the connection you want promoted, while clear-default identifies the application whose default you want removed - the connection is looked up for you.

engini connections delete <connection_id>

Prompts on a TTY; requires --force non-interactively.

engini connections check <connection_id>

Liveness test. A conclusive failure exits 1; an inconclusive check reports is_alive: null and exits 0.

engini connections refresh <connection_id>

Async object refresh - waits by default (--no-wait to return immediately, --timeout default 300s). A failed refresh exits 1; a timeout exits 124.

engini connections objects | select-objects <connection_id>

engini connections sign-in-url | oauth-complete <application>

The OAuth primitives behind connect:

Toolsets

Manage toolsets - named bundles that pin which connection each application uses and which tools are permitted.

engini toolsets list | get

engini toolsets create

Two ways to express the bindings, mutually exclusive:

engini toolsets update <toolset_id>

Same options as create (with --name as a rename). connections and workflows are replaced wholesale, not merged - send the full intended set. --dry-run fetches the current toolset and shows exactly what would be added, removed, or changed in scope before anything is written:

engini toolsets delete <toolset_id>

Prompts on a TTY; requires --force non-interactively.

MCP servers

Inspect and configure the MCP servers your account exposes - the endpoints an MCP client points at (see MCP client setup). Not the MCPClient connection kind, which is Engini consuming someone else’s MCP server. MCP servers are account-wide, not per-user: an Engini API key sees every server on the account.
There is no engini mcp create and no engini mcp delete - deliberately, unlike toolsets, which have both. Creating and destroying an MCP server stays in the engini.io UI, which is where the server’s SSE URL is issued and where its anonymous-access setting is chosen (is_anonymous_allowed is read-only over the API). The CLI maintains servers that already exist.To take a server out of service without destroying it, use engini mcp deactivate. It is reversible, and the server stays listed.
A supplied connections or workflows list replaces the whole set - it does not merge.The CLI does the read-modify-write for you. connections add|rm, tools set, and workflows add|rm each fetch the server first, apply your change to its current set, and send the complete result - which is why the surface is add/rm rather than one --connections flag.Calling PATCH /v1/mcpservers/{mcpServerToken} yourself is not the same. Whatever list you send becomes the entire set, so a body carrying one connection drops every other connection on that server. Read the server, modify the list you got back, and send all of it. Omitting connections (or workflows) entirely leaves that set unchanged - that is how activate/deactivate avoid touching bindings at all.Read-modify-write is last-writer-wins: the API has no ETag/If-Match, so two concurrent edits can lose one. --dry-run prints the exact resulting set before you commit to it.
Treat mcp_server_token as secret-equivalent. It is the {server-token} component of the server’s SSE URL - https://mcp.engini.io/{server-token}/sse - so it identifies a live endpoint, not just a row. Keep it out of shared logs, CI output, screenshots, and tickets, and redact it the way you would an API key.It is not a bearer credential on its own: an MCP client still authenticates through the OAuth 2.1 flow before it can list or call anything. But the token names a live endpoint, so publishing the token publishes the endpoint. The CLI never echoes an unknown token back in an error message.

engini mcp list

Every MCP server on the account. Deactivated servers are included, reporting is_active: false - they are not hidden and not deleted.
Each entry carries mcp_server_token, name, is_active, is_anonymous_allowed, connections[], workflows[], and created_date. Pagination is handled for you.

engini mcp get <mcp_server_token>

Full detail for one server: the list summary plus the SSE url and the audit fields updated_date, created_by_account_user_id, and updated_by_account_user_id.
An unknown token exits 4 with Unknown MCP server token. - the token itself is not echoed back.

engini mcp activate | deactivate <mcp_server_token>

Turn a server on or off. Both send only the is_active scalar, so no other field is read or rewritten and there is nothing to race with. Deactivating is not a delete. The server keeps its token, its connections, and its workflows; it still appears in list and get; and activate puts it straight back. There is no confirmation prompt and no --force.

engini mcp connections add <mcp_server_token>

Expose a connection on the server, or re-scope one that is already exposed. Existing connections are preserved; an entry with the same connection id is replaced.
The id must be a plain decimal integer. --connection 0x10 or --connection 1e3 exits 2 rather than binding a connection you never typed.

engini mcp connections rm <mcp_server_token>

Stop exposing connections, leaving the rest in place.
Removing a connection that is not on the server exits 4. It does not succeed silently - a “removed” that left the binding exposed would be worse than an error.

engini mcp tools list <mcp_server_token>

The tools a connection makes available to this server - the candidates for its allow-list.

engini mcp tools set <mcp_server_token>

Replace the tool allow-list for one connection. Every other connection on the server is left untouched.
--tool and --all are mutually exclusive, and exactly one of them is required - either mistake exits 2. --all sends an empty allow-list, which the API reads as “every tool of the application”; that is why it is a separate flag rather than --tool with no value. The connection must already be on the server, otherwise exit 4 tells you to run engini mcp connections add first.

engini mcp workflows add <mcp_server_token>

Expose workflows as MCP tools. Existing workflows are preserved; an entry with the same workflow token is replaced.
A workflow is identified here by its workflow token; the wire field is workflowToken, not workflowId. Omit the overrides and the tool falls back to the workflow’s own slug and description. --tool-name/--tool-description apply to a single --workflow. Passing either alongside two or more workflows exits 2: one name across several workflows would collide on the server’s unique tool name, and the CLI refuses rather than picking a winner.

engini mcp workflows rm <mcp_server_token>

Stop exposing workflows, leaving the rest in place.
Removing a workflow that is not exposed on the server exits 4.

On-prem agents

Provision, inspect, configure, and credential your On-prem agents - the Engini component a customer installs behind their firewall to reach systems the cloud cannot: SQL Server, Oracle, Priority ERP, file shares. Agents are not connections on this surface. engini connections list never shows them, --kind OPA is rejected, and engini connections get <agent_id> is a 404. Everything about an agent lives under engini opa.
An agent is polled, never pushed to. It polls Engini for work on its own schedule; Engini never opens a connection to it. So a command that “talks to the agent” is really waiting for the agent’s next poll, and an agent that has not polled recently is unreachable regardless of what the API does.That is why update reports applied_to_agent rather than failing, and why logs needs the agent Online. A freshly created agent is WaitingForConnection until it polls for the first time.
The token is a credential. It is what the agent presents to authenticate. engini opa create prints it - that is the command’s job, and it means the token lands in shell history and CI logs. Pipe it to a secret store, or fetch it later with engini opa token get instead.Rotating it stops the running agent. The old token is invalid the instant token rotate returns; the agent is down until someone reconfigures it with the new one. The command confirms on a TTY and requires --force otherwise.

engini opa list

Every on-prem agent on the account. Pagination is handled for you.
Each entry carries agent_id, name, status, version (what the agent last reported; null until it first connects), and last_seen_at (its last heartbeat; null until it first connects).

engini opa get <agent_id>

The list summary plus description, settings (parallel_tasks, pull_period_seconds, log_level), created_date, and updated_date.
An unknown id exits 4. So does an id that belongs to another account, or one that is a regular connection rather than an agent - the API answers 404 to all three on purpose, so the endpoint cannot be used to probe which ids exist.

engini opa create --name <name>

Register a new agent. Prints its token.
The new agent starts as WaitingForConnection. Install the agent on its host with the token from the output; it turns Online on its first poll. If the account has more than one on-prem-agent application (rare), create exits 5 with a message listing the candidate slugs - pass one back with --application-slug. That error is the only place to learn them: engini applications list does not show agent applications.

engini opa update <agent_id>

Change an agent’s name, description, or runtime settings. Takes the same --name, --description, --parallel-tasks, --pull-period, and --log-level flags as create. At least one is required (exit 2 otherwise). Merges, does not replace. A flag you omit leaves that field exactly as it was - unlike engini connections update, whose --field replaces the whole map. --dry-run shows precisely what will be sent.
The result carries two fields the other groups do not have:
  • applied_to_agent - true if the running agent acknowledged the change; false if the change is stored but not yet delivered because the agent was not Online. false is not an error and does not exit non-zero. The agent applies the stored settings when it next connects; there is nothing to retry.
  • dispatched_task_ids - the cloud-task ids handed to the agent, empty when nothing was dispatched.
Settings are range-checked by the server, not the CLI. The agent connector declares what it accepts - currently parallel_tasks 1-100 and pull_period_seconds at least 10 - and an out-of-range value exits 5 with the message One or more agent settings are outside the range this agent accepts. The CLI does not duplicate those numbers, so they cannot drift.

engini opa delete <agent_id>

Permanently delete an agent. Workflows routed through it stop working. Confirms on a TTY; requires --force otherwise (exit 2 if neither).

engini opa enable | disable <agent_id>

Toggle whether the agent is marked online. Not a delete: a disabled agent keeps its token and settings and still appears in list. Disabling stops it being marked Online; tasks it already holds still complete. Enabling returns it to WaitingForConnection (it does not overwrite Online on an agent that is already up). No confirmation - both are reversible.

engini opa token get | rotate <agent_id>

get prints the agent’s current token as {"agent_id": 41, "token": "…"}. rotate issues a new token and invalidates the old one immediately. Confirms on a TTY; requires --force otherwise. The output includes a note reminding you the agent must be reconfigured.

engini opa logs <agent_id> -o <file>

Collect the agent’s logs as a zip archive, written to -o. -o is required - there is no sensible default name for a customer’s log archive, and one would silently overwrite the previous pull. The bytes never go to stdout; the receipt does.
A live round-trip, bounded server-side at 60 seconds. The agent must be Online: otherwise exit 1 with OPA_AGENT_OFFLINE: … is WaitingForConnection; logs can only be collected from an Online agent. - retry once it has connected. If the agent does not answer in time, exit 124.

engini opa download

Fetch the current agent installer.
The manifest is served anonymously - no API key needed for --json. When it carries a sha256, download verifies the file against it and refuses to write on a mismatch (exit 5). When the manifest is degraded (no checksum was published), the file is still written but the receipt says verified: false; a script that requires integrity should check that field. The checksum proves the download was not corrupted, not who published it - the manifest sits beside the installer.

Triggers

Fire on a change in a connected app. A trigger instance (ti_…) is an entity in its own right, created from a trigger type (a catalog slug) against one of your connections; its events are delivered to a destination (an HTTPS endpoint you own). Concepts: Triggers.
A trigger is created disabled. create gives you a ti_…; nothing is captured until enable. And enable also clears a block — Engini blocks a trigger by itself after repeated failures, a deleted connection, or a provider that refused the subscription, and a blocked trigger stays silent until an enable clears it. The exception is an account over its activity limit, which is refused rather than unblocked.
The signing secret is printed once, in the clear. destinations add, destinations rotate-secret, and destinations set when it creates the default all print an esec_… secret with “Shown once — store it now.” No read path returns it again. It lands in your shell history and your CI logs; pipe it to a secret store. Losing it means rotating, which invalidates the current one immediately.

engini triggers types

The catalog of trigger types you can create an instance from.

engini triggers types get <trigger_slug>

One type’s config_schema, payload_schema, and parameter blocks. Read this before create — whether a type takes a schedule, listen columns, or neither is per type. --app <slug> disambiguates a slug carried by more than one application.

engini triggers list

Trigger instances on the account.
--include workflow returns one page only, whatever --top says. Designer-built triggers surfaced this way are not paginated yet. Raise --top to see more in the single page; do not expect to walk the full set with it.

engini triggers get <trigger_id>

Status, schedule, and last-error detail for one instance — including the derived status, its status_reason, next_run_at for polling triggers, and subscription_count.
subscription_count is usually not 1. A provider subscription covers one watched column, so three --listen columns create three subscriptions — and providers meter them.

engini triggers create <trigger_slug>

Connection-less types need no --connection from 0.23.0. Types such as engini_schedule and http_webhook are created with the flag omitted. On 0.20.0-0.22.x the CLI refused without --connection and exited 2; upgrade rather than passing a placeholder id.
--dry-run validates and prints would_create without calling the API.

engini triggers enable | disable <trigger_id>

enable starts capture and clears any block. disable stops capture; it is not a delete, and the instance and its event history stay.

engini triggers delete <trigger_id>

Permanent, and deletes the event history with it. Confirms on a TTY; requires --force otherwise.

engini triggers events <trigger_id>

The captured event log — the record of what fired and what happened to each delivery. --limit <n> caps the rows.

engini triggers events get <event_id>

One event plus its full delivery attempt history. This is where real delivery state lives — delivered, failed, or genuinely pending.

engini triggers events replay <event_id>

Re-deliver a captured event to its destination. A DESTINATION_DISABLED error means the destination auto-disabled after too many failures. Run engini triggers destinations enable <name> first, then replay. Replay queues one more attempt rather than delivering inline, and the attempt numbering continues from where it left off - so a replay gets whatever retries the event had left, none if it had already used all six. Read events get a few seconds later for the result. Walkthrough: Debug and replay a failed delivery.

engini triggers listen

Stream events live. For local development, not a delivery mechanism — production delivery is a destination plus signature verification.
listen prints one compact JSON line per event, always — the stream is line-oriented regardless of --json or whether stdout is a terminal, so it pipes into jq unchanged.The delivery field on the stream is always "pending". The stream reports dispatch, not delivery; read engini triggers events get <event_id> for the settled state.
Ctrl-C stops listen and exits 0 from 0.23.0, after printing the summary line. On 0.20.0-0.22.x the interrupt was only noticed between stream frames, so an idle stream kept running - upgrade if you are on one of those. See the machine contract.
Rehearse a real delivery without a public URL. --forward plus --sign-with POSTs each event to your local handler with a genuine X-Engini-Signature, so the verification code you will ship is the code you are testing. Recipe: Develop a trigger locally.

engini triggers destinations set <url>

Set — or re-point — the account’s default destination. --name <n> names it; --max-failures <n> sets how many consecutive delivery failures auto-disable it (default 5). Prints the signing secret only when this call creates the default. Re-pointing an existing default returns the destination with no secret.

engini triggers destinations add <name> <url>

Register a named destination. --default also makes it the account default. --max-failures <n> as above. Prints the signing secret.

engini triggers destinations list | show <name>

list is every destination on the account. show adds one destination’s failure counters and status. Neither ever returns the signing secret.

engini triggers destinations enable <name>

Re-enable a destination that auto-disabled, and reset its failure counter. Events are still captured while a destination is disabled — they just stop being delivered, so enabling and replaying recovers them.

engini triggers destinations rm <name>

Delete a destination. Deleting one that triggers still point at is a 409; --reassign <name> moves them to another destination in the same call.
The flag and the query parameter are spelled differently. The CLI flag is --reassign; the API query parameter it maps to is reassign_to. If you are reading the API reference and the CLI side by side, this is not a typo in either.
Confirms on a TTY; requires --force otherwise.

engini triggers destinations rotate-secret <name>

Mint a new signing secret. The old one stops working immediately - there is no overlap window, so any receiver still verifying with it starts rejecting deliveries. Update the receiver straight away, then replay whatever failed in between; the steps are in Debug and replay a failed delivery. Prints the new secret. Confirms on a TTY; requires --force otherwise.