Skip to main content
See Actions for declaring actions and the full useIntegrationAction() reference. Actions let a widget call a declared server handler directly—no assistant, no natural language—with input validation and fixed error codes. The harness serves these handlers locally in both fixtures and live mode.

Overview

useIntegrationAction() from @ekanos/sdk/hooks makes a real fetch() to POST /api/integrations/<slug>/actions/<actionName>. The harness route answers it by running your declared handler against a mock (fixtures) or live context.
Your definition declares the action in the actions record, keyed by a kebab-case name:
The theme key must be declared in your definition’s storage schemas.

Fixtures mode

In fixtures mode, the route validates the posted input against the action’s input schema, then runs the handler against a mock context built with createMockContext() from your definition and triggerMocks, one per integration and fixture variant.
ctx.fetch is answered from triggerMocks.fetchHandlers first, then from the active variant’s fixtures, and never the real network. The context lives in dev-server memory, so state accumulates across action calls on the same variant; each variant has its own context.

Live mode

In live mode, the route runs the handler against the same server-held context that OAuth and live webhooks/schedules use—its ctx.fetch reaches your real vendor under the declared egress, with admin credentials from .ekanos/local-secrets.json and whatever onTokens or the activation flow stored. Live mode requires your entry’s live block. An integration with no live block stays on fixtures for actions.

Session confirmation

The first action call on an integration while the toolbar is set to Live API raises a blocking browser confirmation before anything is sent:
Accepting covers every later action call on that integration for the rest of the browser session (tracked in sessionStorage, so it survives a reload and ends when the tab closes). Declining throws LiveActionNotConfirmedError and the request is never sent. Fixtures mode never prompts.
This is a harness convenience against accidental clicks during iteration, not a production security boundary. Production runs exactly one mode, with no toggle and no per-session confirmation.

Response codes

The route answers the same fixed codes as the production host route. useIntegrationAction()’s rejected mutation surfaces them as an IntegrationActionError (exported from @ekanos/sdk/hooks) with message, status, code, and issues?:

Handler errors

If your handler throws, the route returns handler_error with the generic message Action failed.. In production, a handler that throws PublicActionError has its own message returned verbatim under handler_error instead:
Read it from the mutation’s error:
The harness route always answers a thrown handler with Action failed., including for PublicActionError. For debugging it adds a dev-only devError field (message and stack) to the response body and prints the real error in the terminal running ekanos dev; production never returns devError.

Example: widget with actions

Troubleshooting

action_not_found

The action name doesn’t match any key in your definition’s actions. Check the second argument to useIntegrationAction().

invalid_input

The input failed the input schema. The error’s issues array holds the zod issues.

handler_error

The handler threw, or its result failed the output schema. In the harness, read devError in the response body or the ekanos dev terminal for the real error.

Live mode confirmation appears again

The confirmation is per integration and per browser tab session. A new tab, or a different integration, asks again.

Every action returns 500 handler_error in fixtures mode (older harness)

Before @ekanos/harness 0.4.0, a registry that declared a host-route fixture such as GET /api/integrations/<slug>/storage made every action call fail with handler_error before the handler ran, because the server-side context tried to compile that fixture. 0.4.0 skips host-route fixtures on the action path (an action’s ctx.fetch can only address a vendor origin anyway). Upgrade with ekanos upgrade.

No recorded response error in fixtures mode

A handler’s ctx.fetch call matched no triggerMocks.fetchHandlers entry and no fixture for the active variant. Record a fixture (see Fixtures).

Action works in fixtures but fails in live

Check the Local OAuth panel or .ekanos/local-secrets.json for missing admin secrets, and the terminal running ekanos dev for the handler’s error.