Skip to main content

How integrations are structured

An integration is a package that exports a validated definition. Everything you declare upfront shapes how Ekanos treats your integration.

One call, complete declaration

Every integration starts with defineIntegration():
One object, validated at import time. If something is wrong, defineIntegration() throws a remediation message immediately.

The main surfaces

Widgets

Dashboard cards that render in the grid. They read clientReadable storage with fetchIntegrationStorage(), call APIs from the browser with useIntegrationFetch(), and write through actions with useIntegrationAction(). (ctx.fetch is server-side only.)

Tools

Functions the Ekanos assistant can call. They receive user intent, call your vendor API, and return structured results. The assistant reads the description and parameters to understand what they do.

Webhooks

Inbound events from your provider. You declare the expected payload schema and a handler. The host verifies the signature and calls your handler with the validated payload.

Schedules

Cron-driven jobs. Same ctx as every other handler; the handler receives a ScheduleInvocation and returns { status: 'completed' | 'skipped' }.

Actions

Deterministic write handlers invoked by a widget’s Save button. Unlike tools, they have no LLM governance — a human clicks, the handler runs, the result comes back.

The context (ctx)

Every server-side handler (tools, webhooks, schedules, actions, oauth.onTokens, onActivate) receives the same capability context:
There is no raw database client, no Supabase, no request object. Everything is scoped before your code runs.

Storage schema

Declare what state you’ll hold:

Reading and writing

get returns null for a missing key and for an entry past its expiresAt. A read whose stored data no longer matches the schema throws StorageValidationError rather than giving you a shape you didn’t expect. import type { StorageSchemas } from '@ekanos/sdk'. A key’s part before the / must be an allowed data type for its scope: activation, settings, metrics_summary, sync_state, or cache for account; config, preferences, or cache for user.

Egress allowlist

Declare the origins your handlers call:
ctx.fetch refuses anything not covered with EgressDeniedError, before opening a socket — redirect targets included. Entries are https origins only: no path, query, fragment, credentials, or http:. Widening this list is a security-posture change reviewed at promotion.

Naming rules

Everything validates upfront: Invalid names fail validation with a message that states the rule.

Where everything lives

A typical project after ekanos init and a first ekanos dev (the components/ layout is up to you):
ekanos.json’s entry points at the module to validate. The CLI takes its default export if it has a string slug, otherwise the first named export that does. The scaffold exports it as integration.

The full lifecycle

  1. Author — Write src/integration.ts with defineIntegration()
  2. Validate — ekanos validate checks syntax, naming, and structure
  3. Test — ekanos test runs your project’s test script
  4. Dev — ekanos dev runs the harness and renders everything locally
  5. Publish — ekanos publish validates, packs, and submits
  6. Gate — The host’s submission gate checks the archive (for example checks/typecheck and checks/tests); follow it with ekanos status --watch
  7. Connect — A user connects through your activation form or OAuth flow
  8. Run — Handlers execute on schedule, webhook delivery, assistant tool call, or widget action
defineIntegration(), ekanos validate, and the host all validate against the same @ekanos/integration-schema schema. There is no second contract.