> ## Documentation Index
> Fetch the complete documentation index at: https://docs.engini.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Command reference

> Every engini command, its options, and examples.

All commands also accept the universal flags (`--json`, `--quiet`, `--schema`, and `--dry-run` on mutating commands) - see the [overview](/developers/cli).

## Account

### `engini login`

Store an Engini API key.

| Option | Meaning |
| - | - |
| `--api-key <key>` | Opaque Engini key (`eng_…`) - non-interactive |
| `--api-url <url>` | API base URL to persist (e.g. a staging host) |
| `--identity-url <url>` | Dashboard base URL (hosts the keys page) |
| `--force` | Overwrite existing credentials without confirming |

```bash theme={null}
engini login --api-key eng_…                       # store a key non-interactively
engini login --api-url https://staging.engini.io   # point at another environment
engini login --api-key eng_… --dry-run             # preview without writing
```

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).

```bash theme={null}
engini whoami --json | jq -r '.email'
```

## Tools

### `engini tools list`

| Option | Meaning |
| - | - |
| `--application <slug>` | Filter by application slug |
| `--toolset <id>` | List the tools of a server toolset (mutually exclusive with `--application`) |
| `--limit <n>` | Cap the number of tools returned |

```bash theme={null}
engini tools list --application monday --limit 5
engini tools list | jq -r '.[].tool_slug'
```

### `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.)

```bash theme={null}
engini tools get monday_create_item --json
```

### `engini tools call <tool_id>`

Execute a tool against a connection.

| Option | Meaning |
| - | - |
| `--args <args>` | **Required.** JSON object, `@file.json`, or `-` for stdin |
| `--file <spec>` | Attach a file: `field=@path` (repeat the field for a list) |
| `--connection <id>` | Connection id to run against |
| `--toolset <id>` | Toolset id to resolve the connection |
| `--raw` | Emit the tool output verbatim (no envelope) |
| `--llm <vendor>` | Provider-shaped result: `openai` \| `anthropic` |
| `--tool-call-id <id>` | Correlation id for `--llm` output |
| `--max-bytes <n>` | Spill results larger than N bytes to a handle (default 4096; `0` = never) |
| `--inline` | Force the full inline result |

```bash theme={null}
engini tools call monday_create_item --args '{}' --dry-run
engini tools call monday_create_item --args @item.json --connection 3
echo '{"name":"x"}' | engini tools call monday_create_item --args -
engini tools call gmail_send_mail --args '{}' --file attachmentsarray=@report.pdf
engini tools call monday_create_item --args '{}' --llm openai --tool-call-id call_1
```

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.

| Option | Meaning |
| - | - |
| `--select <path>` | Dot-path to extract, e.g. `items.email` (repeatable; maps over lists) |
| `--fields <keys>` | Comma-separated keys to project from object(s) |
| `--offset <n>` / `--limit <m>` | Page a list value |
| `--full` | Emit the entire stored payload |
| `--list` | List stored handles |
| `--clear` | Delete all stored results |

```bash theme={null}
engini tools result h42                          # shape + preview
engini tools result h42 --select items.email     # one dot-path (maps over a list)
engini tools result h42 --fields id,name         # project keys per item
engini tools result h42 --select items --offset 20 --limit 10
```

## Applications

### `engini applications list`

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

```bash theme={null}
engini applications list --available --limit 10
engini applications list --search crm --json
```

### `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](/developers/cli/machine-contract)).

| Option | Meaning |
| - | - |
| `--auth <id>` | Authentication method id |
| `--provider <name\|id\|none>` | Sign in through your own [auth provider](/developers/get-started/auth-providers): a name, a numeric id, or `none` for the connector's own OAuth client. Omit it for the account's default provider, if you may use it |
| `--name <name>` | Connection name |
| `--field <spec>` | Credential field `key=value` (repeatable) |
| `--object <id>` | Object id to select (repeatable) |
| `--no-objects` | Skip object selection |
| `--oauth-state <state>` | Resume an OAuth2 sign-in by its state token |
| `--connection <id>` | Resume object selection for an existing connection |
| `--no-wait` | Return early instead of waiting for async jobs |
| `--timeout <seconds>` / `--poll-interval <seconds>` | Polling controls (defaults 300 / 2) |

