Skip to main content
Live mode lets you test your integration against real vendor APIs while keeping Ekanos mocked. The toolbar’s Data dropdown switches between Fixtures and Live API; in live mode, the harness stops seeding your data keys, your widgets’ own query functions run, and their requests reach your actual API under your declared egress.

Setup

Declare a live block on your integration entry:
Now the toolbar’s Data dropdown’s Live API option is enabled for this integration.

Egress validation

The harness enforces your definition’s egress allowlist in live mode. A browser request to an undeclared origin throws EgressDeniedError (from @ekanos/sdk/context, code: 'EGRESS_DENIED'):
Allowed requests are sent with redirect: 'error', so a redirect to another origin fails rather than being followed. This is a harness convenience, not a preview of production enforcement: production runs no egress check on the client path today. Flipping to live mode is still a real test of the declaration under review, not of a second list kept beside it.
Do NOT set live.egress if definition is set. Supplying both is a hard error naming your slug. The harness reads the allowlist from your definition, so there is only one list and it is the one a reviewer reads. live.egress exists only for an entry with no definition.

Credential storage

Live mode reads admin-tier credentials (the OAuth client id and secret, and signed webhooks’ secrets) from .ekanos/local-secrets.json in your project root (gitignored, never packed by ekanos publish, read on every request). The harness generates the file on first use. OAuth tokens are not written to it. On the Activation or Triggers surface:
  1. Find the Local OAuth panel (shown for integrations that declare oauth, in either mode)
  2. Follow the checklist to create a developer app at your vendor’s console
  3. Paste the client id, secret, and any webhook signing secrets into the form (one field per secret name your definition declares)
  4. Click Save credentials
The shell writes them to .ekanos/local-secrets.json and never echoes values back. A saved key shows as filled beside an empty field; a blank field keeps what is saved. Saving, or editing a value in the file, rebuilds the live context and forgets any current connection.
The OAuth connection (tokens) lives in dev-server memory only: restarting the dev server forgets it and you Connect again. Activation data submitted through a plain activation form is also held in memory only, never written to localStorage, a fixture, or the registry, and is cleared on reload and on Disconnect.

Opting in

An integration with no live block is fixtures-only; the toolbar control is disabled for it and says why. The harness also forces fixtures mode while a live.egress is wider than the definition’s egress. One WITH a live block opens in fixtures mode by default. To use live mode:
  1. Select Live API in the toolbar’s Data dropdown, or
  2. Declare live: { default: 'live' } — for integrations whose widgets never call the vendor from the browser (every vendor call is server-side behind ctx.fetch)
A choice made in the toolbar wins over live.default and is saved in localStorage, so it survives reloads.

Keys that stay mocked (seeds)

live.seeds lists the react-query keys that stay mocked in live mode — Ekanos-side state your widgets read before they can call anything (a saved setting, an account preference). Everything not listed runs its real query function:
This is the boundary the design draws:
  • Mocked: Ekanos-side state
  • Live: your vendor
A query key with a seed never fetches, so an HTTP fixture behind it is unreachable. Use one or the other.

OAuth loop on localhost

For an integration with oauth declared:
1
Switch to Live API (the entry needs a live block).
2
On the Activation or Triggers surface, find the Local OAuth panel.
3
Follow the checklist to set up a developer app at your vendor’s console. It is derived from your declaration: the redirect URI http://localhost:3100/api/integrations/<slug>/oauth/callback on the default port (Copy button), the declared scopes, and the code grant (with PKCE when declared). Add oauth.setup to the entry for the console link and key names it cannot know.
4
Paste the client id, secret, and any webhook signing secrets into the form and click Save credentials.
5
Click Connect in the activation dialog. The shell’s oauth/connect route builds the authorize URL (PKCE when declared) and sends the browser to your vendor.
6
Your vendor redirects back to oauth/callback, which verifies the single-use state, exchanges the code through ctx.fetch (an undeclared token-endpoint origin fails with EgressDeniedError), and awaits your onTokens handler on the live context.
7
oauth/status then answers connected, the SDK’s OAuthActivationForm auto-activates, and onActivate runs on the same context.
8
Your widgets read what the handlers wrote through fetchIntegrationStorage(), unchanged. An empty read runs your declared schedules once first, as the host does.
The connection is memory-only. Restarting the dev server forgets it. The API routes refuse any request whose Host is not loopback unless EKANOS_HARNESS_EXPOSED=1; ekanos dev --host sets that after printing a warning.

API routes available

The shell’s API route (createHarnessApiHandlers from @ekanos/harness/api) serves these under /api/integrations/<slug>/: In live mode the browser passes the active integration’s requests to these routes. local-setup and actions/<actionName> are reached in both modes. The routes run against one server-held context per integration, whose ctx.fetch reaches your vendor under the declared egress.

Widget data fetching

Use useIntegrationFetch() from @ekanos/sdk/hooks. The harness mounts IntegrationFetchProvider and IntegrationActivationDataProvider unconditionally, so the hook returns a fetch already gated for whichever mode the toolbar is in, and useActivationData() returns what was last submitted to the activation form (null before any submission or after Disconnect):
In fixtures mode, fetch is answered from your fixtures. In live mode, it reaches your real API.
Taking an IntegrationFetch as an argument lets the same query function serve ctx.fetch on the server and this hook’s fetch on the client. In the harness the client fetch is gated by the harness’s own check; in production today it is a plain, unmediated browser fetch.
The live.FetchProvider field is deprecated. It still works and receives fetch, mode, and activationData as props; use it only to compose an authenticated fetch once at a single seam.

Knowing which mode is active

Call useHarnessDataMode() from @ekanos/harness/hooks. It has no SDK equivalent, so call it only from code that runs nowhere but the harness:
It returns the EFFECTIVE mode—accounting for an integration with no live block or a live.egress/definition mismatch (both force fixtures regardless of the toolbar).

Troubleshooting

EgressDeniedError in live mode

The origin is not in your definition’s egress. Add it or fix a typo in your URL.

NoRecordedResponseError in live mode

The request was a same-origin /api/… request outside the active integration’s own /api/integrations/<slug>/… routes. Those are answered from fixtures in both modes; record a fixture for it, or call your vendor with an absolute URL.

Can’t find where credentials were saved

Check .ekanos/local-secrets.json in your project root. The file is gitignored and never committed.

Changing credentials

Live connections are memory-only and forgotten on restart. To change credentials, save new values in the Local OAuth panel or edit .ekanos/local-secrets.json; either forgets the current connection.