> ## 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.

# Capability context (ctx)

> The platform surface every server-side handler receives — storage, secrets, fetch, logging, and actor info.

`ctx` is the entire platform surface a server-side handler gets. There is no raw database client, no Supabase, no request object. Every field is scoped and authorized before your code runs.

## The context object

### Storage

Access to schema-validated per-account and per-user state:

```ts theme={null}
// Read
const saved = await ctx.storage.account.get('settings/location');
// → { data, externalId, expiresAt, updatedAt } | null

// Write (with optional expiry)
await ctx.storage.account.set('cache/forecast', data, {
  expiresAt: new Date(Date.now() + 600_000).toISOString(),
});

// Delete
await ctx.storage.account.delete('cache/forecast');
```

All three are async. `get` returns `null` for missing keys and expired entries. User scope throws for machine actors.

### Secrets

Named credentials resolved by the host across its tiers:

```ts theme={null}
// Get a secret (string | null if missing)
const apiKey = await ctx.secrets.get('acme_api_key');

// List secret names (values withheld)
const names = await ctx.secrets.names();

// Write to account tier only
await ctx.secrets.set('acme_access_token', token);
```

`set()` writes only to the account tier for this `{account, product}`. Source- and global-tier credentials are admin-issued and throw `SecretAccessError` if you try to write them.

### Fetch

HTTP client that refuses anything outside your `egress` allowlist:

```ts theme={null}
const response = await ctx.fetch('https://api.acme.example/v1/payments', {
  headers: { authorization: `Bearer ${apiKey}` },
});

if (!response.ok) {
  throw new Error(`API returned ${response.status}`);
}

const data = await response.json();
```

Denied calls throw `EgressDeniedError` synchronously, before opening a socket. Redirect targets are also checked.

### Logger

Structured logging with pre-bound context:

```ts theme={null}
ctx.logger.debug({ paymentId }, 'Processing payment.');
ctx.logger.info({ amount }, 'Payment recorded.');
ctx.logger.warn({ status }, 'Unexpected status.');
ctx.logger.error({ error }, 'Payment fetch failed.');
```

The first argument is a context object (pino-style), the second is the message. The host pre-binds `{integration, accountId, actor}` to every line.

### Actor

Who is calling — a human user or a machine (workflow):

```ts theme={null}
if (ctx.actor.kind === 'user') {
  console.log(ctx.actor.userId);
} else {
  // ctx.actor.kind === 'machine'
  console.log(ctx.actor.tokenId, ctx.actor.createdBy);
}
```

Machine actors cannot use user-scoped storage — every `ctx.storage.user` call throws.

## Identity fields

All read-only and already authorized:

```ts theme={null}
// Account and source
ctx.accountId; // string
ctx.accountSlug; // string | null
ctx.sourceId; // string | null

// User (null for machine actors)
ctx.userId; // string | null

// Integration metadata
ctx.integration.slug; // from definition
ctx.integration.productId;

// Timezone
ctx.timezone; // string | null
```

No id parameters to get wrong, and none to lie in. The platform fills all of these before your code runs.

## Common patterns

### Logging with context

Always include relevant IDs in the context object:

```ts theme={null}
ctx.logger.info(
  { paymentId, customerId, amount },
  'Payment processed successfully.',
);
```

### Handling missing credentials

Not configured is an expected state — log and skip, don't fail:

```ts theme={null}
const apiKey = await ctx.secrets.get('acme_api_key');
if (!apiKey) {
  ctx.logger.debug({}, 'API key not configured; skipping.');
  return { status: 'skipped' };
}
```

### Error handling

Log before throwing so errors reach your observability:

```ts theme={null}
const response = await ctx.fetch('https://api.acme.example/v1/data');
if (!response.ok) {
  ctx.logger.error({ status: response.status }, 'Fetch failed.');
  throw new Error(`Acme returned ${response.status}`);
}
```

## Where ctx is available

* **Tools** — `tools[].run(ctx, args)`
* **Webhooks** — `webhooks[].handler(ctx, event)`
* **Schedules** — `schedules[].handler(ctx, invocation)`
* **Activation hooks** — `onActivate(ctx)`
* **Actions** — `actions[name].handler(ctx, input)`
* **OAuth** — `oauth.onTokens(ctx, tokens)`

Every handler receives the same shape, scoped to the same `{account, integration}`.

## Next steps

* Read about [Storage](/sdk/surfaces/storage) to understand schema validation and expiry
* Learn [Secrets](/sdk/surfaces/secrets) for credential management
* Explore [Tools](/sdk/surfaces/tools) for assistant-callable functions


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