> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.ekanos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Frequently asked questions

> Answers to common questions about building Ekanos integrations

# Frequently asked questions

<AccordionGroup>
  <Accordion title="Do I have to use the harness?">
    No, but it is the only way to see your widgets, marketplace tile, and activation form in Ekanos chrome before publishing. `ekanos dev` also runs your declared webhooks and schedules on its Triggers surface.

    If your integration has no UI (tools only), `ekanos test` with `@ekanos/sdk/testing` covers your handlers.
  </Accordion>

  <Accordion title="Can I use TypeScript?">
    Yes — the scaffold is a TypeScript project, and the host gate typechecks your
    submission (`checks/typecheck`, then `checks/typecheck-host-rules` under
    `strict` and `noUncheckedIndexedAccess`). Your code must compile cleanly under
    those rules.
  </Accordion>

  <Accordion title="How do I handle schema evolution?">
    Storage schema changes are manual. A stored value that no longer matches its schema throws `StorageValidationError` when read. Two approaches:

    1. **Add a new key** — for example `settings/location-v2` with the new schema, and migrate old data on read. (Keys allow only `[a-z0-9_-]`, so `.v2` is not valid.)
    2. **Union the schemas** — use `z.union([OldSchema, NewSchema])` to accept both.

    When adding a field to an existing schema, make it `.optional()` so rows written before the change still parse.
  </Accordion>

  <Accordion title="What's the difference between tools and actions?">
    **Tools** are for the assistant — the model reads the description and `parameters`, decides when to call them, and explains the result. Return `{ error: '…' }` for expected failures.

    **Actions** are for widgets — a user clicks Save, the widget calls `useIntegrationAction()`, and the declared handler runs deterministically with the values the user typed. `input` is validated with a zod schema; `output` optionally.

    Use tools for anything the assistant should do, actions for deterministic writes from your own UI.
  </Accordion>

  <Accordion title="Can widgets call the vendor API directly?">
    Yes. Use `useIntegrationFetch()` from `@ekanos/sdk/hooks` instead of bare `fetch()`. In the harness, live mode checks those requests against your `egress` list as a convenience. In production, no egress check runs on the client path — only `ctx.fetch` (server-side handlers) is enforced.

    `useActivationData()` gives a widget the activation data where the host provides it (it defaults to `null`). For credentials that must stay server-side, call the vendor from an action or tool handler through `ctx.fetch`.
  </Accordion>

  <Accordion title="How do I handle authentication?">
    Two paths:

    1. **Activation form** — The user enters credentials in your activation form; handlers read them with `ctx.secrets.get()`.
    2. **OAuth** — Declare `oauth` in your definition. The transport runs the flow, and your `onTokens(ctx, tokens)` stores the tokens with `ctx.secrets.set()`.

    Keep credentials in secrets, not storage: `ctx.storage` cannot declare the reserved `secret` data type, and `clientReadable` storage is browser-readable.
  </Accordion>

  <Accordion title="onActivate is declared but not called on the host?">
    Correct — the production host does not call `onActivate` yet. It runs in the
    harness (after a connect on the Activation surface) and in tests via
    `invokeActivate()`. Declare it and test it locally; no change on your side is
    expected when host wiring lands.
  </Accordion>

  <Accordion title="How does a widget write data?">
    Through an action. Declare a handler under `actions`, and call it from the
    widget with `useIntegrationAction()` from `@ekanos/sdk/hooks`. The handler
    runs server-side with the full `ctx`, so it can write `ctx.storage` or call
    your vendor through `ctx.fetch`. The browser storage route
    (`fetchIntegrationStorage()`) is read-only.
  </Accordion>

  <Accordion title="Can multiple integrations share a source?">
    Yes. Collision rules:

    * Slugs must be globally unique
    * Widget ids must be globally unique — prefix them with your slug
    * Tool names are compared by their slug-prefixed effective names, so two integrations can use the same raw tool name as long as the effective names differ
    * Webhook ids, schedule ids, and action names only need to be unique within one integration

    `ekanos validate` cross-checks integrations listed together in one `ekanos.json`. The host also checks your definition against first-party ids at registration.
  </Accordion>

  <Accordion title="How does the harness handle credentials?">
    In fixtures mode (the default), no credentials are needed — requests are answered from your recorded fixtures.

    In live mode, credentials come from:

    * **Your activation form** — the submitted activation data is kept in memory only and cleared on reload and on Disconnect
    * **`.ekanos/local-secrets.json`** — admin-tier secrets (OAuth client id and secret, webhook signing secrets) saved from the Local OAuth panel. The file is gitignored and never packed by `ekanos publish`

    A live OAuth connection lives in dev-server memory only; restarting the dev server forgets it.
  </Accordion>

  <Accordion title="Can I test webhooks locally?">
    Yes. `@ekanos/sdk/testing` provides `invokeWebhook()`:

    ```ts theme={null}
    import { invokeWebhook } from '@ekanos/sdk/testing';

    const { result, ctx } = await invokeWebhook(integration, 'payment-updated', {
      payment: { id: '123', status: 'succeeded' },
    });

    expect(result.status).toBe('processed');
    ```

    It runs your real handler against a mock context built from the definition (its slug, storage schemas, and egress). It throws if the webhook id is not declared or the payload fails `payloadSchema`. The harness's Triggers surface runs webhooks interactively.
  </Accordion>

  <Accordion title="What happens if my webhook handler throws?">
    A throw is a retryable failure. Return `{ status: 'processed' }` when you handled the event, or `{ status: 'ignored' }` for a valid but irrelevant event (both are 2xx, and the sender must not retry). Throw only when you could not handle a valid event.
  </Accordion>

  <Accordion title="Can I emit custom logging?">
    Yes. `ctx.logger` is pino-style, with `debug`, `info`, `warn`, and `error`:

    ```ts theme={null}
    ctx.logger.info({ paymentId, amount }, 'Payment processed');
    ctx.logger.warn({ attempt: 3 }, 'Retrying webhook');
    ctx.logger.error({ status }, 'API call failed');
    ```

    The first argument is the context object, the second the message. The host pre-binds `{ integration, accountId, actor }` to every line. In tests, the mock context records lines on `ctx.logs`.
  </Accordion>

  <Accordion title="How do I release a new version?">
    Bump `version` in your definition (and in `package.json` — `ekanos validate`
    warns when they disagree), then validate, test, and publish. `version` must be
    semver (`1.0.0`, optionally with a prerelease/build suffix); anything else
    fails validation. `ekanos status --watch` follows the submission for your
    current slug and `package.json` version.
  </Accordion>

  <Accordion title="Can I have multiple integrations in one package?">
    Yes. Use the list form in `ekanos.json` (declare one form or the other, never both):

    ```json theme={null}
    {
      "integrations": [
        { "slug": "acme-crm", "entry": "src/crm.ts" },
        { "slug": "acme-billing", "entry": "src/billing.ts" }
      ]
    }
    ```

    `ekanos validate` cross-checks them for colliding slugs, widget ids, and tool names.
  </Accordion>

  <Accordion title="What's the minimum viable integration?">
    Only `slug`, `name`, `description`, and `version` are required. A useful minimum is one widget:

    ```tsx theme={null}
    import { defineIntegration } from '@ekanos/sdk/integration';

    function HelloWidget() {
      return <div>Hello!</div>;
    }

    export const integration = defineIntegration({
      slug: 'hello-world',
      name: 'Hello World',
      description: 'A minimal integration.',
      version: '1.0.0',
      components: {
        widgets: [
          {
            id: 'hello-world-card',
            name: 'Hello World',
            component: HelloWidget,
            widgetState: 'active',
          },
        ],
      },
    });
    ```

    `component` must be a component reference (a function component, or a `memo`/`forwardRef`/`lazy` wrapper), not an element.
  </Accordion>

  <Accordion title="Where do I get help?">
    Email **[npm@govastly.com](mailto:npm@govastly.com)** with feedback, bugs, or questions.
  </Accordion>

  <Accordion title="Can I fork/copy an existing integration?">
    Yes, but give it a new slug — in both `ekanos.json` and the definition — and
    re-prefix its widget ids. Slugs and widget ids must be globally unique, so a
    copy that keeps them collides.
  </Accordion>

  <Accordion title="My widget shows the wrong data in the harness but works correctly in my own app. Why?">
    Common causes:

    1. **The fixture doesn't match your widget's URL** — Check the console for `[harness:<slug>] refused GET …`. The path and any pinned query params must match exactly.
    2. **A module-level promise cache** — A widget that starts its promise during render and caches it at module scope keeps serving the first variant's answer after a variant switch. Create the promise in a non-suspending parent and pass it across the Suspense boundary.
    3. **An omitted variant** — `empty` and `error` do not fall back to `default`; every request in an undeclared variant is refused.

    See the [harness fixtures docs](/harness/fixtures) for the full diagnostic.
  </Accordion>

  <Accordion title="Where do icons come from?">
    Icons are Font Awesome glyph names rendered through `@ekanos/ui/icon`. Ekanos
    loads Font Awesome Pro; the generated harness loads Font Awesome Free. A
    Pro-only (or mistyped) name renders a circle-question placeholder in the
    harness, and `ekanos validate` flags Pro-only names with an `icons/pro-only`
    warning.
  </Accordion>

  <Accordion title="Can I publish to more than one source or host?">
    Each project stores one publish target — `host` and `source` in `ekanos.json`, written by the first successful publish or by `ekanos use`. Target another source for a single run with `ekanos publish --source <slug>`, or with `--host` for another host. You need a seat on every source you publish to.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.