```bash theme={null}
engini connect                       # open the web connections page
engini connect monday                # interactive walkthrough (TTY)
engini connect monday --field ApiKey=… --name 'My Monday'
engini connect monday --json         # agent: prints the next step + exit 5
engini connect outlook --oauth-state <state>      # resume an OAuth sign-in
engini connect monday --connection 42 --object 5  # resume object selection
```

Flow: discover auth methods → collect credentials (or run OAuth) → create → check → refresh objects → select objects → `{"connection_id", "is_alive", "objects_selected"}`.

With `--provider`, the provider fixes the authentication method, so `--auth` defaults to it (a different `--auth`, or a provider for another application, exits `2`), and the fields the provider already holds are not prompted for. A name or numeric id is looked up among the providers you can use (`engini connections auth-providers usable`); a provider that is missing, disabled or not available to you exits `4`, and a name shared by several exits `2` - pass the id. The `resume` strings `connect` prints carry the resolved `--provider <id>`.

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"`](/developers/cli/machine-contract#field-options-in-need-fields-and-need-auth).

## Connections

### `engini connections list | get`

```bash theme={null}
engini connections list --application monday
engini connections list --kind Key          # the web UI's "Keys" tab
engini connections get 42 --json
```

`--kind` filters on `Regular`, `Key`, or `MCPClient` (case-insensitive). `Key` is exactly what the Connections page's **Keys** tab shows - see [Keys vs Integrations](/developers/get-started/platform-model#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`](#on-prem-agents). 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.

```bash theme={null}
engini connections create slack --auth 2 --name Sales --field token=xoxb-1
```

`--provider <name|id|none>` is accepted here too, but the server honours it only for an OAuth2 client-credentials method (one with no sign-in); for every other method the provider is chosen at sign-in instead.

### `engini connections update <connection_id>`

Change a connection's name, description, credential fields, or metadata. Anything you don't pass is left unchanged.

```bash theme={null}
engini connections update 481 --name "prod key"
engini connections update 481 --field apiKey=sk-new --dry-run   # preview first
engini connections update 481 --field apiKey=sk-new
```

`--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.

```bash theme={null}
engini connect outlook --json                                    # start a sign-in, get its state
engini connections update 481 --oauth-state <state>               # re-authenticate 481 in place
engini connections update 481 --field RefreshToken=rt-new --oauth-state <state>   # combined with other fields
```

<Warning>
  `--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).
</Warning>

The `--dry-run` output names the risk explicitly, so you can see what you are about to drop before you drop it:

```json theme={null}
{
  "would_update": {
    "connection_id": 481,
    "fields": { "apiKey": "sk-s…t123" },
    "replaces_entire_fields_map": true,
    "replaces_entire_metadata_map": false
  }
}
```

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

Point **one workflow** at a different connection.

```bash theme={null}
engini connections replace --workflow 903 --from 481 --to 482 --dry-run
engini connections replace --workflow 903 --from 481 --to 482
```

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.

```bash theme={null}
engini connections defaults --json
engini connections set-default 481        # takes a CONNECTION ID
engini connections clear-default monday   # takes an APPLICATION SLUG
```

<Note>
  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.
</Note>

### `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>`

```bash theme={null}
engini connections objects 42 --name leads
engini connections select-objects 42 --object 5 --object 9
```

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

The OAuth primitives behind `connect`:

```bash theme={null}
engini connections sign-in-url outlook --auth 0
# -> {"sign_in_url": …, "state": …, "resume": "engini connections oauth-complete outlook --auth 0 --state <state>"}
engini connections oauth-complete outlook --auth 0 --state <state>
engini connections sign-in-url salesforce --auth 9 --provider "Acme prod"   # through your own OAuth app
```

`sign-in-url` takes `--provider <name|id|none>` (see [Auth providers](/developers/get-started/auth-providers)); the server keeps the provider with the sign-in state, and `oauth-complete` has no `--provider` option.

## Auth Providers

Manage your own OAuth credentials for an application - register once, sign in through them from many connections. An auth provider is OAuth2-only: it is the "Set on provider" fields of one OAuth2 authentication method. See [Auth providers](/developers/get-started/auth-providers) for the model and the access rules.

Access is permission-gated: `list`, `get` and `redirect-uri` need permission to manage auth providers, `sources` needs permission to create them, and `usable` needs only permission to use them (any member when RBAC is off).

