/v1 API are announced here before they ship. The OpenAPI spec is the source of truth; the SDKs are generated from it.
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_BLOCKEDonPOST /v1/tools/{toolSlug}/execute. A tool call resolved against a connection with no licence, or an expired one, now returns403and the tool is not executed - unlike an ordinary tool-runtime failure, which is a200withisSuccess: false. See the error code table.- 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/whoamifailures pending an internal repin.
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 for the model and the delivery contract, then Triggers in the SDKs, the engini triggers commands, or the new Triggers group in the API reference. The Cookbook has three new recipes under React to events: receive events on a webhook, debug and replay a failed delivery, and develop a trigger locally.EndpointsNew 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
2xxis 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 other4xx, 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.
engini triggers createno longer requires--connectionfor connection-less trigger types (engini_schedule,http_webhook). On0.20.0-0.22.xit exited2without one.Ctrl-Cnow stopsengini triggers listenand exits0. On0.20.0-0.22.xan idle stream ignored the interrupt.
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.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 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 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.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 createno 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 back400 EMPTY_QUERY, which the CLI rewrites to name--connectionand exits5- not the2of0.20.0-0.22.x. An explicit empty value (--connection "") still exits2.Ctrl-Cnow stopsengini triggers listen. It closes the stream and any in-flight--forwardPOST, prints the usual summary line, and exits0- even if some forwards had failed. On0.20.0-0.22.xan idle stream never noticed the interrupt at all. The TypeScript SDK’sclient.triggers.subscribe()grows a matchingsignaloption (anAbortSignal) that the CLI wires to this handler; aborting returns the generator normally rather than throwing.engini connect’s OAuthresumestring is now replayable. It carries--auth <id>and echoes back any--fieldvalues you originally passed (a secret’s value is shown as<value>), instead of dropping them on the OAuth path. Aresumereplayed afterstate_expires_in_seconds(300s) now fails fast with exit4, rather than polling out the full timeout.
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.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>.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.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.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 changeskind=OPAis no longer accepted onGET /v1/connectionsorGET /v1/applications. It returns400(INVALID_ENUM_VALUE) naming/v1/on-prem-agents; the CLI exits5. 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=MCPClientis unchanged and still accepted.GET /v1/connections/{connectionId}now returns404for an on-prem agent (and for anMCPClientconnection), matching what the list endpoint has always excluded. Before this, detail-by-id answered for connections the list would never show.
- Every per-agent route returns
404when 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-agentsuses the standard paginated envelope;topis clamped to 1-100.
PATCH /v1/on-prem-agents/{id}always persists the change, then tries to hand it to the running agent. The response carriesappliedToAgentanddispatchedTaskIds.appliedToAgent: falseis not an error - the agent was notOnline(a freshly created agent isWaitingForConnectionuntil 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}/logsneeds the agentOnline. Otherwise it returns409 OPA_AGENT_OFFLINEnaming the agent’s actual status; if the agent does not answer within the server-side bound it returns504 OPA_AGENT_TIMEOUT. The CLI exits1and124respectively.
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 replaceUnlike 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 credentialPOST /v1/on-prem-agentsreturns 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 withGET /v1/on-prem-agents/{id}/tokeninstead.- 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--forceotherwise. - Not-found errors echo the agent id, which is not a secret - unlike MCP server tokens.
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- Full reference: On-prem agents. Exit codes and output shapes: machine contract.
- No exit code was added.
OPA_AGENT_OFFLINEexits1(a retriable operational state, not a caller error);OPA_AGENT_TIMEOUTexits124; an out-of-range setting or a missingapplicationSlugexits5. logsanddownloadwrite to a file and print a JSON receipt. Neither ever writes binary to stdout.downloadverifies thesha256when the manifest carries one and refuses to write on a mismatch.
client.opa- Five new typed errors in both SDKs, each a subclass of the status-mapped error it refines so existing
catchblocks keep working:EnginiOpaAgentNotFoundError(404),EnginiOpaAgentOfflineErrorandEnginiOpaApplicationAmbiguousError(409 - the latter carriescandidate_slugs/candidateSlugs),EnginiOpaAgentTimeoutError(504),EnginiOpaAgentDispatchError(502). - Every API error now exposes the wire
errorCodeanddetails-err.error_code/err.detailsin Python,err.errorCode/err.detailsin 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()sendconfirm=trueunconditionally - 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/sdkpins@engini/client0.8.0. 0.7.0 has none of the generated/v1/on-prem-agentsoperations.
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.New endpoints- Servers are account-wide, not per-user: an API key sees every server on the account.
GET /v1/mcpserversuses the standard paginated envelope.topis silently clamped to 1-100 and a negativeoffsetis treated as0- neither is an error, so readoffset/topback off the response rather than assuming what you asked for.- Deactivated servers still appear in
list, reportingisActive: false. Deactivating is a reversible on/off switch, not a soft delete.
- There is no
POST /v1/mcpservers, noDELETE, noengini mcp create, and noengini mcp delete. Unlike toolsets, authoring and destroying an MCP server stay in the engini.io UI, which issues the SSE URL and owns the anonymous-access setting. isAnonymousAllowedis 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 suppliedconnectionsorworkflowslist 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/rmrather than a single--connectionsflag. It is last-writer-wins (the API has noETag/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, notworkflowId.
mcpServerTokenis 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 - 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.).
engini mcp group- Full reference: MCP servers. Output shapes and per-failure exit codes: machine contract.
- No exit code changed. The group reuses the existing table -
2usage,4unknown server or a binding that is not on it,5a server-side rejection.
client.mcpupdate()replacesconnections/workflowsrather than merging them; there is nocreate()ordelete().- An unknown token raises
EnginiMcpServerNotFoundError(new, in both SDKs), which the CLI maps to exit4. - The Python SDK now requires
engini-client>=0.7.0,<0.8.0;@engini/sdkpins@engini/client0.7.0. 0.6.0 has none of the generated/v1/mcpserversoperations.
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-Agentidentifying 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 equivalentengini-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 theEnginiconstructor - see the TypeScript SDK and Python SDK READMEs.
- 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.
- Never shown under
--json,--quiet, in CI, when piped, or on a usage error (a typo’d command stays silent). Opt out entirely withENGINI_NO_UPDATE_CHECK=1.
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.kind on responses- Connection and application objects now carry
kind: one ofRegular,Key,OPA, orMCPClient. kindis always present on/v1responses and is required by the generated clients. If you build fixtures or mocks from the examples in these docs, include it.
?kind= filterGET /v1/connections?kind=KeyandGET /v1/applications?kind=Key. Values are case-insensitive, so?kind=keyworks.- An unrecognised value is a
400listing the accepted ones - the filter is never silently ignored. - On
/v1/connections,?kind=OPAand?kind=MCPClientare accepted but return an empty page: that endpoint serves onlyRegularandKeyconnections.
GET /v1/applications returns more rowsGET /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 commandsengini 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 Keyis the web UI’s Keys tab.
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).
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.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-keyand the interactive prompt; the prompt re-asks up to three times. Nothing is written on any rejection.
- Prompting now requires a terminal on both stdin and stdout. Previously only stdout was checked, so
engini login < /dev/nullwalked into the prompt and exited0having stored nothing. engini login --api-key <malformed>now exits2instead of0. It previously stored the value and reported success.engini connections deleteandengini toolsets deleteexit2instead of0when stdin is piped and--forceis absent.engini connectemits itsneed:*payload and exits5- instead of exiting2- when stdout is a terminal but stdin is piped.- Ctrl-D now aborts a prompt (exit
2) rather than being ignored.
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
- 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 for every non-2xx response, with field-level
detailson validation failures POST /v1/tools/{toolSlug}/executereturns200withisSuccess: falsefor tool-runtime failures - always branch onisSuccess
- API keys (
x-api-key: eng_…) or Bearer JWTs; OAuth 2.1 for apps acting on behalf of Engini users, including MCP clients
- Python:
engini0.11.0 (PyPI) · TypeScript:@engini/sdk0.12.0 (npm) - typed errors, auto-pagination, OAuth helpers, LLM tool-schema providers - CLI:
@engini/cli0.12.0 (npm - the only CLI distribution;pip install enginiis the SDK and ships no CLI) - includestoolsetsmanagement with--dry-rundiffs