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

# Changelog

> Notable changes to the Engini Developer API, SDKs, and CLI.

Breaking changes to the `/v1` API are announced here before they ship. The [OpenAPI spec](/developers/api-reference) is the source of truth; the SDKs are generated from it.

<Update label="2026-10-08" description="CLI/SDK 0.26.0 - auth providers (sign in through your own OAuth app)">
  `engini-client` **py 0.13.0** / **ts 0.12.0** · `engini` / `@engini/sdk` **0.26.0** · `@engini/cli` **0.26.0**.

  This release adds **auth providers**: your account's own credentials - typically your own OAuth app - for one OAuth2 authentication method of one application, filled once and used by every connection signed in through it. Manage them with the new `engini connections auth-providers` group (eight commands: `list`, `get`, `create`, `update`, `delete`, `redirect-uri`, `sources`, `usable`). Sign in through one with `--provider <name|id|none>` on `engini connect`, `engini connections create` and `engini connections sign-in-url`. The SDKs get a matching `client.auth_providers` (Python) / `client.authProviders` (TypeScript) resource.

  **New endpoints**

  | Operation | Path |
  | - | - |
  | List / create | `GET` / `POST /v1/auth-providers` |
  | Get / update / delete | `GET` / `PATCH` / `DELETE /v1/auth-providers/{id}` |
  | Providers you can sign in through | `GET /v1/auth-providers/usable` |
  | Applications and methods a provider can be based on | `GET /v1/auth-providers/sources` |
  | Callback URL to register in your OAuth app | `GET /v1/auth-providers/redirect-uri/{applicationId}` |

  **New on existing endpoints**

  * `authProviderId` in the request body of `POST /v1/connections/get-sign-in-url` (OAuth2 sign-in)
  * `authProviderId` query parameter on `POST /v1/connections`, honoured only for OAuth2 client-credentials (no sign-in) methods

  **Python SDK: Python 3.10 or later.** `requires-python` in `pyproject.toml` is now `>=3.10` (it was `>=3.9`), matching `engini-client`, whose generated code has never imported on 3.9.

  **No exit code was added.** Four `409` codes from `/v1/auth-providers` now exit `5` (validation) instead of `1`: `AUTH_PROVIDER_IN_USE`, `AUTH_PROVIDER_NOT_SUPPORTED`, `AUTH_PROVIDER_NAME_TAKEN` and `CONNECTOR_REMOVED`. See [Auth-provider exit codes](/developers/cli/machine-contract#engini-connections-auth-providers-output-shapes).

  See [Auth providers](/developers/get-started/auth-providers) for the model, the setup steps and the access rules.
</Update>

<Update label="2026-09-30" description="Server-only: blocked-connection guard, and the error envelope drops path">
  Two server-side changes with no SDK/CLI surface, so no package version marks them:

  * **`403 CONNECTION_BLOCKED` on `POST /v1/tools/{toolSlug}/execute`.** A tool call resolved against a connection with no licence, or an expired one, now returns `403` and the tool is **not** executed - unlike an ordinary tool-runtime failure, which is a `200` with `isSuccess: false`. See the [error code table](/developers/get-started/pagination-errors-rate-limits#error-codes).
  * **The error envelope no longer includes `path`.** Every error body dropped the field; update anything that read it. It may still appear on `/v1/auth/whoami` failures pending an internal repin.
</Update>

<Update label="2026-09-24" description="Triggers API live on production">
  The `/v1/triggers` API is **enabled on production**. The `client.triggers` resource and the `engini triggers` commands that shipped in 0.20.0 now work against `api.engini.io` with a normal API key - no upgrade needed to start, though CLI `0.23.0`+ is recommended (see below).

  Start with [Triggers](/developers/get-started/triggers) for the model and the delivery contract, then [Triggers in the SDKs](/developers/sdk/triggers), the [`engini triggers` commands](/developers/cli/command-reference#triggers), or the new **Triggers** group in the [API reference](/developers/api-reference). The Cookbook has three new recipes under **React to events**: [receive events on a webhook](/developers/cookbook/receive-trigger-events-webhook), [debug and replay a failed delivery](/developers/cookbook/debug-replay-failed-delivery), and [develop a trigger locally](/developers/cookbook/develop-trigger-locally).

  **Endpoints**

  | Operation | Path |
  | - | - |
  | Trigger type catalog | `GET /v1/triggers/types` · `GET /v1/triggers/types/{triggerSlug}` |
  | List / create triggers | `GET` / `POST /v1/triggers` |
  | Get / update / delete | `GET` / `PATCH` / `DELETE /v1/triggers/{triggerId}` |
  | Enable / disable | `POST /v1/triggers/{triggerId}/enable` · `/disable` |
  | Send a test event | `POST /v1/triggers/{triggerId}/test` - **new, API-only** |
  | Event log | `GET /v1/triggers/{triggerId}/events` · `GET /v1/triggers/events/{eventId}` |
  | Replay an event | `POST /v1/triggers/events/{eventId}/replay` |
  | Live stream | `GET /v1/triggers/subscribe` (`text/event-stream`) |
  | Account default destination | `GET` / `PUT /v1/triggers/destination` |
  | Named destinations | `GET` / `POST /v1/triggers/destinations` · `GET` / `PATCH` / `DELETE /v1/triggers/destinations/{name}` |
  | Rotate a signing secret | `POST /v1/triggers/destinations/{name}/rotate-secret` |

  **New since 0.20.0: a test endpoint**

  `POST /v1/triggers/{triggerId}/test` signs a synthetic event with the destination's real secret, sends it straight away, and returns what your endpoint answered (`delivered`, `status_code`, `error`). Nothing is stored. It is not wrapped by any released package yet (Python SDK, TypeScript SDK and CLI all at `0.24.0`), so call it over HTTP. Limited to 10 calls a minute per account.

  **Delivery, in short**

  * Any `2xx` is delivered. `408`, `429`, `5xx`, timeouts (10 s) and connection errors are retried 1 min, 5 min, 15 min, 1 h and 6 h apart - 6 attempts in all. Any other `4xx`, or a redirect, dead-letters the event at once.
  * Five dead-lettered events in a row auto-disable the destination. Events are still captured while it is disabled, and re-enabling resumes delivery on its own for events from the last 24 hours; older backlog and dead-lettered events need a replay.
  * Events are kept 30 days. Replay queues one more attempt (`202`) rather than delivering inline.
  * Rotating a signing secret has no overlap window: the old secret stops working immediately.

  **Upgrade the CLI to 0.23.0 or later if you use triggers from the terminal**

  * `engini triggers create` no longer requires `--connection` for connection-less trigger types (`engini_schedule`, `http_webhook`). On `0.20.0`-`0.22.x` it exited `2` without one.
  * `Ctrl-C` now stops `engini triggers listen` and exits `0`. On `0.20.0`-`0.22.x` an idle stream ignored the interrupt.

  **Docs correction**

  The webhook examples on [Triggers in the SDKs](/developers/sdk/triggers) passed arguments to `verify_webhook` / `verifyWebhook` in the wrong order. The signature has always been `(payload, signature_header, secret)` - raw body first. Code copied from the old example raises `EnginiWebhookSignatureError` on every delivery. The TypeScript `create` example also showed a positional slug (it takes one object, `{ triggerSlug, ... }`), and the destination examples used `max_failures` / `maxFailures` where the parameter is `max_consecutive_failures` / `maxConsecutiveFailures`. All fixed.
</Update>

<Update label="2026-09-22" description="Client/SDK/CLI 0.24.0 - field options on the OAuth access-token poll">
  `engini-client` **0.11.0** · `@engini/sdk` **0.24.0** · `@engini/cli` **0.24.0** · `engini` (Python) **0.24.0** (Python: 2026-09-30).

  `AccessTokenResponse` gains **`field_options`** / **`fieldOptions`**: a per-field map of dynamically-discovered legal values, returned on the successful poll of `GET /v1/connections/access-token/{state}` for fields with no static list (Salesforce's `ApiVersionUrl` is the first case). `ApplicationConnectionField` gains **`maskInLogs`**, mirroring `AppConnectionField.MaskInLogs` - a secret field that isn't a password; test `isPassword || maskInLogs` to catch both. See [Field options discovered after sign-in](/developers/sdk/connections-oauth#field-options-discovered-after-sign-in) for the orientation warning (the map's key is the value to save, not the label) and [Field options in need fields and need auth](/developers/cli/machine-contract#field-options-in-need-fields-and-need-auth) for the CLI's `need:"fields"`/`need:"auth"` shape.

  Since **0.23.0**, `engini connect` merges `--field` with OAuth-derived values instead of dropping it on the OAuth path. 0.24.0 adds client-side validation of a supplied field against its discovered options (exit `2` on an illegal value, before any request reaches the server) and a numbered picker in a TTY.
</Update>

<Update label="2026-09-15" description="Client/SDK/CLI 0.23.0 - connection-less triggers, Ctrl-C, and OAuth resume hints">
  `engini-client` **0.10.0** · `@engini/sdk` **0.23.0** · `@engini/cli` **0.23.0** · `engini` (Python) **0.23.0** (Python: 2026-09-22).

  * **`engini triggers create` no longer requires `--connection`.** Omit it for a trigger type whose application needs no connection (`engini_schedule`, `http_webhook`) and the server supplies its own. For a type that does need one, omitting the flag now reaches the server and comes back `400 EMPTY_QUERY`, which the CLI rewrites to name `--connection` and exits **`5`** - not the `2` of `0.20.0`-`0.22.x`. An explicit empty value (`--connection ""`) still exits `2`.
  * **`Ctrl-C` now stops `engini triggers listen`.** It closes the stream and any in-flight `--forward` POST, prints the usual summary line, and exits `0` - even if some forwards had failed. On `0.20.0`-`0.22.x` an idle stream never noticed the interrupt at all. The TypeScript SDK's `client.triggers.subscribe()` grows a matching `signal` option (an `AbortSignal`) that the CLI wires to this handler; aborting returns the generator normally rather than throwing.
  * **`engini connect`'s OAuth `resume` string is now replayable.** It carries `--auth <id>` and echoes back any `--field` values you originally passed (a secret's value is shown as `<value>`), instead of dropping them on the OAuth path. A `resume` replayed after `state_expires_in_seconds` (300s) now fails fast with exit `4`, rather than polling out the full timeout.
</Update>

<Update label="2026-09-14" description="Client/SDK/CLI 0.22.0 - shim removal, OIDC publishing">
  `engini-client` **0.10.0** · `engini` / `@engini/sdk` **0.22.0** · `@engini/cli` **0.22.0**.

  Behaviour-neutral release: the Python package drops the OAuth-state/`helpUrl` compatibility shims now that `engini-client` 0.10.0 carries them natively, and the npm packages switch to publishing via OIDC. No API, SDK, or CLI surface changed.
</Update>

<Update label="2026-09-14" description="Client/SDK/CLI 0.21.0 - breaking: OAuth2 connections require a sign-in state">
  `engini-client` **0.10.0** · `engini` / `@engini/sdk` **0.21.0** · `@engini/cli` **0.21.0**.

  **Breaking.** Creating an OAuth2 connection now requires the sign-in state from `GET /v1/connections/get-sign-in-url` - pass it as `oauth_state` / `oauthState` on create. Two new error codes: **`OAUTH_STATE_REQUIRED`** (`400`, the state was omitted) and **`OAUTH_STATE_EXPIRED`** (`400`, the state was issued but is stale or already used). Re-authenticating an existing connection follows the same shape via `connections update --oauth-state <state>`.
</Update>

<Update label="2026-09-10" description="CLI/SDK 0.20.0 - the triggers surface ships in the packages, ahead of the API">
  `engini-client` **0.9.0** · `engini` / `@engini/sdk` **0.20.0** · `@engini/cli` **0.20.0**.

  This release adds a **triggers** surface to both SDKs and the CLI: a `client.triggers` resource and an `engini triggers` command group, for creating trigger instances from a catalog of trigger types, managing the destinations their events are delivered to, and reading the event log.

  <Note>
    **Enabled on production on 2026-09-24** - see the entry above. At the time of this release the API was not yet switched on, and every `/v1/triggers/*` path returned `404`; the packages shipped first and the server followed.
  </Note>

  Everything else in 0.20.0 is unchanged from 0.19.0 — no exit code was added, and no existing endpoint, method, or flag changed behaviour.
</Update>

<Update label="2026-09-03" description="CLI/SDK 0.19.0 - manage On-prem agents over the API, SDKs, and CLI">
  `engini-client` **0.8.0** · `engini` / `@engini/sdk` **0.19.0** · `@engini/cli` **0.19.0**.

  Your **On-prem agents** - the Engini component a customer installs behind their firewall to reach SQL Server, Oracle, Priority ERP, and file shares - are now manageable outside the engini.io UI: a new `/v1/on-prem-agents` group, a `client.opa` resource in both SDKs, and an `engini opa` command group. Provision an agent, read or rotate its token, change its runtime settings, pull its logs, and fetch the current installer.

  **Two breaking changes**

  * **`kind=OPA` is no longer accepted** on `GET /v1/connections` or `GET /v1/applications`. It returns **`400`** (`INVALID_ENUM_VALUE`) naming `/v1/on-prem-agents`; the CLI exits **`5`**. Previously it was accepted and returned an empty page forever - on-prem agents were never served by those endpoints, so no caller can have been receiving data from the old behaviour. `kind=MCPClient` is unchanged and still accepted.
  * **`GET /v1/connections/{connectionId}` now returns `404` for an on-prem agent** (and for an `MCPClient` connection), matching what the list endpoint has always excluded. Before this, detail-by-id answered for connections the list would never show.

  **New endpoints**

  | Operation | Path |
  | - | - |
  | List agents | `GET /v1/on-prem-agents` |
  | Create agent | `POST /v1/on-prem-agents` |
  | Get / update / delete | `GET` / `PATCH` / `DELETE /v1/on-prem-agents/{id}` |
  | Enable / disable | `POST /v1/on-prem-agents/{id}/enable` · `/disable` |
  | Read token / rotate token | `GET /v1/on-prem-agents/{id}/token` · `POST /v1/on-prem-agents/{id}/token/rotate?confirm=true` |
  | Collect logs | `GET /v1/on-prem-agents/{id}/logs` (`application/zip`) |
  | Installer manifest | `GET /v1/on-prem-agents/releases/latest` - **anonymous** |

  * Every per-agent route returns **`404`** when the id is unknown, belongs to another account, *or* is not an on-prem agent. The three are deliberately indistinguishable so the endpoint cannot be used to probe which ids exist.
  * `GET /v1/on-prem-agents` uses the standard paginated envelope; `top` is clamped to 1-100.

  **An agent is polled, never pushed to**

  The agent polls Engini for work; Engini never connects to it. Two consequences a caller must plan for:

  * `PATCH /v1/on-prem-agents/{id}` **always persists** the change, then tries to hand it to the running agent. The response carries `appliedToAgent` and `dispatchedTaskIds`. `appliedToAgent: false` is **not an error** - the agent was not `Online` (a freshly created agent is `WaitingForConnection` until it first polls), and it will pick the new settings up when it connects. The write already committed, so retrying is safe but unnecessary.
  * `GET /v1/on-prem-agents/{id}/logs` needs the agent `Online`. Otherwise it returns **`409 OPA_AGENT_OFFLINE`** naming the agent's actual status; if the agent does not answer within the server-side bound it returns **`504 OPA_AGENT_TIMEOUT`**. The CLI exits `1` and `124` respectively.

  **Settings bounds come from the connector, not from you**

  `parallelTasks` and `pullPeriodSeconds` are validated against the range the agent connector itself declares - currently **1-100** and a **minimum of 10 seconds**. A value outside it is **rejected with `400`** (CLI exit `5`), never silently clamped. Every settings member is nullable: on `PATCH`, an **omitted** member is left unchanged, which is why `0` is treated as a mistake rather than a request. Neither the SDKs nor the CLI duplicate the numbers client-side; the API is the one authority.

  **`PATCH` merges, it does not replace**

  Unlike `PUT /v1/connections/{id}` (and unlike the `connections`/`workflows` lists on `/v1/mcpservers`), an omitted member here is simply left alone. `{"settings": {"logLevel": "Debug"}}` changes the log level and nothing else.

  **The agent token is a credential**

  * `POST /v1/on-prem-agents` **returns the token in the create response**, so a box can be provisioned in one round-trip. That means it lands in shell history and CI logs; pipe it to a secret store, or fetch it later with `GET /v1/on-prem-agents/{id}/token` instead.
  * **Rotation is destructive to a running agent.** The old token stops working the instant the call returns, and the agent is down until someone reconfigures it with the new one. The API requires `?confirm=true`; the CLI confirms on a TTY and demands `--force` otherwise.
  * Not-found errors echo the agent **id**, which is not a secret - unlike MCP server tokens.

  **The installer manifest**

  `GET /v1/on-prem-agents/releases/latest` is anonymous and returns `{version, platform, url, sha256, sizeBytes, releasedAt, degraded}`. `degraded: true` means the published release manifest could not be read and the result was reconstructed from the installer URL alone - `sha256`, `sizeBytes`, and `releasedAt` are then `null`. A caller that requires integrity verification should refuse a degraded manifest. The checksum proves the download was not corrupted; it does **not** prove authenticity, since the manifest lives beside the installer.

  **CLI: the `engini opa` group**

  ```bash theme={null}
  engini opa list [--status] | get | create --name | update | delete | enable | disable
  engini opa token get <id> | token rotate <id> [--force]
  engini opa logs <id> -o <file>            # the agent must be Online
  engini opa download [-o <file>] [--json]  # --json prints the manifest, downloads nothing
  ```

  * Full reference: [On-prem agents](/developers/cli/command-reference#on-prem-agents). Exit codes and output shapes: [machine contract](/developers/cli/machine-contract#on-prem-agent-commands).
  * **No exit code was added.** `OPA_AGENT_OFFLINE` exits `1` (a retriable operational state, not a caller error); `OPA_AGENT_TIMEOUT` exits `124`; an out-of-range setting or a missing `applicationSlug` exits `5`.
  * `logs` and `download` write to a **file** and print a JSON receipt. Neither ever writes binary to stdout. `download` verifies the `sha256` when the manifest carries one and refuses to write on a mismatch.

  **SDKs: `client.opa`**

  <CodeGroup>
    ```python Python theme={null}
    agent = client.opa.create("Warehouse SQL", pull_period_seconds=30)
    print(agent.token)                                   # a credential - store it, do not log it

    r = client.opa.update(agent.agent_id, log_level="Debug")   # merges; only log_level is sent
    if not r.applied_to_agent:
        print("stored; the agent will apply it on its next poll")

    zipped = client.opa.download_logs(agent.agent_id)    # bytes; raises EnginiOpaAgentOfflineError if not Online
    ```

    ```typescript TypeScript theme={null}
    const agent = await client.opa.create({ name: "Warehouse SQL", pullPeriodSeconds: 30 });
    console.log(agent.token);                             // a credential - store it, do not log it

    const r = await client.opa.update(agent.agentId, { logLevel: "Debug" }); // merges
    if (!r.appliedToAgent) console.log("stored; the agent will apply it on its next poll");

    const zipped = await client.opa.downloadLogs(agent.agentId); // Uint8Array; throws EnginiOpaAgentOfflineError if not Online
    ```
  </CodeGroup>

  * Five new typed errors in both SDKs, each a subclass of the status-mapped error it refines so existing `catch` blocks keep working: `EnginiOpaAgentNotFoundError` (404), `EnginiOpaAgentOfflineError` and `EnginiOpaApplicationAmbiguousError` (409 - the latter carries `candidate_slugs`/`candidateSlugs`), `EnginiOpaAgentTimeoutError` (504), `EnginiOpaAgentDispatchError` (502).
  * **Every API error now exposes the wire `errorCode` and `details`** - `err.error_code` / `err.details` in Python, `err.errorCode` / `err.details` in TypeScript. Messages are unchanged. This was needed because three OPA conditions share a status and differ only by code; it applies to every error the SDKs raise, not just OPA ones.
  * `rotate_token()` / `rotateToken()` send `confirm=true` unconditionally - the human gate lives in the CLI, not in library code.
  * The Python SDK now requires `engini-client>=0.8.0,<0.9.0`; `@engini/sdk` pins `@engini/client` `0.8.0`. 0.7.0 has none of the generated `/v1/on-prem-agents` operations.
</Update>

<Update label="2026-09-02" description="CLI/SDK 0.18.0 - maintain your MCP servers over the API, SDKs, and CLI">
  `engini-client` **0.7.0** · `engini` / `@engini/sdk` **0.18.0** · `@engini/cli` **0.18.0**.

  The MCP servers your account **exposes** are now readable and configurable outside the engini.io UI: a new `/v1/mcpservers` group, a `client.mcp` resource in both SDKs, and an `engini mcp` command group.

  This is the endpoint an MCP client points at - not the `MCPClient` connection kind, which is Engini *consuming* someone else's MCP server. See [Connect via MCP](/developers/ai-agents/connect-via-mcp).

  **New endpoints**

  | Operation | Path |
  | - | - |
  | List MCP servers | `GET /v1/mcpservers` |
  | Get MCP server | `GET /v1/mcpservers/{mcpServerToken}` |
  | Update MCP server | `PATCH /v1/mcpservers/{mcpServerToken}` |
  | List a connection's available tools | `GET /v1/mcpservers/{mcpServerToken}/available-tools?connectionId=` |

  * Servers are **account-wide**, not per-user: an API key sees every server on the account.
  * `GET /v1/mcpservers` uses the standard paginated envelope. `top` is silently **clamped to 1-100** and a negative `offset` is treated as `0` - neither is an error, so read `offset`/`top` back off the response rather than assuming what you asked for.
  * Deactivated servers still appear in `list`, reporting `isActive: false`. Deactivating is a reversible on/off switch, not a soft delete.

  **No create, no delete - by design**

  * There is no `POST /v1/mcpservers`, no `DELETE`, no `engini mcp create`, and no `engini mcp delete`. Unlike [toolsets](/developers/sdk/toolsets), authoring and destroying an MCP server stay in the engini.io UI, which issues the SSE URL and owns the anonymous-access setting.
  * `isAnonymousAllowed` is **read-only** here and is preserved by every update.
  * To take a server out of service, `engini mcp deactivate` / `PATCH {"isActive": false}`.

  **`connections` and `workflows` replace, they do not merge**

  * On `PATCH`, an omitted member is left unchanged, but a **supplied `connections` or `workflows` list replaces the entire set**. A body carrying one connection drops every other connection on that server.
  * Calling the API directly, you must read the server, modify the list you got back, and send all of it. Every connection entry needs an explicit `connectionId` - MCP servers are account-wide, so there is no per-user default connection to resolve one from.
  * The CLI and SDK CLI-side helpers do that read-modify-write for you, which is why the surface is `add`/`rm` rather than a single `--connections` flag. It is last-writer-wins (the API has no `ETag`/`If-Match`), so preview with `--dry-run`, which prints the full resulting set plus what was added, removed, or re-scoped.
  * A workflow entry is keyed by **`workflowToken`**, not `workflowId`.

  **The server token is secret-equivalent**

  * `mcpServerToken` is the `{server-token}` component of the server's SSE URL (`https://mcp.engini.io/{server-token}/sse`). Keep it out of shared logs, CI output, screenshots, and tickets, and redact it as you would an API key.
  * It is not a bearer credential on its own - an authenticated client still runs the [OAuth 2.1 flow](/developers/ai-agents/connect-via-mcp#authentication) - but it names a live endpoint, so it does not belong in anything you publish.
  * The SDKs and CLI deliberately do not echo the token back in a not-found error (`Unknown MCP server token.`).

  **CLI: the `engini mcp` group**

  ```bash theme={null}
  engini mcp list | get | activate | deactivate
  engini mcp connections add|rm      # which connections the server exposes
  engini mcp tools list|set          # which of a connection's tools it exposes
  engini mcp workflows add|rm        # which workflows it exposes as tools
  ```

  * Full reference: [MCP servers](/developers/cli/command-reference#mcp-servers). Output shapes and per-failure exit codes: [machine contract](/developers/cli/machine-contract#mcp-server-commands).
  * **No exit code changed.** The group reuses the existing table - `2` usage, `4` unknown server or a binding that is not on it, `5` a server-side rejection.

  **SDKs: `client.mcp`**

  <CodeGroup>
    ```python Python theme={null}
    servers = client.mcp.list()                      # includes deactivated ones
    client.mcp.deactivate(servers[0].mcp_server_token)   # reversible; sends only is_active
    tools = client.mcp.available_tools(token, connection_id=2139)   # candidates for tool_slugs
    ```

    ```typescript TypeScript theme={null}
    const servers = await client.mcp.list();                    // includes deactivated ones
    await client.mcp.deactivate(servers[0].mcpServerToken);     // reversible; sends only isActive
    const tools = await client.mcp.availableTools(token, 2139); // candidates for toolSlugs
    ```
  </CodeGroup>

  * `update()` replaces `connections`/`workflows` rather than merging them; there is no `create()` or `delete()`.
  * An unknown token raises `EnginiMcpServerNotFoundError` (new, in both SDKs), which the CLI maps to exit `4`.
  * The Python SDK now requires `engini-client>=0.7.0,<0.8.0`; `@engini/sdk` pins `@engini/client` `0.7.0`. 0.6.0 has none of the generated `/v1/mcpservers` operations.
</Update>

<Update label="2026-08-30" description="CLI/SDK 0.17.0 - client version telemetry and update notices">
  `@engini/cli`, `@engini/sdk`, and `engini` (PyPI) **0.17.0**. Every request now identifies the
  calling client and runtime on the wire, and the CLI tells you when a newer version is out.

  **Versioned `User-Agent`**

  * Every API call now sends a `User-Agent` identifying the SDK and runtime, e.g.
    `engini-cli/0.17.0 engini-sdk-ts/0.17.0 node/20.11.0 darwin-arm64`. The Python SDK sends the
    equivalent `engini-sdk-python/0.17.0 python/3.11.6 darwin`.
  * If you embed the SDK in your own product, prepend your own token with `userAgentPrefix` (TS) /
    `user_agent_prefix` (Python) on the `Engini` constructor - see the [TypeScript
    SDK](https://www.npmjs.com/package/@engini/sdk) and [Python SDK](https://pypi.org/project/engini/)
    READMEs.

  **CLI: "update available" notice**

  * On an interactive terminal, the CLI checks once a day for a newer release on your current
    channel and prints a one-line notice to stderr - see [Upgrading](/developers/cli#upgrading).
  * Never shown under `--json`, `--quiet`, in CI, when piped, or on a usage error (a typo'd command
    stays silent). Opt out entirely with `ENGINI_NO_UPDATE_CHECK=1`.
</Update>

<Update label="2026-08-30" description="Connection kind - Keys over the API, SDKs, and CLI">
  `engini-client` **0.6.0** · `engini` / `@engini/sdk` **0.16.0** · `@engini/cli` **0.16.0**.

  The Connections page's **Keys** tab is now reachable from the API, both SDKs, and the CLI. A Key is not a new resource - it is a connection whose application takes an API key rather than an OAuth grant - so this adds a discriminator and a filter rather than a parallel surface. See [Keys vs Integrations](/developers/get-started/platform-model#keys-vs-integrations).

  **`kind` on responses**

  * Connection and application objects now carry **`kind`**: one of `Regular`, `Key`, `OPA`, or `MCPClient`.
  * `kind` is **always present** on `/v1` responses and is required by the generated clients. If you build fixtures or mocks from the examples in these docs, include it.

  **`?kind=` filter**

  * `GET /v1/connections?kind=Key` and `GET /v1/applications?kind=Key`. Values are case-insensitive, so `?kind=key` works.
  * An unrecognised value is a `400` listing the accepted ones - the filter is never silently ignored.
  * On `/v1/connections`, `?kind=OPA` and `?kind=MCPClient` are accepted but return an empty page: that endpoint serves only `Regular` and `Key` connections.

  **Behaviour change - `GET /v1/applications` returns more rows**

  `GET /v1/applications` previously hid any application that was not MCP-enabled. Key applications are now exempt from that filter, because a Key application exists so a workflow can hold a credential for it and need not expose a single tool - excluding them made Keys undiscoverable. **A caller that paginates the catalogue will see a larger `totalCount`.** Nothing is removed, and `?kind=Key` remains a true subset of the unfiltered list. This is the only behavioural change in this release; everything else is additive.

  **New CLI commands**

  * `engini connections update <id>` - change a connection's name, description, credential fields, or metadata.
  * `engini connections replace --workflow <id> --from <id> --to <id>` - point **one workflow** at a different connection.
  * `engini connections defaults` / `set-default <id>` / `clear-default <app>` - manage per-application defaults.
  * `engini connections list --kind <kind>` - filter by kind; `--kind Key` is the web UI's Keys tab.

  <Warning>
    `engini connections update --field` and `--metadata` **replace their entire map** - they do not merge. Rotating one credential with a single `--field` clears every other field on the connection. Read the current values first, or preview with `--dry-run` (secrets are masked in the preview).
  </Warning>

  **New SDK methods** (Python and TypeScript)

  * `connections.update(...)` - same replace-not-merge semantics as the CLI.
  * `connections.replace(...)` - workflow-scoped, not a global swap.
  * `connections.list(..., kind=...)` / `connections.list(application, kind)`.

  No exit codes changed. See [exit codes](/developers/cli/machine-contract#exit-codes).

  <Note>
    Clients on these versions expect `kind` on every connection and application response. Point them at an API that emits it; if you self-host or target an environment that has not been updated yet, stay on the previous client release until it has.
  </Note>
</Update>

<Update label="2026-08-25" description="CLI 0.13.0 - login prompt and interactivity">
  `@engini/cli` and `@engini/sdk` **0.13.0**. Fixes the interactive `engini login` prompt and tightens when the CLI will prompt at all.

  **`engini login`**

  * The key prompt is now visible and gives feedback: masked input (one `*` per character) plus a confirmation of what was captured (`✓ Received 44 characters — eng_…wxyz`). Previously the prompt label was erased by the terminal on the first keypress and nothing was echoed, so there was no way to tell whether a paste had registered.
  * A key must start with `eng_`. Empty, malformed, and line-broken values are rejected on **both** `--api-key` and the interactive prompt; the prompt re-asks up to three times. Nothing is written on any rejection.

  **Behaviour changes worth checking if you script the CLI**

  * Prompting now requires a terminal on **both** stdin and stdout. Previously only stdout was checked, so `engini login < /dev/null` walked into the prompt and exited `0` having stored nothing.
  * `engini login --api-key <malformed>` now exits `2` instead of `0`. It previously stored the value and reported success.
  * `engini connections delete` and `engini toolsets delete` exit `2` instead of `0` when stdin is piped and `--force` is absent.
  * `engini connect` emits its `need:*` payload and exits `5` - instead of exiting `2` - when stdout is a terminal but stdin is piped.
  * Ctrl-D now aborts a prompt (exit `2`) rather than being ignored.

  See [exit codes](/developers/cli/machine-contract) for the full table, including the prompt-specific cases.
</Update>

<Update label="2026-08-15" description="Initial public release">
  The Engini Developer API `v1` is available to early adopters, alongside the Python and TypeScript SDKs, the `engini` CLI, and MCP access.

  **Surface**

  * **Applications** - browse the connector catalog and each application's authentication methods
  * **Connections** - create and manage connections (direct credentials or OAuth), defaults, object selection
  * **Tools** - discover tools, read their JSON Schemas, execute them
  * **Toolsets** - bundle connections and permitted tools to scope an agent
  * `GET /v1/auth/whoami` - verify a credential

  **Conventions**

  * JSON is camelCase throughout; applications and tools are addressed by **slug**, connections by integer id, toolsets by GUID
  * Offset-based pagination (`offset` / `top`) with a `{ items, totalCount, offset, top }` envelope
  * One [error envelope](/developers/get-started/pagination-errors-rate-limits) for every non-2xx response, with field-level `details` on validation failures
  * `POST /v1/tools/{toolSlug}/execute` returns `200` with `isSuccess: false` for tool-runtime failures - always branch on `isSuccess`

  **Authentication**

  * API keys (`x-api-key: eng_…`) or Bearer JWTs; [OAuth 2.1](/developers/get-started/oauth-apps) for apps acting on behalf of Engini users, including MCP clients

  **SDKs & CLI**

  * Python: `engini` **0.11.0** (PyPI) · TypeScript: `@engini/sdk` **0.12.0** (npm) - typed errors, auto-pagination, OAuth helpers, LLM tool-schema providers
  * CLI: `@engini/cli` **0.12.0** (npm - the only CLI distribution; `pip install engini` is the SDK and ships no CLI) - includes [`toolsets` management](/developers/cli/command-reference#toolsets) with `--dry-run` diffs
</Update>


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