A provider is **a name plus values for the method's fields, addressed by slot**: `Key1`..`Key10`, `ApiUser`, `ApiPassword`, `BaseUrl`. Which slots a method takes is connector-specific - read them from `sources`. There is no `--client-id` / `--client-secret`: the server rejects both with `400 INVALID_AUTH_PROVIDER`, so a client id and secret are just `--field` values in whichever slots the connector marks.

### `engini connections auth-providers sources`

Which applications and methods can have a provider, and the slots to fill. Takes no options besides the universal flags.

```bash theme={null}
engini connections auth-providers sources
```

Each application lists its `application_id`, its `methods` (`authentication_id`, `display_name`, `authentication_type`) and, per method, the `fields` to fill (`slot`, `label`, `is_password`, `is_required`). Shapes are in the [machine contract](/developers/cli/machine-contract#engini-connections-auth-providers-output-shapes).

### `engini connections auth-providers redirect-uri`

The callback URL to register in your own OAuth app **before** you create the provider.

```bash theme={null}
engini connections auth-providers redirect-uri --app salesforce
# -> {"redirect_uri": "https://…"}
engini connections auth-providers redirect-uri --app salesforce --sandbox
```

| Option | Meaning |
| - | - |
| `--app <slug\|id>` | **Required.** Application slug, or its numeric id (from `sources`) |
| `--auth <id>` | Authentication method id - optional when the application has one eligible method |
| `--sandbox` | The vendor's sandbox redirect URI instead of the production one |

A slug is resolved by matching the application's display name against `sources`, so an application with no eligible method exits `4`, and one whose name matches several exits `2` - pass the numeric id.

### `engini connections auth-providers create`

Register a provider for one authentication method of an application.

```bash theme={null}
engini connections auth-providers sources                     # find the app, method and slots
engini connections auth-providers create --app salesforce --name "Acme prod" \
  --field Key1=<client-id> --field Key2                       # bare Key2 prompts, hidden (TTY only)
printf %s "$SECRET" | engini connections auth-providers create --app salesforce \
  --name "Acme prod" --field Key1=<client-id> --field-stdin Key2   # or pipe the secret in
```

| Option | Meaning |
| - | - |
| `--app <slug\|id>` | **Required.** Application slug, or numeric application id |
| `--name <name>` | **Required.** Provider name, unique within the account |
| `--auth <id>` | Authentication method id - optional when the application has one eligible method; with several, the command exits `2` and lists them |
| `--field <slot[=value]>` | Provider field (repeatable): `Slot=value`, or a bare `Slot` to be prompted |
| `--field-stdin <slot>` | Read one field value from stdin - given at most once |
| `--default` | Make this the application's default provider (replaces any other default) |
| `--sandbox` | The provider is for the vendor's sandbox environment |
| `--scopes <scopes>` | Scopes that replace the method's own scope option |
| `--extra-options <json>` | Extra OAuth options as a JSON object of strings |

Secrets passed as `--field Slot=value` are visible in `ps` and shell history. A bare `--field Slot` (hidden prompt, terminal only) or `--field-stdin Slot` keeps them out. A blank prompt or stdin value aborts with exit `2` instead of sending an empty string. Naming a slot twice, or a slot that is not one of the ten above plus `ApiUser`, `ApiPassword`, `BaseUrl`, exits `2`. With `--dry-run` nothing is prompted, read or sent: the output lists slot names only.

### `engini connections auth-providers list | get`

```bash theme={null}
engini connections auth-providers list
engini connections auth-providers get 7
```

`list` returns every provider on the account, including those whose application was removed (`connector_removed: true`). `get <id>` returns one. A secret slot reads back as `has_value: true` with a `null` `value`.

### `engini connections auth-providers update <id>`

Only what you pass changes - an omitted option or field slot keeps its stored value:

```bash theme={null}
engini connections auth-providers update 7 --status disabled
engini connections auth-providers update 7 --field Key1=<new-client-id>
engini connections auth-providers update 7 --field Key3=     # trailing '=' CLEARS Key3
```

| Option | Meaning |
| - | - |
| `--name <name>` | New provider name |
| `--default` / `--no-default` | Make, or stop being, the application's default provider |
| `--sandbox` / `--no-sandbox` | Mark as the vendor's sandbox / production environment |
| `--status <active\|disabled>` | Enable or disable the provider (`1` / `0` also accepted) - a disabled provider refuses new sign-ins |
| `--scopes <scopes>` | New scopes override (`''` clears it) |
| `--extra-options <json>` | New extra OAuth options (`''` clears them) |
| `--field <slot[=value]>` | Field to change: `Slot=value`, a bare `Slot` to be prompted, `Slot=` to clear (repeatable) |
| `--field-stdin <slot>` | Read one field value from stdin |

Passing no option at all exits `2`. A field the OAuth options read (such as a client id) cannot change while connections are signed in through the provider; other values, secrets included, can change at any time.

### `engini connections auth-providers delete <id>`

Remove a provider. Confirms on a terminal; pass `--force` to skip the prompt, which a non-interactive caller must do. Refused with `409 AUTH_PROVIDER_IN_USE` (exit `5`) while any connection is signed in through it. Prints `{"deleted": 7}`.

### `engini connections auth-providers usable`

The active providers you can sign a connection in through - ids and names only, no field values. Takes no options besides the universal flags.

```bash theme={null}
engini connections auth-providers usable
```

This is what `--provider <name>` looks a name up in, so a plain member can use a provider by name without being able to `list`.

## Toolsets

Manage [toolsets](/developers/cookbook/scope-agent-toolset) - named bundles that pin which connection each application uses and which tools are permitted.

### `engini toolsets list | get`

```bash theme={null}
engini toolsets list | jq -r '.[] | "\(.toolset_id)\t\(.name)"'
engini toolsets get <id> --json
```

### `engini toolsets create`

Two ways to express the bindings, mutually exclusive:

| Option | Meaning |
| - | - |
| `--name <name>` | Display name for the toolset |
| `--from-file <path>` | Whole request body: JSON object, `@file.json`, or `-` for stdin |
| `--connection <spec>` | `<id>` or `<id>:slug,slug` - bare id permits all of that connection's tools (repeatable) |
| `--application <spec>` | `<slug>` or `<slug>:slug,slug` - uses your default connection for the app, pinned server-side at save time (repeatable) |
| `--workflow <guid>` | Include a workflow (repeatable) |

```bash theme={null}
# two connections, each scoped to specific tools
engini toolsets create --name support-agent \
  --connection 42:gmail_send_mail,gmail_list_messages \
  --connection 17:monday_create_item

# scoped + unscoped mix; or resolve by application slug
engini toolsets create --name support-agent --connection 42:gmail_send_mail --application monday

# from a file (accepts snake_case or camelCase keys)
engini toolsets create --name support-agent --from-file toolset.json
```

### `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:

```bash theme={null}
engini toolsets update <id> --connection 42:gmail_list_messages --dry-run
# copy -> tweak -> apply round-trip:
engini toolsets get <id> --json | jq '.connections |= map(select(.connection_id != 17))' \
  | engini toolsets update <id> --from-file -
```

### `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](/developers/ai-agents/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.

<Note>
  **There is no `engini mcp create` and no `engini mcp delete`** - deliberately, unlike [toolsets](#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.
</Note>

<Warning>
  **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.
</Warning>

<Warning>
  **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](/developers/ai-agents/connect-via-mcp#authentication) 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.
</Warning>

### `engini mcp list`

Every MCP server on the account. Deactivated servers are **included**, reporting `is_active: false` - they are not hidden and not deleted.

```bash theme={null}
engini mcp list --json
engini mcp list --json | jq -r '.[] | select(.is_active == false) | .name'
```

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`.

```bash theme={null}
engini mcp get <mcp_server_token> --json
```

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`.

```bash theme={null}
engini mcp deactivate <mcp_server_token>
engini mcp activate <mcp_server_token> --dry-run
# -> {"would_update": {"mcp_server_token": "…", "is_active": true}}
```

### `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.

| Option | Meaning |
| - | - |
| `--connection <id[:slug,slug]>` | Connection to expose - bare `<id>` permits all of that application's tools (repeatable) |

```bash theme={null}
engini mcp connections add <token> --connection 2139
engini mcp connections add <token> --connection 2139:gmail_send_mail,gmail_list_messages
engini mcp connections add <token> --connection 2139:gmail_send_mail --dry-run
```

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.

| Option | Meaning |
| - | - |
| `--connection-id <id>` | Connection id to remove (repeatable) |

```bash theme={null}
engini mcp connections rm <token> --connection-id 2139
engini mcp connections rm <token> --connection-id 2139 --dry-run
```

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.

| Option | Meaning |
| - | - |
| `--connection-id <id>` | Connection whose available tools to list (required) |

```bash theme={null}
engini mcp tools list <token> --connection-id 2139 --json
# -> [{"tool_name": "gmail_send_mail", "description": "…", "is_mandatory": false}, …]
```

### `engini mcp tools set <mcp_server_token>`

Replace the tool allow-list for **one** connection. Every other connection on the server is left untouched.

| Option | Meaning |
| - | - |
| `--connection-id <id>` | Connection to re-scope (required) |
| `--tool <slug>` | Tool slug to expose (repeatable) |
| `--all` | Expose every tool of the connection's application |

```bash theme={null}
engini mcp tools set <token> --connection-id 2139 --tool gmail_send_mail --tool gmail_list_messages
engini mcp tools set <token> --connection-id 2139 --all
engini mcp tools set <token> --connection-id 2139 --tool gmail_send_mail --dry-run
```

`--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.

| Option | Meaning |
| - | - |
| `--workflow <workflow_token>` | Workflow to expose (repeatable) |
| `--tool-name <name>` | Override the name the tool is addressable by (single `--workflow` only) |
| `--tool-description <text>` | Override the tool description (single `--workflow` only) |

```bash theme={null}
engini mcp workflows add <token> --workflow <workflow_token>
engini mcp workflows add <token> --workflow <workflow_token> --tool-name send_report
engini mcp workflows add <token> --workflow <workflow_token> --dry-run
```

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.

| Option | Meaning |
| - | - |
| `--workflow <workflow_token>` | Workflow token to remove (repeatable) |

```bash theme={null}
engini mcp workflows rm <token> --workflow <workflow_token>
```

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`.

<Note>
  **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.
</Note>

<Warning>
  **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.
</Warning>

### `engini opa list`

Every on-prem agent on the account. Pagination is handled for you.

| Option | Meaning |
| - | - |
| `--status <s>` | `WaitingForConnection`, `Online`, `Offline`, or `Disabled` (case-insensitive) |
| `--name <substring>` | Filter by name |

```bash theme={null}
engini opa list --json
engini opa list --status offline --json | jq -r '.[].name'
```

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`.

```bash theme={null}
engini opa get 41 --json
```

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.**

| Option | Meaning |
| - | - |
| `--name <name>` | Display name (required) |
| `--description <text>` | Optional |
| `--parallel-tasks <n>` | Concurrent tasks the agent runs. Omit for the default (`2`) |
| `--pull-period <seconds>` | Seconds between polls. Omit for the default (`10`) |
| `--log-level <level>` | e.g. `Information`, `Debug`. Omit for the default (`Information`) |
| `--application-slug <slug>` | Only when more than one on-prem-agent application exists - see below |

```bash theme={null}
engini opa create --name "Warehouse SQL" --json
engini opa create --name Backoffice --pull-period 30 --log-level Debug --dry-run
# -> {"would_create": {"name": "Backoffice", "settings": {"pullPeriodSeconds": 30, "logLevel": "Debug"}}}
```

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.

```bash theme={null}
engini opa update 41 --log-level Debug --json
engini opa update 41 --parallel-tasks 8 --pull-period 15 --dry-run
# -> {"would_update": 41, "changes": {"settings": {"parallelTasks": 8, "pullPeriodSeconds": 15}}}
```

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).

```bash theme={null}
engini opa delete 41 --dry-run     # -> {"would_delete": 41}
engini opa delete 41 --force
```

### `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.

```bash theme={null}
engini opa disable 41
engini opa enable 41 --dry-run     # -> {"would_enable": 41}
```

### `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.

```bash theme={null}
engini opa token get 41 --json
engini opa token rotate 41 --force --json
```

### `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.

```bash theme={null}
engini opa logs 41 -o ./opa-41-logs.zip
# -> {"wrote": "./opa-41-logs.zip", "bytes": 184320, "agent_id": 41}
```

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.

| Option | Meaning |
| - | - |
| `-o, --out <file>` | Where to write it. Defaults to the installer's own filename in the current directory |
| `--json` | Print the release manifest and download **nothing** |

```bash theme={null}
engini opa download --json
# -> {"version": "2.6.25.1", "url": "https://…/EnginiAgent-2.6.25.1.exe", "sha256": "…", "sizeBytes": 65285897, "degraded": false, …}
engini opa download -o ./EnginiAgent.exe
# -> {"wrote": "./EnginiAgent.exe", "bytes": 65285897, "version": "2.6.25.1", "verified": true, "degraded": false}
```

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](/developers/get-started/triggers).

<Note>
  **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.
</Note>

<Warning>
  **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.
</Warning>

### `engini triggers types`

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

| Option | Meaning |
| - | - |
| `--app <slug>` | Narrow to one application |
| `--search <q>` | Substring search |
| `--connection <id>` | Only types this connection can back |

```bash theme={null}
engini triggers types --app monday --json
```

### `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.

| Option | Meaning |
| - | - |
| `--app <slug>` | Narrow to one application |
| `--connection <id>` | Narrow to one connection |
| `--status <s>` | `enabled`, `disabled`, or `errored` |
| `--include workflow` | Also surface designer-built workflow triggers |
| `--top <n>` | Page size (also the row cap under `--include`) |

```bash theme={null}
engini triggers list --status errored --json
```

<Note>
  **`--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.
</Note>

### `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`.

<Note>
  `subscription_count` is usually not 1. A provider subscription covers one watched column, so three `--listen` columns create three subscriptions — and providers meter them.
</Note>

### `engini triggers create <trigger_slug>`

| Option | Meaning |
| - | - |
| `--connection <name\|id>` | The connection to run against. Optional from `0.23.0` - omit it for types that need no connection |
| `--config <json>` | The trigger activity's own inputs |
| `--every <n><s\|m\|h\|d>` | Polling interval, e.g. `--every 15m` |
| `--weekdays <spec>` | Restrict polling to certain days |
| `--window <HH:mm-HH:mm>` | Restrict polling to a time-of-day window |
| `--listen <column>` | A column to watch. Repeat for several |
| `--destination <name>` | Deliver to a named destination instead of the account default |
| `--dedupe-window-hours <n>` | How long an identical payload is suppressed |

```bash theme={null}
engini triggers create monday_item_created --connection 12 --listen status --dry-run
# -> {"would_create": {"trigger_slug": "monday_item_created", "connection": "12", "listen_columns": []}}
engini triggers create monday_item_created --connection 12 --listen status
engini triggers enable ti_DG9q7IlQO0MeghC4IEzP19
```

<Note>
  **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.
</Note>

`--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.

```bash theme={null}
engini triggers events ti_DG9q7IlQO0MeghC4IEzP19 --limit 20 --json
```

### `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](/developers/cookbook/debug-replay-failed-delivery).

### `engini triggers listen`

Stream events live. **For local development, not a delivery mechanism** — production delivery is a destination plus signature verification.

| Option | Meaning |
| - | - |
| `--trigger <id>` | Only this instance |
| `--slug <s>` | Only this trigger type |
| `--app <s>` | Only this application |
| `--since <event_id>` | Replay everything after this event, then continue live |
| `--no-resume` | Do not reconnect automatically when the stream ends |
| `--forward <url>` | POST each event to a local URL |
| `--sign-with <secret>` | Sign the forwarded POSTs, so your handler's verification path is exercised |

```bash theme={null}
engini triggers listen --trigger ti_DG9q7IlQO0MeghC4IEzP19
engini triggers listen --app monday --forward http://localhost:3000/hooks --sign-with esec_…
```

<Note>
  **`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.
</Note>

<Note>
  **`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](/developers/cli/machine-contract#trigger-commands).
</Note>

<Tip>
  **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](/developers/cookbook/develop-trigger-locally).
</Tip>

### `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.

<Note>
  **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](/developers/api-reference) and the CLI side by side, this is not a typo in either.
</Note>

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](/developers/cookbook/debug-replay-failed-delivery#rotate-a-secret-without-losing-events). **Prints the new secret.** Confirms on a TTY; requires `--force` otherwise.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.