Skip to main content

Surfaces at a glance

Every integration declares surfaces — the things it renders or runs. Here’s what each one does and when to use it.

Widgets

Client-side components that render in the dashboard grid. When to use: Primary interface for viewing data, displaying metrics, or interactive controls.
Key points:
  • Render through the Widget.* compound API from @ekanos/sdk/components
  • Read clientReadable account storage with fetchIntegrationStorage() (@ekanos/sdk/hooks)
  • Call APIs from the browser with useIntegrationFetch() (@ekanos/sdk/hooks)
  • Write through a declared action with useIntegrationAction()
  • isPinnable adds “Pin to board” / “Unpin from board” to the Widget.Header ⋯ menu
  • Preview on the harness’s Widgets surface

Tools

Functions the Ekanos assistant can call. The assistant reads the description and parameters, decides when to invoke, and shows results to the user. When to use: Anything the assistant should be able to look up or do on the user’s behalf.
Key points:
  • Invoked by the assistant (and, where the host allows it, the workflow engine) — not deterministic
  • Return { error: '…' } for expected failures the model should explain
  • Throw for genuine faults
  • parameters is a narrow JSON Schema slice: the top level accepts only type: 'object', properties, required, and additionalProperties
  • The assistant sees a slug-namespaced name: acme-payments + list_recent_payments → acme_payments_list_recent_payments

Webhooks

Inbound events from your provider. Ekanos receives them, validates the signature and payload, and calls your handler. When to use: Responding to real-time events (payment status change, customer created, etc.).
Key points:
  • The handler receives a payload already validated against payloadSchema; a failing payload never reaches it
  • Return { status: 'processed' } when handled (2xx)
  • Return { status: 'ignored' } for valid but irrelevant events (2xx — the sender must not retry)
  • Throw when you could not handle a valid event (retryable failure)
  • Signature verification is the transport’s job — don’t verify it yourself. Use signature: 'none' for an unsigned source

Schedules

Cron-driven jobs that run at regular intervals. Same ctx as every other handler. When to use: Regular syncing (hourly reconcile, daily summary) or maintenance tasks.
Key points:
  • 5-field cron syntax (minute hour day-of-month month day-of-week)
  • Numeric only, with *, lists 1,15, ranges 1-5, and steps */15, 0-30/5 — no names, no @daily macros, no seconds field
  • Return { status: 'completed' } when the run did its work
  • Return { status: 'skipped' } when it correctly did nothing
  • Throw for failures — the transport records a failed run
  • invocation.trigger is 'schedule' or 'manual' (a human pressed “Run now”)

Actions

Widget-invoked handlers for deterministic writes. A user clicks Save in a widget, the action runs, the widget handles the result. When to use: Deterministic writes (save settings, create a record, delete something).
From the widget:
A rejected mutation’s error is always an IntegrationActionError with status, code, and — for a 400 invalid_input — your schema’s zod issues. Key points:
  • Invoked deterministically (a user clicks Save, not LLM intent)
  • input is a required zod schema; output is an optional zod schema — without it, the resolved value passes through unvalidated
  • A thrown error becomes a generic handler_error response; throw PublicActionError (@ekanos/sdk/integration) to show your own message verbatim
  • Response codes: action_not_found (404), invalid_input (400), rate_limited (429), handler_error (500)
  • No LLM governance (no parameters, no proposes classification)

OAuth

Secure connection to a third-party provider. The transport runs the authorize flow and code exchange, then calls your onTokens hook — you persist the tokens. When to use: Connecting to a provider that requires OAuth (versus API key).
Key points:
  • Both endpoint origins must be covered by your egress allowlist — defineIntegration() rejects the declaration otherwise
  • Tokens are passed to onTokens; store them with ctx.secrets.set()
  • The redirect URI is host-owned and built from the host’s own origin — don’t declare one (provider.redirectUri is an unrecognized key)
  • There is no host-driven token refresh yet
  • In the harness, live mode runs the real OAuth loop on localhost

onActivate

Optional hook, (ctx) => Promise<void>, for cache seeding and eager credential checks at connect time. When to use: Eager data loading or testing credentials at connect time.
Key points:
  • Errors are NON-FATAL (logged, shown as a warning, connection stays active) — it cannot reject a connection
  • Runs after an activation is first persisted AND after activationData updates
  • Runs in the harness’s Activation surface and under invokeActivate(); the production host does not call it yet

Summary table