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.- Render through the
Widget.*compound API from@ekanos/sdk/components - Read
clientReadableaccount storage withfetchIntegrationStorage()(@ekanos/sdk/hooks) - Call APIs from the browser with
useIntegrationFetch()(@ekanos/sdk/hooks) - Write through a declared action with
useIntegrationAction() isPinnableadds “Pin to board” / “Unpin from board” to theWidget.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.- 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
parametersis a narrow JSON Schema slice: the top level accepts onlytype: 'object',properties,required, andadditionalProperties- 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.).- 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. Samectx as every other handler.
When to use: Regular syncing (hourly reconcile, daily summary) or maintenance tasks.
- 5-field cron syntax (minute hour day-of-month month day-of-week)
- Numeric only, with
*, lists1,15, ranges1-5, and steps*/15,0-30/5— no names, no@dailymacros, 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.triggeris'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).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)
inputis a required zod schema;outputis an optional zod schema — without it, the resolved value passes through unvalidated- A thrown error becomes a generic
handler_errorresponse; throwPublicActionError(@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, noproposesclassification)
OAuth
Secure connection to a third-party provider. The transport runs the authorize flow and code exchange, then calls youronTokens hook — you persist the tokens.
When to use: Connecting to a provider that requires OAuth (versus API key).
- Both endpoint origins must be covered by your
egressallowlist —defineIntegration()rejects the declaration otherwise - Tokens are passed to
onTokens; store them withctx.secrets.set() - The redirect URI is host-owned and built from the host’s own origin — don’t declare one (
provider.redirectUriis 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.
- 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
activationDataupdates - Runs in the harness’s Activation surface and under
invokeActivate(); the production host does not call it yet