Generated from `@cosmoops/connect-sdk/server`'s TypeScript source and its own doc comments - see [Reference](/docs/reference) for the other entry points. Regenerated on every deploy; don't hand-edit.

## Classes

### Class: ConnectClient [#class-connectclient]

The CosmoOps Connect application-partner API.

Covers `/api/v1/application/*` and nothing else - the surface an application platform
integrates against. Connect's internal API is not part of this package and
never will be.

```ts
const connect = new ConnectClient({
  apiKey: process.env.CONNECT_API_KEY!,
  clientId: process.env.CONNECT_CLIENT_ID,
});

const session = await connect.identity.exchangeCode({ code, redirectUri });
const { ready } = await connect.entitlements.getReadiness(session.user.workspaceId);
```

Every failure throws a `ConnectError` carrying a stable `code`. Nothing here
holds state between calls, so one client can be built once and shared.

#### Constructors

##### Constructor

```ts
new ConnectClient(options: ConnectClientOptions): ConnectClient;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | [`ConnectClientOptions`](#interface-connectclientoptions) |

###### Returns

`ConnectClient`

#### Properties

##### identity

```ts
readonly identity: IdentityResource;
```

"Login with Connect" - the OAuth2 handshake and the session after it.

***

##### access

```ts
readonly access: AccessResource;
```

What a signed-in member may do on your application.

***

##### apps

```ts
readonly apps: AppsResource;
```

The applications a workspace can open - for a launcher grid.

***

##### entitlements

```ts
readonly entitlements: EntitlementsResource;
```

Whether a workspace may use your application, and on what plan.

***

##### billing

```ts
readonly billing: BillingResource;
```

Your plan catalog, and metering usage against add-on balances.

***

##### drive

```ts
readonly drive: DriveResource;
```

Files in a workspace's Drive.

***

##### scheduling

```ts
readonly scheduling: SchedulingResource;
```

A subscribing workspace's own calendar - what is bookable, free, and booked.

***

##### contacts

```ts
readonly contacts: ContactsResource;
```

Who a workspace already knows how to reach, by phone or by id.

***

##### channels

```ts
readonly channels: ChannelsResource;
```

The phone numbers Connect has assigned to your application, and where each one routes.

***

##### calls

```ts
readonly calls: CallsResource;
```

The calls that arrived on those channels, with their transcripts.

***

##### telephony

```ts
readonly telephony: TelephonyResource;
```

Calls on the numbers your application handles, when it answers its own calls - see `TelephonyResource`.

***

##### doNotCall

```ts
readonly doNotCall: DoNotCallResource;
```

The workspace's do-not-call list, which Connect enforces on every outbound call - see `DoNotCallResource`.

***

##### credit

```ts
readonly credit: CreditResource;
```

Prepaid credit a workspace buys from Connect and your application spends per use - see `CreditResource`.

***

##### ai

```ts
readonly ai: AiResource;
```

RAG chat and structured extraction.

***

##### enrichment

```ts
readonly enrichment: EnrichmentResource;
```

***

##### data

```ts
readonly data: DataResource;
```

Your application's own document store - see `DataResource`.

***

##### kv

```ts
readonly kv: KvResource;
```

Your application's key/value store - see `KvResource`.

***

##### otp

```ts
readonly otp: OtpResource;
```

WhatsApp/SMS phone verification.

***

##### logs

```ts
readonly logs: LogsResource;
```

Feature events into the Console's audit log, filterable to your application.

***

##### analytics

```ts
readonly analytics: AnalyticsResource;
```

Emit product events and read back this application's aggregated numbers.

***

##### sessions

```ts
readonly sessions: SessionsResource;
```

This Connect account's own login sessions - account-wide, not scoped to this application. Requires the `sessions:manage` scope.

***

##### integrations

```ts
readonly integrations: IntegrationsResource;
```

Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected.

***

##### workspaces

```ts
readonly workspaces: WorkspacesResource;
```

Which workspaces the signed-in user belongs to, switching between them, and the team - see `WorkspacesResource`.

***

##### visitor

```ts
readonly visitor: VisitorResource;
```

The public chat bubble on your own site - see `VisitorResource`. The one
resource here whose caller is not one of your users.

***

##### platform

```ts
readonly platform: PlatformResource;
```

The platform's own legal identity and DPDP grievance channel - see `PlatformResource`.

***

##### push

```ts
readonly push: PushResource;
```

Web Push. Connect holds the VAPID keys, the devices and the member's own
preferences; you supply a title, a body and a URL. See `PushResource`.

***

##### tasks

```ts
readonly tasks: TasksResource;
```

Background tasks on this application's declared Cloud Tasks queues - see `TasksResource`.

***

##### whatsapp

```ts
readonly whatsapp: WhatsAppResource;
```

Your application's own approved WhatsApp templates, to a workspace's customers.

***

##### email

```ts
readonly email: EmailResource;
```

Your application's own email, sent from Connect's address for a subscribing workspace - see `EmailResource`.

***

### Class: ConnectTaskVerificationError [#class-connecttaskverificationerror]

Receiving a Connect task - verify Google's OIDC token, then run. See
`TasksResource` for the sending half.

#### Extends

- `Error`

#### Constructors

##### Constructor

```ts
new ConnectTaskVerificationError(message: string): ConnectTaskVerificationError;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`ConnectTaskVerificationError`

###### Overrides

```ts
Error.constructor
```

#### Properties

##### stackTraceLimit

```ts
static stackTraceLimit: number;
```

The `Error.stackTraceLimit` property specifies the number of stack frames
collected by a stack trace (whether generated by `new Error().stack` or
`Error.captureStackTrace(obj)`).

The default value is `10` but may be set to any valid JavaScript number. Changes
will affect any stack trace captured _after_ the value has been changed.

If set to a non-number value, or set to a negative number, stack traces will
not capture any frames.

###### Inherited from

```ts
Error.stackTraceLimit
```

***

##### cause?

```ts
optional cause?: unknown;
```

The cause of the error.

###### Inherited from

```ts
Error.cause
```

***

##### name

```ts
name: string;
```

###### Inherited from

```ts
Error.name
```

***

##### message

```ts
message: string;
```

###### Inherited from

```ts
Error.message
```

***

##### stack?

```ts
optional stack?: string;
```

###### Inherited from

```ts
Error.stack
```

#### Methods

##### captureStackTrace()

###### Call Signature

```ts
static captureStackTrace(targetObject: object, constructorOpt?: Function): void;
```

Creates a `.stack` property on `targetObject`, which when accessed returns
a string representing the location in the code at which
`Error.captureStackTrace()` was called.

```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack;  // Similar to `new Error().stack`
```

The first line of the trace will be prefixed with
`${myObject.name}: ${myObject.message}`.

The optional `constructorOpt` argument accepts a function. If given, all frames
above `constructorOpt`, including `constructorOpt`, will be omitted from the
generated stack trace.

The `constructorOpt` argument is useful for hiding implementation
details of error generation from the user. For instance:

```js
function a() {
  b();
}

function b() {
  c();
}

function c() {
  // Create an error without stack trace to avoid calculating the stack trace twice.
  const { stackTraceLimit } = Error;
  Error.stackTraceLimit = 0;
  const error = new Error();
  Error.stackTraceLimit = stackTraceLimit;

  // Capture the stack trace above function b
  Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
  throw error;
}

a();
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `targetObject` | `object` |
| `constructorOpt?` | `Function` |

###### Returns

`void`

###### Inherited from

```ts
Error.captureStackTrace
```

###### Call Signature

```ts
static captureStackTrace(targetObject: object, constructorOpt?: Function): void;
```

Create .stack property on a target object

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `targetObject` | `object` |
| `constructorOpt?` | `Function` |

###### Returns

`void`

###### Inherited from

```ts
Error.captureStackTrace
```

***

##### prepareStackTrace()

```ts
static prepareStackTrace(err: Error, stackTraces: CallSite[]): any;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `err` | `Error` |
| `stackTraces` | `CallSite`[] |

###### Returns

`any`

###### See

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

###### Inherited from

```ts
Error.prepareStackTrace
```

***

##### isError()

```ts
static isError(value: unknown): value is Error;
```

Check if a value is an instance of Error

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `value` | `unknown` | The value to check |

###### Returns

`value is Error`

True if the value is an instance of Error, false otherwise

###### Inherited from

```ts
Error.isError
```
## Interfaces

### Interface: AccessResource [#interface-accessresource]

What a signed-in member may do on your application, and the role catalog behind it.

#### Methods

##### getUserRoles()

```ts
getUserRoles(userId: string, input: {
  workspaceId: string;
}): Promise<UserRolesResponse>;
```

Re-check one member's role and granted permissions, live. The same block
`identity.getUser()` returns - use this when you need only the role axis
and already know who the user is.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `userId` | `string` |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<[`UserRolesResponse`](/docs/reference/core#interface-userrolesresponse)\>

***

##### listRoles()

```ts
listRoles(): Promise<RolesResponse>;
```

Your application's own permission catalog and role definitions - the definitions `getUserRoles` resolves against.

###### Returns

`Promise`\<[`RolesResponse`](/docs/reference/core#interface-rolesresponse)\>

***

### Interface: AiResource [#interface-airesource]

RAG chat and structured extraction against a workspace's data.

`asUser` on `chat` behaves as it does on the store resources: pass a
signed-in user's `sessionToken` and Connect resolves the workspace from it,
ignoring `workspaceId`. Without it you are the application, naming the
workspace yourself.

#### Methods

##### chat()

```ts
chat(input: {
  workspaceId: string;
  messages: ChatMessage[];
  asUser?: string;
  conversationId?: string;
  screens?: unknown;
  screenContext?: unknown;
}, options?: {
  signal?: AbortSignal;
}): Promise<Response>;
```

RAG-grounded chat. Returns the raw streaming `Response` rather than a
parsed body - this endpoint answers with server-sent events, and buffering
it here would throw away the only reason to stream. Pipe
`response.body` onward, or hand it to the AI SDK's own reader.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `input` | \{ `workspaceId`: `string`; `messages`: [`ChatMessage`](/docs/reference/core#interface-chatmessage)[]; `asUser?`: `string`; `conversationId?`: `string`; `screens?`: `unknown`; `screenContext?`: `unknown`; \} | - |
| `input.workspaceId` | `string` | - |
| `input.messages` | [`ChatMessage`](/docs/reference/core#interface-chatmessage)[] | - |
| `input.asUser?` | `string` | - |
| `input.conversationId?` | `string` | Your own id for this conversation, sent with every turn of it. What makes the conversation *resumable*: Connect upserts against it and `conversations.list`/`get` read it back. Persisted only alongside `asUser` - a conversation belongs to a person, and a server-to-server call acts as nobody. Omit it to keep a turn out of history entirely. |
| `input.screens?` | `unknown` | Your application's screens, so the assistant answers in your product's words and can link into it - see `ConnectScreen`. Prose and links only: Connect never uses these to decide what anyone may do. |
| `input.screenContext?` | `unknown` | Where the user is, as `{ pathname }`. Matched against `screens`. |
| `options` | \{ `signal?`: `AbortSignal`; \} | - |
| `options.signal?` | `AbortSignal` | - |

###### Returns

`Promise`\<`Response`\>

***

##### listConversations()

```ts
listConversations(input: {
  asUser: string;
  limit?: number;
  cursor?: string;
}): Promise<AiConversationList>;
```

The signed-in user's past conversations in your application, most recent
first.

`asUser` is required rather than optional here, unlike on `chat`: the rows
are scoped to a person, and there is no application-wide view of them to
fall back to. Connect reads the user and the workspace from the token.

Cursor-paginated with no total - the list is ordered by last use, which
moves while it is being read.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `asUser`: `string`; `limit?`: `number`; `cursor?`: `string`; \} |
| `input.asUser` | `string` |
| `input.limit?` | `number` |
| `input.cursor?` | `string` |

###### Returns

`Promise`\<[`AiConversationList`](/docs/reference/core#interface-aiconversationlist)\>

***

##### getConversation()

```ts
getConversation(input: {
  conversationId: string;
  asUser: string;
}): Promise<AiConversationDetail>;
```

One stored transcript, oldest turn first.

`turns[].parts` comes back exactly as the model produced it - prose,
reasoning and tool calls in order - so it can be handed straight back to a
chat UI. Not this user's, or not your application's, is a `404` either way.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `conversationId`: `string`; `asUser`: `string`; \} |
| `input.conversationId` | `string` |
| `input.asUser` | `string` |

###### Returns

`Promise`\<[`AiConversationDetail`](/docs/reference/core#interface-aiconversationdetail)\>

***

##### deleteConversation()

```ts
deleteConversation(input: {
  conversationId: string;
  asUser: string;
}): Promise<{
  deleted: true;
}>;
```

Deletes a conversation and every turn beneath it.

Idempotent - one already gone answers `{ deleted: true }`, so a retry after
a dropped response does not read as a failure.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `conversationId`: `string`; `asUser`: `string`; \} |
| `input.conversationId` | `string` |
| `input.asUser` | `string` |

###### Returns

`Promise`\<\{
  `deleted`: `true`;
\}\>

***

##### generate()

```ts
generate(input: AiGenerateInput): Promise<AiGenerateResult>;
```

One generation from your own prompt, against a model Connect allows -
messages, optional tool declarations, an optional JSON schema for the
answer. Needs `ai:generate`, and the platform must have it enabled.

**Not `chat`.** That is Connect's assistant, grounded in a workspace's
Context Index; this is a bare model you prompt yourself, billed to the
workspace you name against its monthly AI budget.

**Tools come back to you.** Connect never runs one: a requested call
arrives in `toolCalls`, you run it, and the next `generate` carries the
model's turn and your result. One model step per call.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`AiGenerateInput`](/docs/reference/core#interface-aigenerateinput) |

###### Returns

`Promise`\<[`AiGenerateResult`](/docs/reference/core#interface-aigenerateresult)\>

***

##### analyzeCall()

```ts
analyzeCall(input: CallAnalysisInput): Promise<CallAnalysis>;
```

Transcript, summary, intent, sentiment and action items for a call
recording your application stored in the workspace's Drive - e.g. the
`fileId` `connect.telephony.archiveRecording` returned. Counted against
the workspace's monthly AI budget like `generate`; Connect stores nothing.

`not_found` for a file your application did not upload there;
`unavailable` (503) when the platform has no call analyzer.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`CallAnalysisInput`](/docs/reference/core#interface-callanalysisinput) |

###### Returns

`Promise`\<[`CallAnalysis`](/docs/reference/core#interface-callanalysis)\>

***

##### extract()

```ts
extract<T>(input: {
  schemaId: string;
  fileUrl: string;
}): Promise<T>;
```

Extract structured JSON from a file or image URL, against a platform extraction schema referenced by id.

###### Type Parameters

| Type Parameter | Default type |
| ------ | ------ |
| `T` | `unknown` |

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `schemaId`: `string`; `fileUrl`: `string`; \} |
| `input.schemaId` | `string` |
| `input.fileUrl` | `string` |

###### Returns

`Promise`\<`T`\>

***

##### draftAssessment()

```ts
draftAssessment(input: {
  assessmentKey: string;
  seed: string;
}): Promise<AssessmentDraft>;
```

Draft every section of a platform-defined assessment from one sentence.

The intended flow is draft first, then let the person correct it section
by section: a blank questionnaire is the reason most people abandon one,
and editing a strawman is a far smaller ask than authoring from nothing.

Unattributed - assessments run against platform-wide definitions and the
first consumers run before anyone has signed in, so there is no `asUser`
and no workspace here.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `assessmentKey`: `string`; `seed`: `string`; \} |
| `input.assessmentKey` | `string` |
| `input.seed` | `string` |

###### Returns

`Promise`\<[`AssessmentDraft`](/docs/reference/core#interface-assessmentdraft)\>

***

##### reviewAssessment()

```ts
reviewAssessment(input: {
  assessmentKey: string;
  answers: AssessmentAnswers;
}): Promise<AssessmentReview>;
```

Critique a completed assessment - the tensions between sections, the
riskiest assumption, and a coarse score.

Unknown section keys in `answers` are ignored rather than rejected, so a
definition changing under a half-finished assessment costs the reader
nothing.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `assessmentKey`: `string`; `answers`: [`AssessmentAnswers`](/docs/reference/core#type-alias-assessmentanswers); \} |
| `input.assessmentKey` | `string` |
| `input.answers` | [`AssessmentAnswers`](/docs/reference/core#type-alias-assessmentanswers) |

###### Returns

`Promise`\<[`AssessmentReview`](/docs/reference/core#interface-assessmentreview)\>

***

##### readiness()

```ts
readiness(input: {
  domain: string;
  asUser?: string;
  refresh?: boolean;
}, options?: {
  signal?: AbortSignal;
}): Promise<ReadinessAssessmentResult>;
```

How ready the site at a domain is for AI agents.

Probes for an MCP server, an OpenAPI description, `llms.txt`, structured
data and a markdown twin; evaluates the site's `robots.txt` against the
assistants that actually fetch pages; checks whether its content survives
without JavaScript and whether a bot wall refuses agents outright. Then
grades what it found and says what to fix first.

Needs an API key scoped to **`ai:readiness`**, which is separate from every
other AI scope because it is the widest outbound surface on the API - it
POSTs to candidate MCP endpoints and fetches the home page under two user
agents.

```ts
const result = await connect.ai.readiness({ domain: "acme.com" });
if (result.status === "assessed") {
  console.log(result.report.grade, result.report.topFix?.fix);
}
```

Four things to know before wiring it in:

- **Set your client timeout to at least 90 seconds.** A cold assessment
  runs the whole probe before answering and can take up to a minute; many
  HTTP clients default to 30s and abandon a request that was about to
  succeed. Cached reads return in milliseconds and are the normal case -
  reports are held for a week.
- **A week, not ninety days**, because a report is a to-do list somebody is
  working through. Pass `refresh` after shipping a fix to see it land.
- **Handle `status: "error"` checks as unknown, not as failures.** They mean
  Connect could not measure that check, and they are excluded from the
  score rather than counted against it.
- **`skipped` says whose problem it is.** `unreachable` means the site did
  not answer; `provider_unavailable` means Connect's own assessment service
  did. Only the second is worth retrying silently, and neither should be
  shown as a failing grade.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `input` | \{ `domain`: `string`; `asUser?`: `string`; `refresh?`: `boolean`; \} | - |
| `input.domain` | `string` | A domain or a URL in any form - `acme.com`, `www.acme.com`, `https://acme.com/pricing`. |
| `input.asUser?` | `string` | - |
| `input.refresh?` | `boolean` | Re-assess even if Connect has a fresh report cached. For an operator who has just shipped a fix. |
| `options` | \{ `signal?`: `AbortSignal`; \} | - |
| `options.signal?` | `AbortSignal` | - |

###### Returns

`Promise`\<[`ReadinessAssessmentResult`](/docs/reference/core#type-alias-readinessassessmentresult)\>

***

### Interface: AnalyticsResource [#interface-analyticsresource]

Your application's own analytics: emit product events, and read back the daily
numbers Connect aggregates from them.

There is no way to name an application in either call. The scope is always the one
your API key belongs to - that is the endpoint's security model, not an
omission.

#### Methods

##### series()

```ts
series(query: AnalyticsQuery): Promise<AnalyticsResponse>;
```

Daily series for the requested metrics, up to 90 days.

**Throws on failure**, unlike `emit` below. A read whose result you are
about to render has to be able to say "I could not load"; silently
returning an empty series would draw a chart of zeroes.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `query` | [`AnalyticsQuery`](/docs/reference/core#interface-analyticsquery) |

###### Returns

`Promise`\<[`AnalyticsResponse`](/docs/reference/core#interface-analyticsresponse)\>

***

##### emit()

```ts
emit(events: EmitEventInput[]): Promise<EmitEventsResponse>;
```

Never throws - the same judgement `LogsResource.write` makes, for the same
reason: telemetry that takes down the code path it was observing is worse
than a lost event. A failure is reported through `delivered`.

Up to 50 events per call. Pass `sessionToken` on an event to attribute it
to the signed-in user; without one it still lands, just unattributed.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `events` | [`EmitEventInput`](/docs/reference/core#interface-emiteventinput)[] |

###### Returns

`Promise`\<[`EmitEventsResponse`](/docs/reference/core#interface-emiteventsresponse)\>

***

### Interface: AppsResource [#interface-appsresource]

The applications a workspace can open - the data behind a launcher grid.

#### Methods

##### list()

```ts
list(workspaceId: string): Promise<WorkspaceApp[]>;
```

Launchable apps only, so a grid tile is never a dead end. The calling
application is included and flagged `isCurrent` rather than filtered out.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`WorkspaceApp`](/docs/reference/core#interface-workspaceapp)[]\>

***

### Interface: AuthorizeUrlInput [#interface-authorizeurlinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `redirectUri` | `string` | - |
| `state` | `string` | Fresh and random per request; check it matches on the way back. `createState()` makes one. |
| `prompt?` | `"none"` \| `"create"` | `"create"` opens registration instead of sign-in, so one entry point serves both buttons. `"none"` is OIDC silent authentication: Connect answers from the visitor's existing session without showing them anything, returning a code if they are signed in and have already authorised this client, and `error=login_required`/`consent_required`/`interaction_required` if not. Use it to sign in a returning visitor with no click - never to *start* a sign-in, since by definition it will never show them a login screen. |
| `codeChallenge?` | `string` | Required only if this application enforces PKCE. |

***

### Interface: BillingResource [#interface-billingresource]

The plan catalog you sell, and metering usage back against a subscriber's add-on balance.

#### Methods

##### listPlans()

```ts
listPlans(): Promise<PlansResponse>;
```

Your application's active plans - render live pricing instead of hardcoding it.

`features` is normalised on the way out: Connect used to send bare keys
and now sends `{ key, name, description }`, and this SDK is versioned
independently of the instance it talks to. Without the coercion a partner
pointed at a Connect that has not upgraded yet renders a feature list of
blanks - which is exactly what happened. See `normalizePlanFeatures`.

`limits` is defaulted for the same reason, one version later: an instance
that predates limits sends no such field, and a pricing grid mapping over
`plan.limits` would throw rather than simply show no limit rows.

###### Returns

`Promise`\<[`PlansResponse`](/docs/reference/core#interface-plansresponse)\>

***

##### getAddonUsage()

```ts
getAddonUsage(workspaceId: string): Promise<AddonUsageResponse>;
```

What is left of every consumable add-on on a workspace's
subscription - the read half of `consumeAddon`.

Needs a key scoped to `addons:read`, which is separate from
`addons:consume` on purpose: a key that only renders a usage meter should
not also be able to spend the balance down.

Returns an empty `addons` array rather than throwing when there is
nothing to meter, so a caller can render it unconditionally.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`AddonUsageResponse`](/docs/reference/core#interface-addonusageresponse)\>

***

##### consumeAddon()

```ts
consumeAddon(input: ConsumeAddonInput, options?: {
  idempotencyKey?: string;
}): Promise<ConsumeAddonResponse>;
```

Debit a consumable add-on's per-cycle balance on behalf of an
workspace's users. Call it at whatever cadence your usage happens.

An `Idempotency-Key` is always sent, generated per call when you do not
supply one: a balance cannot be un-spent, so a retried consume without a
key double-debits. Pass your own when the natural unit of work is
something you can name (a job id, a message id) and might submit twice.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ConsumeAddonInput`](/docs/reference/core#interface-consumeaddoninput) |
| `options` | \{ `idempotencyKey?`: `string`; \} |
| `options.idempotencyKey?` | `string` |

###### Returns

`Promise`\<[`ConsumeAddonResponse`](/docs/reference/core#interface-consumeaddonresponse)\>

***

### Interface: CallsResource [#interface-callsresource]

Every call that arrived on a channel assigned to your application.

The application is resolved from your API key and is never a parameter, so
there is no request that reads another partner's calls.

`channelId` is the only filter, and that is a budget decision rather than an
oversight: each filter costs a Firestore composite index against a project
already near its ceiling. Filter the rest client-side, or read the
aggregates from `analytics`.

#### Methods

##### list()

```ts
list(input?: ListCallsInput): Promise<CallsPage>;
```

Your calls, newest first, across all of your subscribing workspaces.

**Page until `hasMore` is `false`; do not stop on an empty `items`.** A
page can come back empty with more behind it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ListCallsInput`](#interface-listcallsinput) |

###### Returns

`Promise`\<[`CallsPage`](/docs/reference/core#interface-callspage)\>

***

##### get()

```ts
get(callId: string): Promise<CallDetail>;
```

One call and every transcript turn, ordered by `sequence` - which is the
sort to trust, since two turns can share a millisecond.

`recordingFileId` and `transcriptFileId` are Drive file ids, readable with
a `drive:read` key. Both are `null` until the carrier posts the audio,
which is the normal state for a call that has only just ended - branch on
`recordingStatus` rather than treating `null` as a failure.

Throws `ConnectError` with `code: "not_found"` for a call that does not
exist *or* belongs to another application.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `callId` | `string` |

###### Returns

`Promise`\<[`CallDetail`](/docs/reference/core#interface-calldetail)\>

***

##### getCallingAvailability()

```ts
getCallingAvailability(input: {
  asUser: string;
}): Promise<CallingAvailability>;
```

Whether the member can call, and if not why - what `<ConnectCallButton>`
renders from. Throws `ConnectError` 403 for a member without "Make
calls"; the bridge turns that into `status: "no_permission"`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `asUser`: `string`; \} |
| `input.asUser` | `string` |

###### Returns

`Promise`\<[`CallingAvailability`](/docs/reference/core#interface-callingavailability)\>

***

##### getRouting()

```ts
getRouting(input: {
  asUser: string;
}): Promise<any>;
```

Reads the inbound call routing flow and published agents for the workspace.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `asUser`: `string`; \} |
| `input.asUser` | `string` |

###### Returns

`Promise`\<`any`\>

***

##### setRouting()

```ts
setRouting(input: {
  routing: Record<string, unknown>;
  asUser: string;
}): Promise<any>;
```

Sets the inbound call routing flow (smart CRM owner routing, AI agent, single number, team, fallback).

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `routing`: `Record`\<`string`, `unknown`\>; `asUser`: `string`; \} |
| `input.routing` | `Record`\<`string`, `unknown`\> |
| `input.asUser` | `string` |

###### Returns

`Promise`\<`any`\>

***

##### getCallSettings()

```ts
getCallSettings(input: {
  asUser: string;
}): Promise<CallSettings>;
```

Whether the workspace records its member calls, and whether this member
may change that. The same switch as Connect's Calling screen.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `asUser`: `string`; \} |
| `input.asUser` | `string` |

###### Returns

`Promise`\<[`CallSettings`](/docs/reference/core#interface-callsettings)\>

***

##### setCallRecording()

```ts
setCallRecording(input: {
  recordingEnabled: boolean;
  asUser: string;
}): Promise<CallSettings>;
```

Switches recording on or off for calls started from now. Throws
`ConnectError` 403 when the member lacks `channel:manage`; the change is
audited against them. Answers the settings as they now stand.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `recordingEnabled`: `boolean`; `asUser`: `string`; \} |
| `input.recordingEnabled` | `boolean` |
| `input.asUser` | `string` |

###### Returns

`Promise`\<[`CallSettings`](/docs/reference/core#interface-callsettings)\>

***

##### getCallingReadiness()

```ts
getCallingReadiness(input: 
  | {
  asUser: string;
}
  | {
  workspaceId: string;
}): Promise<CallingReadiness>;
```

Whether the workspace may legally make each kind of call - service or
sales, dialled by a rep or by software - and the steps left on Connect's
calling compliance journey. Send the member to `setupUrl` to finish them.

Pass `asUser` to ask for a signed-in member, or `workspaceId` to ask from
your own server with nobody signed in - a dialer checking before it places
a call. The answer is the same either way, except that `canManage` is
always `false` without a member.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \| \{ `asUser`: `string`; \} \| \{ `workspaceId`: `string`; \} |

###### Returns

`Promise`\<[`CallingReadiness`](/docs/reference/core#interface-callingreadiness)\>

***

##### getBrowserCallingToken()

```ts
getBrowserCallingToken(input: {
  asUser: string;
}): Promise<BrowserCallingToken>;
```

A carrier token the member's browser signs in to the phone network with.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `asUser`: `string`; \} |
| `input.asUser` | `string` |

###### Returns

`Promise`\<[`BrowserCallingToken`](/docs/reference/core#interface-browsercallingtoken)\>

***

##### startBrowserCall()

```ts
startBrowserCall(input: {
  phone: string;
  asUser: string;
  purpose?: ConnectCallPurpose;
}): Promise<StartedBrowserCall>;
```

Approves one call from the member to `phone` (E.164) and records it.

Throws `ConnectError` with status `412` when the workspace has no active
number, is not allowed to place calls, or the member is already on one -
the message is written to be shown to them. With a `purpose`, also when
no number may carry it and the workspace enforces purposes.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `phone`: `string`; `asUser`: `string`; `purpose?`: [`ConnectCallPurpose`](/docs/reference/core#type-alias-connectcallpurpose); \} |
| `input.phone` | `string` |
| `input.asUser` | `string` |
| `input.purpose?` | [`ConnectCallPurpose`](/docs/reference/core#type-alias-connectcallpurpose) |

###### Returns

`Promise`\<[`StartedBrowserCall`](/docs/reference/core#interface-startedbrowsercall)\>

***

##### cancelBrowserCall()

```ts
cancelBrowserCall(input: {
  callId: string;
  asUser: string;
}): Promise<void>;
```

Closes a call the browser never managed to place - recorded as
`unanswered`/`cancelled`. A no-op once the carrier has it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `callId`: `string`; `asUser`: `string`; \} |
| `input.callId` | `string` |
| `input.asUser` | `string` |

###### Returns

`Promise`\<`void`\>

***

### Interface: ChannelsResource [#interface-channelsresource]

The phone numbers Connect has assigned to your application.

**Inventory is platform-owned.** A number is a regulated asset bought under
Connect's carrier account and reassignable between workspaces, so your
application never provisions, prices or releases one - it claims from a pool
it cannot see, and reads back what it was given. That is why `claim` takes a
workspace and a kind rather than a number, and why nothing here exposes a
carrier, a provider reference or a monthly rate.

The application is resolved from your API key and is never a parameter, so
no request shape here can reach another partner's channels. A channel
belonging to someone else answers `404`, not `403` - confirming it exists
would make these reads an oracle for enumerating their channel ids.

#### Methods

##### list()

```ts
list(input?: ListChannelsInput): Promise<ChannelsPage>;
```

Every channel assigned to your application, across all of your subscribing
workspaces.

Page until `hasMore` is `false`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ListChannelsInput`](#interface-listchannelsinput) |

###### Returns

`Promise`\<[`ChannelsPage`](/docs/reference/core#interface-channelspage)\>

***

##### get()

```ts
get(channelId: string): Promise<ConnectChannel>;
```

The channel behind an id you already hold - a webhook's `channelId`, or a
call record's.

Throws `ConnectError` with `code: "not_found"` for an id that does not
exist *or* belongs to another application.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `channelId` | `string` |

###### Returns

`Promise`\<[`ConnectChannel`](/docs/reference/core#interface-connectchannel)\>

***

##### claim()

```ts
claim(input: ClaimChannelInput): Promise<ClaimedChannel>;
```

Takes a number out of Connect's inventory and binds it to one of your
subscribing workspaces.

**You do not pick the number.** A workspace may hold exactly one, across
every application it uses, and it is then fixed for the life of the
engagement - there is no re-claim and no swap. It leaves the workspace when
you `release` it or a Connect operator unassigns it.

Two different `409`s, and they need different handling: the workspace
already holds a channel of this kind (fix the call), or the pool is empty
right now (retry - Connect is notified, and this is not permanent).

The claimed channel comes back without routing, because it has none yet.
Call `setRouting` next, or the number refuses every call that reaches it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ClaimChannelInput`](#interface-claimchannelinput) |

###### Returns

`Promise`\<[`ClaimedChannel`](/docs/reference/core#interface-claimedchannel)\>

***

##### release()

```ts
release(channelId: string, input: {
  workspaceId: string;
}): Promise<ConnectChannel>;
```

Gives a number back: its rental closes, routing is cleared and it returns
to Connect's inventory. A number your application handled stops reaching
your telephony webhook. The inverse of `claim`, under the same scope.

Throws `code: "not_found"` for another application's channel, or one held
by a different workspace than the one you name.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `channelId` | `string` |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<[`ConnectChannel`](/docs/reference/core#interface-connectchannel)\>

***

##### setRouting()

```ts
setRouting(channelId: string, destination: Record<string, unknown>): Promise<ChannelRoutingRecord>;
```

Points a channel at wherever inbound conversations should go.

`destination` is yours: Connect stores it verbatim and never reads into
it. It **replaces** the stored record rather than merging, so send the
whole destination each time; it must serialize to 16 KB or fewer.

Throws `code: "conflict"` when the channel is suspended or released - use
`clearRouting` to stop a suspended number answering, which is deliberately
still allowed.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `channelId` | `string` |
| `destination` | `Record`\<`string`, `unknown`\> |

###### Returns

`Promise`\<[`ChannelRoutingRecord`](/docs/reference/core#interface-channelroutingrecord)\>

***

##### clearRouting()

```ts
clearRouting(channelId: string): Promise<ChannelRoutingRecord>;
```

Takes a number out of service without giving it up - the channel stays
assigned to you and stays billable, and inbound calls are refused until
you write routing again.

This is the only way to say "stop answering": overwriting `destination`
can only ever point the number somewhere *else*, and unassigning it is a
Console action that returns the number to Connect's inventory.

Idempotent - clearing already-clear routing answers `200` with
`routing: null`, so a retry after a lost response is safe.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `channelId` | `string` |

###### Returns

`Promise`\<[`ChannelRoutingRecord`](/docs/reference/core#interface-channelroutingrecord)\>

***

### Interface: ClaimChannelInput [#interface-claimchannelinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `kind?` | [`ChannelKind`](/docs/reference/core#type-alias-channelkind) | Defaults to `"voice"`, the only kind Connect stocks inventory for today. |

***

### Interface: ConnectAccountSessionOptions [#interface-connectaccountsessionoptions]

Account mode: one CosmoOps sign-in shared by every first-party app.

Connect sets an opaque account-session id in a cookie on the parent domain;
the bridge only ever **reads** it (and clears it on sign-out). Every read
resolves it through `identity.getAccountUser`, so the answer is still this
application's own role, permissions and subscription.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `cookieName` | `string` | The cookie Connect sets - must equal Connect's `CONNECT_ACCOUNT_COOKIE`. |
| `cookieDomain?` | `string` \| `null` | The `Domain` Connect set it with (`CONNECT_ACCOUNT_COOKIE_DOMAIN`), needed to clear it on sign-out - a `Set-Cookie` without the same `Domain` clears a different, host-only cookie and leaves the shared one in place. Unset means host-only, which is what local development uses. |

***

### Interface: ConnectClientOptions [#interface-connectclientoptions]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `apiKey` | `string` | The API key plaintext from this application's Connect client section, shown once at creation. One credential does three jobs: it is the `Authorization: Bearer` value for every endpoint, the OAuth2 `client_secret` for the `/sso/*` exchange, and the HMAC key `identity.verifyIdToken()` checks against. Revoking the key revokes all three. |
| `clientId?` | `string` | Required only for `identity.*`. Public - it goes in the authorize URL. |
| `baseUrl?` | `string` | Defaults to `https://connect.cosmoops.com`. Point at your own instance if you run one. |
| `timeoutMs?` | `number` | Per attempt, not per call. Defaults to 20s. |
| `maxRetries?` | `number` | Extra attempts after the first, for requests that are safe to repeat. Defaults to 2. See `request()` in `http.ts` for what qualifies - a bare POST never does. |
| `fetch?` | [`FetchLike`](#type-alias-fetchlike) | Swap in your own instrumented `fetch`. Defaults to the runtime's. |

***

### Interface: ConnectHandlerOptions [#interface-connecthandleroptions]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `client` | [`ConnectClient`](#class-connectclient) | - |
| `session` | [`ConnectSessionStore`](#interface-connectsessionstore) | - |
| `basePath?` | `string` | Where this handler is mounted, so it can strip the prefix off incoming paths. Must match `basePath` on `<ConnectProvider>`. Defaults to `/api/connect`. |
| `defaultRedirect?` | `string` | Where `/callback` sends the user when the request carried no `next`. Defaults to `/`. |
| `stateCookieName?` | `string` | Name of the short-lived cookie holding the OAuth `state`. Only change it if it collides with something you already set. |
| `account?` | [`ConnectAccountSessionOptions`](#interface-connectaccountsessionoptions) | Account mode - see `ConnectAccountSessionOptions`. When set, `/session` and every route resolve the shared account cookie through `getAccountUser`, `/signin` goes to Connect's continue URL, `/logout` ends the session in every app, `/workspaces/switch` moves every app, and `/callback` and One Tap do not exist. `session` is still read when the account cookie is absent, so a per-application session from before the switch keeps working until it expires. |
| `sessionCacheTtlMs?` | `number` | How long, in milliseconds, this handler reuses a resolved session for the routes that only need to know *whose* request it is. Defaults to `SESSION_CACHE_TTL_MS` (15 seconds); `0` turns the cache off. See `createConnectHandler` for exactly what it trades. |

***

### Interface: ConnectSessionStore [#interface-connectsessionstore]

What the bridge needs from the partner's own session, and what it may change
about it.

Deliberately an interface rather than a cookie this SDK sets: the partner
already has a session mechanism, and a second one owned by a dependency
would be a second source of truth about who is signed in.

#### Methods

##### read()

```ts
read(request: Request): string | Promise<string | null> | null;
```

The Connect `sessionToken` for this caller, or `null` if signed out.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `request` | `Request` |

###### Returns

`string` \| `Promise`\<`string` \| `null`\> \| `null`

***

##### write()?

```ts
optional write(
   request: Request, 
   response: Response, 
sessionToken: string): void | Promise<void>;
```

Persist a freshly exchanged session. Supplying this turns on `/signin` and
`/callback`, so the handler runs the whole OAuth round trip instead of
leaving you to write the callback route - see `createConnectHandler`.
Set cookies on `response`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `request` | `Request` |
| `response` | `Response` |
| `sessionToken` | `string` |

###### Returns

`void` \| `Promise`\<`void`\>

***

##### clear()?

```ts
optional clear(request: Request, response: Response): void | Promise<void>;
```

Called on sign-out, before Connect is told. Clear your own session here - set cookies on `response`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `request` | `Request` |
| `response` | `Response` |

###### Returns

`void` \| `Promise`\<`void`\>

***

##### migrate()?

```ts
optional migrate(
   request: Request, 
   response: Response, 
   sessionToken: string, 
session: UserResponse): void | Promise<void>;
```

Called by `GET /session` once Connect has **accepted** the token `read`
returned, so a store that found it somewhere legacy can re-persist it
where it now belongs - `cookieSessionStore` uses it to move a token from
the shared `connect_session` cookie to this application's own name.
`session` is the resolved answer (its `sessionExpiresAt` bounds the new
cookie). Optional, and a no-op for any store with nothing to migrate.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `request` | `Request` |
| `response` | `Response` |
| `sessionToken` | `string` |
| `session` | [`UserResponse`](/docs/reference/core#interface-userresponse) |

###### Returns

`void` \| `Promise`\<`void`\>

***

### Interface: ConnectTaskHandlerOptions\<TPayload\> [#interface-connecttaskhandleroptions]

#### Extends

- [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions)

#### Type Parameters

| Type Parameter |
| ------ |
| `TPayload` |

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `invoker?` | `string` | The invoker service account's email - the one the platform gives you with your API key. Defaults to `CONNECT_TASK_INVOKER`. | [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions).[`invoker`](#interface-verifyconnecttaskoptions) |
| `audience?` | `string` | Your own origin, which Connect sets as the token's audience. Defaults to `CONNECT_TASK_AUDIENCE`, then to the request URL's origin. | [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions).[`audience`](#interface-verifyconnecttaskoptions) |
| `fetch?` | [`FetchLike`](#type-alias-fetchlike) | Injectable for tests. Defaults to the global `fetch`. | [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions).[`fetch`](#interface-verifyconnecttaskoptions) |
| `now?` | () => `number` | Injectable for tests. Milliseconds since the epoch. | [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions).[`now`](#interface-verifyconnecttaskoptions) |
| `handle` | (`delivery`: [`ConnectTaskDelivery`](/docs/reference/core#interface-connecttaskdelivery)\<`TPayload`\>) => `void` \| `Promise`\<`void`\> | Run one task. Return normally and Cloud Tasks counts it done; **throw** and it is retried with backoff, up to the queue's declared attempts. A failure no retry can fix (the record is gone) should return, not throw. | - |

***

### Interface: ConnectWebhookHandlerOptions [#interface-connectwebhookhandleroptions]

Webhook verification and a ready-made POST route. Server-only for the
obvious reason: the signing secret must never reach a browser bundle.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `secret` | `string` | The endpoint's signing secret, from Console. Keep it in an environment variable. |
| `on` | [`ConnectWebhookHandlers`](#type-alias-connectwebhookhandlers) | - |
| `onError?` | (`error`: `unknown`, `envelope`: [`ConnectWebhookEnvelope`](/docs/reference/core#interface-connectwebhookenvelope)) => `void` \| `Promise`\<`void`\> | Called when a handler throws. Return nothing to have the delivery retried (Connect responds to a 5xx by retrying for about 33 hours); the default logs and rethrows, which produces exactly that. |

***

### Interface: ContactsResource [#interface-contactsresource]

Who a workspace already knows how to reach.

**This is the telephony identity, not a CRM record.** A contact is keyed by
`primaryPhone` - the one fact every inbound call carries - and it is written
automatically: a voice agent creates one on the first call from a new number
and fills fields in as it learns them. `source` says which kind you are
holding, and it is worth rendering: `call` means an agent guessed and nobody
has confirmed, `manual` means a person typed it.

**Reference it, do not copy it.** If your application keeps its own customer
records, store this contact's `id` on yours rather than duplicating the
fields. Two writable copies of a person's name with no single owner drift,
and Connect's is the one an agent keeps updating from calls. The id is also
an exact join to anything else keyed by it - `Booking.contactId`, most
usefully - so "which of my records is this appointment for" becomes a lookup
instead of a phone match.

**Writing is contributing, not editing.** `upsertFromInteraction` is the one
write, and it declares where its facts came from (`call` or `extraction`):
Connect merges them the way it merges a call's own, so they fill blanks and
never overwrite what a person typed.

#### Methods

##### lookupByPhone()

```ts
lookupByPhone(input: LookupContactInput): Promise<ConnectContact | null>;
```

"Do we already know this number?" - one point read, cheap enough for a
screen pop on every inbound call.

Resolves to `null` for a number nobody has called from before. That is the
ordinary case for a first-time caller, not an error, which is why it is a
null rather than a thrown `ConnectError`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`LookupContactInput`](#interface-lookupcontactinput) |

###### Returns

`Promise`\<[`ConnectContact`](/docs/reference/core#interface-connectcontact) \| `null`\>

***

##### get()

```ts
get(contactId: string, options: {
  workspaceId: string;
  asUser?: string;
}): Promise<ConnectContact>;
```

The record behind an id you already hold.

Throws `ConnectError` with `code: "not_found"` for an id that does not
exist *or* belongs to another workspace - deliberately the same answer,
since telling those apart would confirm a contact exists somewhere else.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `contactId` | `string` |
| `options` | \{ `workspaceId`: `string`; `asUser?`: `string`; \} |
| `options.workspaceId` | `string` |
| `options.asUser?` | `string` |

###### Returns

`Promise`\<[`ConnectContact`](/docs/reference/core#interface-connectcontact)\>

***

##### upsertFromInteraction()

```ts
upsertFromInteraction(input: UpsertContactFromInteractionInput): Promise<ContactInteractionResult>;
```

Finds the workspace's contact for `phone`, or creates it, and folds in
what a call or conversation learned. Needs `contacts:write`; server-side
only (no `asUser`).

**Automated facts fill blanks; they never overwrite.** A name somebody
typed into the contact stays, a phone or email is never marked verified,
and arrays are added to. `changedFields` is what actually landed, so
answer the caller from it - "I've saved your email" when nothing changed
is a promise the record does not keep.

**Count the call once.** Leave `countCall` off for every save made while
the interaction is still running and pass `true` on the one write that
closes it.

The workspace's applications receive `contact.created` or
`contact.updated` when something changed - including yours, if you
subscribe to them.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`UpsertContactFromInteractionInput`](/docs/reference/core#interface-upsertcontactfrominteractioninput) |

###### Returns

`Promise`\<[`ContactInteractionResult`](/docs/reference/core#interface-contactinteractionresult)\>

***

### Interface: CreateBookingInput [#interface-createbookinginput]

#### Extends

- `ActingAsOption`

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `asUser?` | `string` | The signed-in member's opaque Connect session token. | `ActingAsOption.asUser` |
| `workspaceId` | `string` | - | - |
| `eventTypeId` | `string` | - | - |
| `startAt` | `string` \| `Date` | The `startAt` from a `findSlots` result, passed back unchanged. Re-deriving it from a local wall time is how an integration books an hour that was never offered - the slot list is computed in the *business's* timezone, which is rarely the one your process is running in. | - |
| `attendee` | [`BookingAttendee`](/docs/reference/core#interface-bookingattendee) | - | - |
| `notes?` | `string` \| `null` | - | - |
| `source?` | `"api"` \| `"agent-call"` | `api` (the default) for your integration, `agent-call` for a booking an AI agent you run made on a call - with that call's id as `sourceCallId`. | - |
| `sourceCallId?` | `string` \| `null` | - | - |

***

### Interface: CreditResource [#interface-creditresource]

Prepaid credit - a rupee balance a workspace buys from Connect for your
application (topping up whenever it likes, GST invoiced by Connect), and
your application spends per use: calling minutes, SMS.

Unlike a consumable add-on it never resets with the billing cycle, can be
topped up mid-cycle, and can be credited back. Every amount is **paise,
pre-tax**.

```ts
const { spendable, topUpPath } = await connect.credit.getBalance(workspaceId);
// ...after the call ends:
await connect.credit.consume({ workspaceId, amount: 360, idempotencyKey: call.id, description: "Call (4 min)" });
```

A consume larger than `spendable` throws `ConnectError` code
`insufficient_credit`; send the customer to `topUpPath` through
`useConnect().openConnect()`. Needs `credit:read` / `credit:write`.

#### Methods

##### getBalance()

```ts
getBalance(workspaceId: string): Promise<CreditBalance>;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`CreditBalance`](/docs/reference/core#interface-creditbalance)\>

***

##### consume()

```ts
consume(input: ConsumeCreditInput): Promise<CreditMovement>;
```

Debits credit for one unit of work. `idempotencyKey` is required and is
enforced by Connect's ledger, so the request is safe to retry - a replay
answers `duplicate: true` with the original entry.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ConsumeCreditInput`](/docs/reference/core#interface-consumecreditinput) |

###### Returns

`Promise`\<[`CreditMovement`](/docs/reference/core#interface-creditmovement)\>

***

##### refund()

```ts
refund(input: RefundCreditInput): Promise<CreditMovement>;
```

Credits back part or all of one earlier consume. Never more than it took; idempotent like `consume`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`RefundCreditInput`](/docs/reference/core#interface-refundcreditinput) |

###### Returns

`Promise`\<[`CreditMovement`](/docs/reference/core#interface-creditmovement)\>

***

##### listLedger()

```ts
listLedger(workspaceId: string, options?: {
  cursor?: string;
  limit?: number;
}): Promise<CreditLedgerPage>;
```

Every movement, newest first. Pass the previous page's `nextCursor` to continue.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `options` | \{ `cursor?`: `string`; `limit?`: `number`; \} |
| `options.cursor?` | `string` |
| `options.limit?` | `number` |

###### Returns

`Promise`\<[`CreditLedgerPage`](/docs/reference/core#interface-creditledgerpage)\>

***

### Interface: DataResource [#interface-dataresource]

Your application's own document store - a dedicated Firestore database that
only Connect can reach.

You hold no Firebase credential and write no security rules. Collections are
declared in the Console; an undeclared name is refused rather than created,
and `workspaceId` is a path segment rather than a filter, so a query cannot
cross tenants.

**A query needs an index for its shape, and there are two ways to have one.**
Firestore maintains a single-field index automatically, which covers one
filter, or an `orderBy` on that same field. Anything wider - two filters, or
a filter ordered by a different field - needs a composite index **declared
on your application's Store tab**, which Connect then builds in your
database. A query with no matching index is refused up front, naming the
index to declare, rather than failing later with a Firestore index error
pointing at a console you cannot open.

**Dates are ISO-8601 strings.** The store writes what you send it, and JSON
has no date type, so a `Date` must be sent as `toISOString()`. That is not a
compromise: UTC ISO strings sort lexicographically in chronological order,
so ordering and range filters on them behave exactly as they would on a
timestamp - and Connect's own `_createdAt`/`_updatedAt` come back in the
same form.

**`workspaceId` is omitted for master data.** A collection declared
application-scoped in the Console belongs to no workspace - one copy, owned
by you, read by every tenant. Those are written with your API key only,
never on behalf of a user: a write permission is something a *workspace*
grants, so honouring one on shared data would give one tenant's admin reach
over every other tenant's reference data.

**`asUser` is how you act on behalf of a signed-in user.** Pass the opaque
`sessionToken` you were given at sign-in and Connect resolves the user and
their workspace from it - checking the collection's declared permission
against what that member actually holds, and **ignoring `workspaceId`
entirely**. Without it you are the application itself: your API key's scopes
are the only gate, and the per-user permissions do not apply. The bridge
(`createConnectHandler`) always passes it, which is what makes the browser
path safe.

#### Methods

##### get()

```ts
get(input: {
  workspaceId?: string;
  collection: string;
  id: string;
  asUser?: string;
}): Promise<StoreDocument>;
```

Reads one document. Throws `ConnectError` with status 404 when it does not exist.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `id`: `string`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.id` | `string` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreDocument`](/docs/reference/core#interface-storedocument)\>

***

##### getMany()

```ts
getMany(input: {
  workspaceId?: string;
  collection: string;
  ids: string[];
  asUser?: string;
}): Promise<StoreGetManyResult>;
```

Reads up to 200 documents of one collection by id, in **one request** -
the batch form of `get`.

Reach for it wherever you would loop over `get`: the contacts on a page of
deals, the plans behind a list of subscriptions. It is gated exactly like
`get` and spends one request of your rate limit however many ids it names.

```ts
const { items } = await connect.data.getMany({
  workspaceId,
  collection: "contacts",
  ids: deals.map((deal) => deal.data.contactId as string),
});
// items[i] belongs to ids[i]; null where that contact no longer exists.
```

Answers positionally, `null` where a document does not exist - unlike
`get`, a missing document does not throw.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `ids`: `string`[]; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.ids` | `string`[] |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreGetManyResult`](/docs/reference/core#interface-storegetmanyresult)\>

***

##### query()

```ts
query(input: {
  workspaceId?: string;
  collection: string;
  filters?: StoreQueryFilter[];
  orderBy?: StoreOrderBy;
  limit?: number;
  cursor?: string | null;
  asUser?: string;
}): Promise<StoreQueryPage>;
```

Lists a collection, one page at a time.

`filters` are ANDed. More than one, or one combined with an `orderBy` on a
different field, needs a composite index declared on the Store tab - see
the class note above.

`limit` defaults to 50 and caps at 200. Page by passing the previous
page's `nextCursor`; a `null` `nextCursor` is the end of the collection.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `filters?`: [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[]; `orderBy?`: [`StoreOrderBy`](/docs/reference/core#interface-storeorderby); `limit?`: `number`; `cursor?`: `string` \| `null`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.filters?` | [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[] |
| `input.orderBy?` | [`StoreOrderBy`](/docs/reference/core#interface-storeorderby) |
| `input.limit?` | `number` |
| `input.cursor?` | `string` \| `null` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreQueryPage`](/docs/reference/core#interface-storequerypage)\>

***

##### aggregate()

```ts
aggregate(input: {
  workspaceId?: string;
  collection: string;
  aggregations: StoreAggregation[];
  filters?: StoreQueryFilter[];
  asUser?: string;
}): Promise<StoreAggregatePage>;
```

Counts, sums and averages a filtered collection - **in Firestore**, not by
reading the rows back and adding them up.

That distinction is the whole point. An aggregation is billed by the index
entries it scans rather than by the documents it would have returned, so a
dashboard tile over ten thousand rows costs a handful of reads and stays
flat as the collection grows. Paging the rows into your own process to sum
them is an unbounded read, and it is what every integration does when
there is no aggregate call to reach for.

```ts
const { values } = await connect.data.aggregate({
  workspaceId,
  collection: "deals",
  filters: [{ field: "outcome", op: "==", value: "won" }],
  aggregations: [
    { op: "count", alias: "count" },
    { op: "sum", field: "value", alias: "total" },
  ],
});
```

A `sum` or `avg` over nothing answers `null` rather than `0`, because
"nothing matched" and "the total is zero" are different facts.

The index it needs is the one its **filters** need plus the aggregated
field - Firestore reads a sum out of the index rather than out of the
documents.

There is no `orderBy`, `limit` or `cursor`: the aggregate of an ordered
page is the aggregate of the whole filtered set.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `aggregations`: [`StoreAggregation`](/docs/reference/core#interface-storeaggregation)[]; `filters?`: [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[]; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.aggregations` | [`StoreAggregation`](/docs/reference/core#interface-storeaggregation)[] |
| `input.filters?` | [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[] |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreAggregatePage`](/docs/reference/core#interface-storeaggregatepage)\>

***

##### scan()

```ts
scan(input: {
  collection: string;
  filters?: StoreQueryFilter[];
  orderBy?: StoreOrderBy;
  limit?: number;
  cursor?: string | null;
}): Promise<StoreScanPage>;
```

Reads one collection across **every workspace at once** - your own
back-office view of your whole customer base. Each row carries the
`workspaceId` it came from.

Needs the `data:read:all` scope, and the collection must be marked
cross-workspace in the Console. There is deliberately **no `asUser`**: a
scan is never available on behalf of a signed-in user, because a workspace
admin is an admin of their own workspace and not of your other customers.

Filtering and ordering follow the same index rules as `query`, and
additionally need collection-group scope. Connect creates those from the
declaration - the single-field ones when you mark a field queryable, and a
group-scoped copy of every composite index the collection declares. A scan
issued in the minutes after that declaration may fail while the index
builds.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `collection`: `string`; `filters?`: [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[]; `orderBy?`: [`StoreOrderBy`](/docs/reference/core#interface-storeorderby); `limit?`: `number`; `cursor?`: `string` \| `null`; \} |
| `input.collection` | `string` |
| `input.filters?` | [`StoreQueryFilter`](/docs/reference/core#interface-storequeryfilter)[] |
| `input.orderBy?` | [`StoreOrderBy`](/docs/reference/core#interface-storeorderby) |
| `input.limit?` | `number` |
| `input.cursor?` | `string` \| `null` |

###### Returns

`Promise`\<[`StoreScanPage`](/docs/reference/core#interface-storescanpage)\>

***

##### create()

```ts
create(input: {
  workspaceId?: string;
  collection: string;
  data: Record<string, unknown>;
  id?: string;
  asUser?: string;
}): Promise<StoreDocument>;
```

Creates a document. Pass `id` to choose it, or omit it and one is
allocated. An `id` that already exists throws with status 409 - use
`replace` to overwrite deliberately.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `data`: `Record`\<`string`, `unknown`\>; `id?`: `string`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.data` | `Record`\<`string`, `unknown`\> |
| `input.id?` | `string` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreDocument`](/docs/reference/core#interface-storedocument)\>

***

##### replace()

```ts
replace(input: {
  workspaceId?: string;
  collection: string;
  id: string;
  data: Record<string, unknown>;
  asUser?: string;
}): Promise<StoreDocument>;
```

Replaces every field, creating the document if it does not exist. `_createdAt`/`_createdBy` survive.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `id`: `string`; `data`: `Record`\<`string`, `unknown`\>; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.id` | `string` |
| `input.data` | `Record`\<`string`, `unknown`\> |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreDocument`](/docs/reference/core#interface-storedocument)\>

***

##### update()

```ts
update(input: {
  workspaceId?: string;
  collection: string;
  id: string;
  data: Record<string, unknown>;
  asUser?: string;
}): Promise<StoreDocument>;
```

Merges top-level fields into an existing document. Throws with status 404 if it does not exist.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `id`: `string`; `data`: `Record`\<`string`, `unknown`\>; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.id` | `string` |
| `input.data` | `Record`\<`string`, `unknown`\> |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreDocument`](/docs/reference/core#interface-storedocument)\>

***

##### delete()

```ts
delete(input: {
  workspaceId?: string;
  collection: string;
  id: string;
  asUser?: string;
}): Promise<void>;
```

Idempotent - deleting a document that is not there succeeds.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `collection`: `string`; `id`: `string`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.collection` | `string` |
| `input.id` | `string` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<`void`\>

***

##### commit()

```ts
commit(input: {
  workspaceId?: string;
  operations: StoreCommitOperation[];
  asUser?: string;
}): Promise<StoreCommitResult>;
```

Applies a set of writes atomically, across as many of your collections as
they name. All of them land, or none.

This is the store's answer to a database transaction, and the shape is
deliberate: **you never hold one**. A transaction handle passed across an
HTTP boundary is a server-side lock whose duration your network controls,
with Firestore's sixty-second ceiling and lock contention as the failure
mode. Instead you read, decide, and commit *conditional on nothing having
moved* - Connect runs the whole set as one short transaction on its side.

```ts
await connect.data.commit({
  workspaceId,
  operations: [
    { op: "create", collection: "ledger", id, data: entry },
    {
      op: "merge",
      collection: "funds",
      id: entry.fundId,
      data: { balancePaise: increment(entry.amountPaise) },
    },
  ],
});
```

A precondition that no longer holds throws `ConnectError` with status 409
and **writes nothing** - see `transaction`, which re-reads and retries for
you. Note that the example above needs no precondition at all: an
`increment` is commutative, so concurrent commits both land correctly.

Returns the id each operation wrote, in order - which is how you learn the
id a `create` without one was given.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `operations`: [`StoreCommitOperation`](/docs/reference/core#interface-storecommitoperation)[]; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.operations` | [`StoreCommitOperation`](/docs/reference/core#interface-storecommitoperation)[] |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`StoreCommitResult`](/docs/reference/core#interface-storecommitresult)\>

***

##### transaction()

```ts
transaction(build: () => Promise<StoreCommitOperation[]>, options?: {
  workspaceId?: string;
  asUser?: string;
  attempts?: number;
}): Promise<StoreCommitResult>;
```

Read, decide, commit - retried when the world moved underneath you.

The optimistic-concurrency loop, written once. `build` reads whatever it
needs and returns the operations to apply; if any precondition among them
has gone stale by the time Connect looks, the commit is rejected whole and
`build` runs again against fresh reads.

```ts
await connect.data.transaction(async () => {
  const fund = await connect.data.get({ workspaceId, collection: "funds", id });
  if (!fund.data.active) throw new Error("That fund is closed.");
  return [
    {
      op: "merge",
      collection: "funds",
      id,
      data: { balancePaise: increment(amount) },
      precondition: unchangedSince(fund),
    },
  ];
});
```

**Only a 409 is retried.** A 403 or a validation error means the operation
was wrong rather than late, and retrying it would turn one clear failure
into `attempts` identical ones. An error thrown by `build` itself - the
closed-fund check above - is not retried either: it is a decision, not a
collision.

`build` must be free of side effects outside the returned operations,
because it can run more than once. That is the price of not holding a
lock, and it is the same contract Firestore's own `runTransaction` states
for the same reason.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `build` | () => `Promise`\<[`StoreCommitOperation`](/docs/reference/core#interface-storecommitoperation)[]\> |
| `options` | \{ `workspaceId?`: `string`; `asUser?`: `string`; `attempts?`: `number`; \} |
| `options.workspaceId?` | `string` |
| `options.asUser?` | `string` |
| `options.attempts?` | `number` |

###### Returns

`Promise`\<[`StoreCommitResult`](/docs/reference/core#interface-storecommitresult)\>

***

### Interface: DoNotCallActorInput [#interface-donotcallactorinput]

Who made a change, in your own terms - kept as your application's claim.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id?` | `string` | Your id for whoever made it - a user id, or `"backfill"`. |
| `name?` | `string` | - |

***

### Interface: DoNotCallResource [#interface-donotcallresource]

A workspace's do-not-call list - numbers that asked the business not to
ring them.

**Connect enforces it; you record it.** Every outbound call to a listed
number - a member's `<ConnectCallButton>` in any application, Connect's own
dialer, an agent placing a call - is refused with `ConnectError` code
`do_not_call` (HTTP 409) before anything reaches the carrier. So when a
person asks your application not to be called, write it here; you do not
need to check the list before dialling, and should not treat a read as
permission to dial.

Every purpose: a person who said "stop ringing me" meant the service call
too. Every change is kept in an append-only ledger (`history: true`).

Needs `do-not-call:read` / `do-not-call:write`.

#### Methods

##### get()

```ts
get(input: DoNotCallScope & {
  phone: string;
  history?: boolean;
}): Promise<ConnectDoNotCallLookup>;
```

A number's standing. Never-listed numbers resolve, with `doNotCall: false`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `DoNotCallScope` & \{ `phone`: `string`; `history?`: `boolean`; \} |

###### Returns

`Promise`\<[`ConnectDoNotCallLookup`](/docs/reference/core#interface-connectdonotcalllookup)\>

***

##### set()

```ts
set(input: DoNotCallScope & {
  phone: string;
  reason: ConnectDoNotCallReason;
  note?: string | null;
  actor?: DoNotCallActorInput;
}): Promise<ConnectDoNotCallChange>;
```

Puts a number on the list. Idempotent - a number already listed keeps its
original entry and answers `changed: false`, so a backfill can re-run.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `DoNotCallScope` & \{ `phone`: `string`; `reason`: [`ConnectDoNotCallReason`](/docs/reference/core#type-alias-connectdonotcallreason); `note?`: `string` \| `null`; `actor?`: [`DoNotCallActorInput`](#interface-donotcallactorinput); \} |

###### Returns

`Promise`\<[`ConnectDoNotCallChange`](/docs/reference/core#interface-connectdonotcallchange)\>

***

##### clear()

```ts
clear(input: DoNotCallScope & {
  phone: string;
  note: string;
  actor?: DoNotCallActorInput;
}): Promise<ConnectDoNotCallChange>;
```

Lifts a number's entry, so calls to it are placed again. `note` is
required - it is a claim the person took their request back.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `DoNotCallScope` & \{ `phone`: `string`; `note`: `string`; `actor?`: [`DoNotCallActorInput`](#interface-donotcallactorinput); \} |

###### Returns

`Promise`\<[`ConnectDoNotCallChange`](/docs/reference/core#interface-connectdonotcallchange)\>

***

### Interface: DriveResource [#interface-driveresource]

Files in a workspace's Drive - upload, and signed URLs to read back.

#### Methods

##### upload()

```ts
upload(input: UploadInput): Promise<UploadedFile>;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`UploadInput`](#interface-uploadinput) |

###### Returns

`Promise`\<[`UploadedFile`](/docs/reference/core#interface-uploadedfile)\>

***

##### getFileUrl()

```ts
getFileUrl(fileId: string, input: {
  workspaceId: string;
}): Promise<string>;
```

A signed URL for the original bytes. Short-lived - fetch it when you need it, do not store it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `fileId` | `string` |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<`string`\>

***

##### getPreviewUrl()

```ts
getPreviewUrl(fileId: string, input: {
  workspaceId: string;
  size?: ThumbnailSize;
}): Promise<string>;
```

A signed URL for the thumbnail. Same lifetime caveat as `getFileUrl`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `fileId` | `string` |
| `input` | \{ `workspaceId`: `string`; `size?`: [`ThumbnailSize`](/docs/reference/core#type-alias-thumbnailsize); \} |
| `input.workspaceId` | `string` |
| `input.size?` | [`ThumbnailSize`](/docs/reference/core#type-alias-thumbnailsize) |

###### Returns

`Promise`\<`string`\>

***

##### list()

```ts
list(input: {
  workspaceId: string;
  limit?: number;
}): Promise<DriveFileList>;
```

The files *your application* uploaded to this workspace's Drive, newest
first - never a member's files, never another application's. `limit`
defaults to 50 and is capped at 100; `truncated` says there were more.
Needs `drive:read`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId`: `string`; `limit?`: `number`; \} |
| `input.workspaceId` | `string` |
| `input.limit?` | `number` |

###### Returns

`Promise`\<[`DriveFileList`](/docs/reference/core#interface-drivefilelist)\>

***

##### delete()

```ts
delete(fileId: string, input: {
  workspaceId: string;
}): Promise<void>;
```

Deletes a file your application uploaded. Throws `ConnectError` 404 for a
file that is not yours, does not exist, or is already deleted - treat that
as done when retrying. Needs `drive:write`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `fileId` | `string` |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<`void`\>

***

##### getUsage()

```ts
getUsage(input: {
  workspaceId: string;
}): Promise<DriveUsage>;
```

How much of the workspace's Drive is used, against its limit. Needs
`drive:read` and an active subscription to your application.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<[`DriveUsage`](/docs/reference/core#interface-driveusage)\>

***

### Interface: EmailResource [#interface-emailresource]

Your application's own email - a support reply, a portal sign-in code -
sent through Connect for a workspace that subscribes to you.

You hold no email provider credential. Every message leaves from Connect's
sending address under the display name you choose (the workspace's name by
default), with your own address in `replyTo` so the answer reaches you.

**Server-side only**, and deliberately absent from the browser bridge: mail
to a customer is your backend's act, and Connect refuses a send that
carries a member's session.

#### Methods

##### send()

```ts
send(input: SendEmailInput, options?: {
  idempotencyKey?: string;
}): Promise<SendEmailResponse>;
```

Send one email.

**Store the returned `messageId`.** It is the Message-ID the email
actually left under - Connect's mail provider replaces any you propose -
and a customer's reply names it in `In-Reply-To`. Thread the next reply by
passing it as `headers.inReplyTo` (and appending it to `headers.references`).

**`suppressed` is not an error.** An address that hard-bounced or marked
Connect's mail as spam is skipped and listed there while the others are
sent; when every recipient is suppressed nothing is sent and the call
still resolves. Retrying changes nothing - show the address as
undeliverable.

Idempotent on `idempotencyKey` (a fresh UUID when you pass none, which
protects only this call's own retries). Pass your own - the ticket and
reply ids - so a retry after a timeout cannot put a second copy in
somebody's inbox.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`SendEmailInput`](/docs/reference/core#interface-sendemailinput) |
| `options` | \{ `idempotencyKey?`: `string`; \} |
| `options.idempotencyKey?` | `string` |

###### Returns

`Promise`\<[`SendEmailResponse`](/docs/reference/core#interface-sendemailresponse)\>

***

### Interface: EnrichmentResource [#interface-enrichmentresource]

What Connect knows about the company at a domain.

The thing behind "the account filled itself in": a record holding nothing
but `priya@acme.com` can offer a name, an industry, a size band and an
address instead of an empty form.

Two properties are worth knowing before you wire it in, because they decide
how you should render the result:

- **Every value is cited, and nothing is recalled.** Connect reads the
  company's own website and returns the URL each claim came from. A field
  the pages never stated comes back `null` rather than as a plausible guess.
- **It is a suggestion, not a save.** Nothing is written to your store, and
  nothing should be written to it without the person in front of it
  agreeing - which is also how HubSpot and Zoho present the same feature.

#### Methods

##### company()

```ts
company(input: {
  email?: string;
  domain?: string;
  asUser?: string;
  refresh?: boolean;
}, options?: {
  signal?: AbortSignal;
}): Promise<CompanyEnrichmentResult>;
```

Look a company up from a work email address or a domain.

Send whichever you hold; `domain` wins when both are given. A consumer
address (`founder@gmail.com`) is answered with
`{ status: "skipped", reason: "consumer_domain" }` rather than an error -
it names a person, not an employer.

`asUser` attributes the generation to the signed-in caller's workspace,
resolved from their session token. Without it the call is attributed to
your application.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `input` | \{ `email?`: `string`; `domain?`: `string`; `asUser?`: `string`; `refresh?`: `boolean`; \} | - |
| `input.email?` | `string` | - |
| `input.domain?` | `string` | - |
| `input.asUser?` | `string` | - |
| `input.refresh?` | `boolean` | Re-read the site even if Connect has a fresh profile cached. For an operator pressing "refresh" - not for automatic calls, which should take the cache. |
| `options` | \{ `signal?`: `AbortSignal`; \} | - |
| `options.signal?` | `AbortSignal` | - |

###### Returns

`Promise`\<[`CompanyEnrichmentResult`](/docs/reference/core#type-alias-companyenrichmentresult)\>

***

##### brand()

```ts
brand(input: {
  domain: string;
  asUser?: string;
  refresh?: boolean;
}, options?: {
  signal?: AbortSignal;
}): Promise<BrandExtractionResult>;
```

What the company at a domain **looks like** - logo, palette, typefaces,
and the radius, spacing and breakpoint scales its own stylesheets declare.

The sibling of `company()` above: that one answers what a company does,
this one answers what it looks like. Useful for white-labelling a portal
to a customer's brand, pre-filling a design brief, or showing a real
account card instead of a grey placeholder.

Needs an API key scoped to **`enrichment:brand`** - separate from
`enrichment:read` because it is far more expensive: it crawls
stylesheets, may render the page in a headless browser, and caches image
bytes.

```ts
const result = await connect.enrichment.brand({ domain: "acme.com" });
if (result.status === "extracted") {
  const primary = result.profile.brandKit.palette.find((c) => c.role === "primary");
}
```

Three things to know before wiring it in:

- **Set your client timeout to at least 90 seconds.** A cold read runs
  the whole extraction before answering and can take up to a minute; many
  HTTP clients default to 30s and abandon a request that was about to
  succeed. Cached reads return in milliseconds, and cached is the normal
  case - profiles are held for 90 days, so you pay the cold path roughly
  once per domain.
- **Never store `brandKit.logos[].url`.** It is a short-lived signed URL
  minted per read. Render it directly or re-read the profile; persist
  `sourceUrl` if you need something stable.
- **The values were measured, not generated.** Check `palette[].source` -
  a `css-var` entry is the company naming its own colour, a `frequency`
  entry is a ranked count and much weaker.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `input` | \{ `domain`: `string`; `asUser?`: `string`; `refresh?`: `boolean`; \} | - |
| `input.domain` | `string` | A domain or a URL in any form - `acme.com`, `www.acme.com`, `https://acme.com/pricing`. |
| `input.asUser?` | `string` | - |
| `input.refresh?` | `boolean` | Re-extract even if Connect has a fresh profile cached. For an operator pressing "refresh" - not for automatic calls. |
| `options` | \{ `signal?`: `AbortSignal`; \} | - |
| `options.signal?` | `AbortSignal` | - |

###### Returns

`Promise`\<[`BrandExtractionResult`](/docs/reference/core#type-alias-brandextractionresult)\>

***

##### presence()

```ts
presence(input: {
  domain: string;
  asUser?: string;
  refresh?: boolean;
}, options?: {
  signal?: AbortSignal;
}): Promise<PresenceScanResult>;
```

How **visible** the business at a domain is online, scored out of 100.

The third sibling of `company()` and `brand()`: that one answers what a
business does, the other what it looks like, and this one answers how
findable it is - by a search engine, by a customer, and by the assistants
that increasingly answer questions about a business before anybody visits
its site.

Needs an API key scoped to **`enrichment:presence`** - separate from
`enrichment:read` and `enrichment:brand` because it is the widest outbound
read of the three: roughly twenty fetches across the site's well-known
paths, and sometimes a headless browser.

```ts
const result = await connect.enrichment.presence({ domain: "acme.com" });
if (result.status === "scanned") {
  for (const next of result.scan.recommendations) {
    console.log(`${next.priority}. ${next.title} - ${next.fix}`);
  }
}
```

Four things to know before wiring it in:

- **The score is computed, not generated.** Every point comes from a check
  run against something actually observed, and every check ships with its
  weight, what was seen and the URL that settles it - so the total is
  reconstructable. A model only orders `recommendations` and writes the
  prose; it cannot move the number. That is the whole reason this is worth
  showing a customer: ask a model how good a company's web presence is and
  it answers 72 with total confidence for a site it never read.
- **`social` is claimed, never verified.** Those are URLs the site itself
  printed. Nothing proves the account belongs to that business - render it
  as "linked from their site". Check `machineReadable` too: `false` means
  a real profile that search engines have to infer from a footer link,
  which is a specific, common and easily fixed finding.
- **A low score is a success, not a skip.** Every check failing is the most
  useful report this produces. Only `unreachable` means the site did not
  answer - and `provider_unavailable` means *Connect* did not, which is
  never something to tell your user about their own site.
- **Set your client timeout to at least 90 seconds.** A cold scan runs the
  whole probe before answering; many HTTP clients default to 30s and
  abandon a request that was about to succeed. Cached scans return in
  milliseconds, and cached is the normal case - results are held for a
  fortnight, so you pay the cold path roughly once per domain.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `input` | \{ `domain`: `string`; `asUser?`: `string`; `refresh?`: `boolean`; \} | - |
| `input.domain` | `string` | A domain or a URL in any form - `acme.com`, `www.acme.com`, `https://acme.com/pricing`. |
| `input.asUser?` | `string` | - |
| `input.refresh?` | `boolean` | Re-scan even if Connect has a fresh result cached. For an operator who has just shipped a fix - not for automatic calls. |
| `options` | \{ `signal?`: `AbortSignal`; \} | - |
| `options.signal?` | `AbortSignal` | - |

###### Returns

`Promise`\<[`PresenceScanResult`](/docs/reference/core#type-alias-presencescanresult)\>

***

### Interface: EntitlementsResource [#interface-entitlementsresource]

The two checks worth making on every request: may this workspace be here at
all, and what is it paying for.

#### Methods

##### getReadiness()

```ts
getReadiness(workspaceId: string): Promise<ReadinessResponse>;
```

Every gate Connect owns between a workspace and your application -
subscription, KYC/KYB, a chargeable payment method, seat headroom -
resolved in one call.

Blocked users are the point: each blocker carries a `resolveUrl` into the
Connect screen that fixes it, so you can route them somewhere useful
instead of refusing them with nowhere to go.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`ReadinessResponse`](/docs/reference/core#interface-readinessresponse)\>

***

##### getSubscription()

```ts
getSubscription(workspaceId: string): Promise<SubscriptionResponse>;
```

The live plan and subscription, including `plan.features`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse)\>

***

##### hasFeature()

```ts
hasFeature(workspaceId: string, feature: string): Promise<boolean>;
```

Whether an active subscription unlocks a plan feature - the check that
gates a plan-restricted module.

Deliberately `false` rather than a throw when there is no subscription at
all: "not entitled" is the same answer either way, and a feature gate
should not need a try/catch around it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `feature` | `string` |

###### Returns

`Promise`\<`boolean`\>

***

##### getLimit()

```ts
getLimit(workspaceId: string, key: string): Promise<PlanLimit>;
```

How much of a metered resource this workspace may have - the numeric
counterpart of `hasFeature`, for the resources your application counts
itself (sites, projects, pipelines).

**This is the effective ceiling, not the plan's own number.** Connect has
already folded in whatever the add-ons on the subscription raise it by, so
"Starter includes 1 site, they bought two 5-site packs" arrives here as
`{ model: "limited", value: 11 }`. You never join the plan against the
add-on list yourself, and a customer who buys another pack needs no change
on your side.

Returns `{ model: "none" }` rather than throwing when there is no active
subscription, when the plan carries no answer for this key, and when the
Connect instance predates limits entirely - the same reasoning
`hasFeature` gives for answering `false`: "not entitled" is the same answer
either way, and a limit check should not need a try/catch around it.

Connect does not enforce this. It cannot see what you are counting, so it
hands you the number and your code refuses the next row - `canAdd` is that
comparison if you already have the count.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `key` | `string` |

###### Returns

`Promise`\<[`PlanLimit`](/docs/reference/core#interface-planlimit)\>

***

##### canAdd()

```ts
canAdd(
   workspaceId: string, 
   key: string, 
   currentCount: number, 
additional?: number): Promise<boolean>;
```

Whether this workspace may add `additional` more of a limited resource,
given how many it already has.

You supply the count, because only your application can produce it. This
is `getLimit` plus `planLimitAllows`, which exists so the comparison is
not rewritten per call site - see that helper for the three ways of
getting it wrong.

```ts
if (!(await connect.entitlements.canAdd(workspaceId, "sites", sites.length))) {
  return refuse("Your plan's site limit is reached.");
}
```

###### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `workspaceId` | `string` | `undefined` |
| `key` | `string` | `undefined` |
| `currentCount` | `number` | `undefined` |
| `additional` | `number` | `1` |

###### Returns

`Promise`\<`boolean`\>

***

##### reportUsage()

```ts
reportUsage(workspaceId: string, counts: Readonly<Record<string, number>>): Promise<ReportUsageResponse>;
```

Tell Connect how many of your limited resources a workspace has right
now, so the customer's Usage page can show "640 of 1,000" against the
ceiling `getLimit` returns. Needs a key scoped to `usage:report`.

Each value is the **current count**, not a change - the number you
already pass to `canAdd`. Reporting it twice is harmless and a lost
report is healed by the next, which is why no idempotency key is sent.
Keys you leave out keep their last value. A key outside your limit
catalog is refused by name.

Connect enforces nothing on these numbers; your `canAdd` check is still
the gate. So a failure here should never fail the user's action - report
after it, and catch:

```ts
await db.insert(record);
connect.entitlements
  .reportUsage(workspaceId, { records: await db.count(workspaceId) })
  .catch(() => {}); // the Usage page is a moment stale; nothing else is affected
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `counts` | `Readonly`\<`Record`\<`string`, `number`\>\> |

###### Returns

`Promise`\<[`ReportUsageResponse`](/docs/reference/core#interface-reportusageresponse)\>

***

##### listSubscribers()

```ts
listSubscribers(options?: {
  status?: "created" | "authenticated" | "active" | "paused" | "cancelled";
  before?: string;
  limit?: number;
}): Promise<SubscribersResponse>;
```

One page of the workspaces subscribed to your application, newest first.

The inverse of `getSubscription`, which asks about one workspace you
already know. This asks who your subscribers *are* - what a partner
dashboard, an MRR total or a churn report is built on.

Rate-limited far more tightly than the per-workspace read (10/min per
key, against 60): it walks your whole subscriber base and is a reporting
call, not a per-request gate. Use `getSubscription` in a hot path.

Your application is resolved from the API key, so there is no parameter
for it and no way to ask about anyone else's.

###### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `options` | \{ `status?`: `"created"` \| `"authenticated"` \| `"active"` \| `"paused"` \| `"cancelled"`; `before?`: `string`; `limit?`: `number`; \} | - |
| `options.status?` | `"created"` \| `"authenticated"` \| `"active"` \| `"paused"` \| `"cancelled"` | - |
| `options.before?` | `string` | Cursor from a previous page's `nextBefore`. |
| `options.limit?` | `number` | 1..100, default 25. |

###### Returns

`Promise`\<[`SubscribersResponse`](/docs/reference/core#interface-subscribersresponse)\>

***

##### iterateSubscribers()

```ts
iterateSubscribers(options?: {
  status?: "created" | "authenticated" | "active" | "paused" | "cancelled";
  pageSize?: number;
}): AsyncGenerator<ApplicationSubscriber>;
```

Every subscriber, paged through for you.

An async generator rather than an array: a large subscriber base is
exactly where an accidental full materialisation hurts, and `for await`
reads the same as a loop over one page.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | \{ `status?`: `"created"` \| `"authenticated"` \| `"active"` \| `"paused"` \| `"cancelled"`; `pageSize?`: `number`; \} |
| `options.status?` | `"created"` \| `"authenticated"` \| `"active"` \| `"paused"` \| `"cancelled"` |
| `options.pageSize?` | `number` |

###### Returns

`AsyncGenerator`\<[`ApplicationSubscriber`](/docs/reference/core#interface-applicationsubscriber)\>

***

### Interface: ExchangeCodeInput [#interface-exchangecodeinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `code` | `string` | - |
| `redirectUri` | `string` | Must be byte-identical to the one sent to `authorizeUrl()`. |
| `codeVerifier?` | `string` | - |

***

### Interface: ExecuteIntegrationActionInput [#interface-executeintegrationactioninput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `toolkitSlug` | `string` | - |
| `actionSlug` | `string` | - |
| `params?` | `Record`\<`string`, `unknown`\> | Passed through to Composio verbatim - its shape is defined by the toolkit's own action schema, which Connect does not model. |

***

### Interface: FindSlotsInput [#interface-findslotsinput]

#### Extends

- `ActingAsOption`

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `asUser?` | `string` | The signed-in member's opaque Connect session token. | `ActingAsOption.asUser` |
| `workspaceId` | `string` | - | - |
| `eventTypeId` | `string` | - | - |
| `from` | `string` \| `Date` | ISO-8601 instant, or a `Date`. | - |
| `to` | `string` \| `Date` | - | - |
| `excludeBookingId?` | `string` | A booking to treat as if it were not there - **set this when offering times to move an existing appointment to.** Without it the appointment counts as blocking its own replacement, so the adjustment people actually make - same day, half an hour later - comes back with nothing available. | - |

***

### Interface: IdentityResource [#interface-identityresource]

"Login with Connect" - the OAuth2 handshake, and the session that outlives
it.

The whole flow is five calls: `authorizeUrl` to send them out,
`exchangeCode` when they come back, `verifyIdToken` once, then `getUser` on
each later request and `logout` at the end.

#### Methods

##### authorizeUrl()

```ts
authorizeUrl(input: AuthorizeUrlInput): string;
```

Step 1 - the URL to send the user's browser to.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`AuthorizeUrlInput`](#interface-authorizeurlinput) |

###### Returns

`string`

***

##### oneTapEndpoints()

```ts
oneTapEndpoints(): 
  | {
  hintUrl: string;
  authorizeUrl: string;
}
  | null;
```

The two Connect endpoints One Tap talks to **from the browser**, or `null`
when this client has no `clientId` configured and the flow cannot run.

Handed out by the bridge rather than built in the page, so One Tap needs no
client-side configuration at all - a `./next` application sets
`CONNECT_CLIENT_ID` server-side and nothing else. Both URLs carry only the
public `client_id`; neither is a credential.

Returning `null` rather than throwing (as `authorizeUrl` does) because this
is read on the `/session` route, which must keep answering for an
application that has no OAuth client configured at all.

###### Returns

  \| \{
  `hintUrl`: `string`;
  `authorizeUrl`: `string`;
\}
  \| `null`

***

##### publicClientId()

```ts
publicClientId(): string | null;
```

This client's public `client_id`, or `null` when none is configured.

The bridge hands it to the browser on `/session` so `openConnect` can name
the application on every hand-off without the host passing `clientId` to
`<ConnectProvider>` too - Connect only honours a `return_to` it can match
against *a client's* registered redirect origins. Public by definition:
it already rides on every authorize URL.

###### Returns

`string` \| `null`

***

##### exchangeCode()

```ts
exchangeCode(input: ExchangeCodeInput): Promise<TokenResponse>;
```

Step 2 - trade the one-time `code` for a session. Server-side only.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ExchangeCodeInput`](#interface-exchangecodeinput) |

###### Returns

`Promise`\<[`TokenResponse`](/docs/reference/core#interface-tokenresponse)\>

***

##### verifyIdToken()

```ts
verifyIdToken(idToken: string): Promise<IdTokenClaims>;
```

Step 3 - verify the `id_token` once, then discard it.

HS256 over `header.payload`, keyed with your API key, plus the `exp` and
`aud` checks. There is no JWKS to fetch: Connect stores only your key's
sha256 hash, so the token is signed with the shared secret rather than an
asymmetric key. Signature comparison goes through WebCrypto's own
`verify`, which is why this is async - hand-rolling the compare is the
step integrations most often get wrong.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `idToken` | `string` |

###### Returns

`Promise`\<[`IdTokenClaims`](/docs/reference/core#interface-idtokenclaims)\>

***

##### getUser()

```ts
getUser(sessionToken: string): Promise<UserResponse>;
```

Step 4 - who is signed in, right now. Same blocks as `exchangeCode`,
re-resolved live, so a role change or a lapsed subscription shows up here
without another sign-in.

Any `ConnectError` with `isSessionExpired` means send them back through
`authorizeUrl()`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `sessionToken` | `string` |

###### Returns

`Promise`\<[`UserResponse`](/docs/reference/core#interface-userresponse)\>

***

##### getAccountUser()

```ts
getAccountUser(accountSession: string): Promise<UserResponse>;
```

`getUser`, for a **CosmoOps account session** instead of this
application's own session token - first-party applications only.

The account session is the opaque id in the parent-domain cookie Connect
sets at sign-in (`CONNECT_ACCOUNT_COOKIE`). One id is shared by every
CosmoOps app, but the answer is per application: the client credentials
say which app is asking, so `role`, `permissions` and `subscription` are
this application's, and every entry check `getUser` implies still runs.

Same wire shape and the same mapping as `getUser`, and the same failure:
an unknown, revoked or expired id is `invalid_token`, so
`isSessionExpired` is still the "send them to sign in" test. A client
that is not marked first-party in Console is refused `invalid_client`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `accountSession` | `string` |

###### Returns

`Promise`\<[`UserResponse`](/docs/reference/core#interface-userresponse)\>

***

##### logoutAccount()

```ts
logoutAccount(accountSession: string): Promise<void>;
```

Ends the account session - in every CosmoOps app *and* in Connect itself,
because it revokes the one session record they all point at. Clearing the
cookie is the caller's job (the `./next` bridge does it on `/logout`).

Always succeeds for an unknown or already-revoked id, like `logout`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `accountSession` | `string` |

###### Returns

`Promise`\<`void`\>

***

##### switchAccountWorkspace()

```ts
switchAccountWorkspace(accountSession: string, workspaceId: string): Promise<UserResponse>;
```

Moves the account session to another workspace, so every CosmoOps app
follows - the active workspace lives on the shared session record rather
than in a per-application token. Answers the re-resolved session, in
`getAccountUser`'s shape. `forbidden` when the member does not belong to
`workspaceId`.

The account id does not rotate, so there is no cookie to rewrite.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `accountSession` | `string` |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`UserResponse`](/docs/reference/core#interface-userresponse)\>

***

##### accountContinueUrl()

```ts
accountContinueUrl(input: {
  returnTo: string;
}): string;
```

Where to send a browser with no account session.

Connect's continue endpoint signs them in if needed, makes sure the
account cookie is set, and redirects to `returnTo` - no OAuth code
exchange and no consent screen. `returnTo` must be absolute, and its
**origin** must be one of this client's registered redirect URI origins,
or Connect answers 400.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `returnTo`: `string`; \} |
| `input.returnTo` | `string` |

###### Returns

`string`

***

##### logout()

```ts
logout(sessionToken: string, options?: {
  scope?: LogoutScope;
}): Promise<void>;
```

Step 5 - end the sign-in immediately rather than leaving it valid for the
rest of its ~15-day life. Discard your own copy of `sessionToken` too;
this does not do that for you.

`scope` defaults to `"device"`: this sign-in, every other application signed
into from the same browser, and Connect's own session on that one device.
`"all"` ends every session on every device - an explicit "sign out
everywhere" action only.

Always succeeds. An unknown or already-revoked token is an
indistinguishable no-op, so there is nothing to branch on.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `sessionToken` | `string` |
| `options` | \{ `scope?`: [`LogoutScope`](/docs/reference/core#type-alias-logoutscope); \} |
| `options.scope?` | [`LogoutScope`](/docs/reference/core#type-alias-logoutscope) |

###### Returns

`Promise`\<`void`\>

***

### Interface: InitiateIntegrationConnectionInput [#interface-initiateintegrationconnectioninput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `toolkitSlug` | `string` | - |
| `returnUrl` | `string` | - |
| `userId` | `string` | The signed-in member starting the connection, recorded by Connect as `connectedByUserId`. Required, and deliberately not defaulted: the connection is *shared by the whole workspace*, so "who turned this on" is the only attribution anyone gets - and a call that cannot name a person is one where nobody is present to finish the OAuth screen either. |
| `connectionData?` | `Record`\<`string`, `string`\> | Pre-fills the toolkit's own connection fields on the provider screen - `{ extension: "in" }` puts a Zoho connection on the India data centre, which otherwise defaults to `zoho.com`. Which keys a toolkit reads is Composio's catalogue; Connect passes them through. |

***

### Interface: IntegrationsResource [#interface-integrationsresource]

Composio-hosted third-party connections (Google Calendar, Slack,
Twitter/X, ...) a workspace has connected. Connect proxies every call
here - your application never sees or holds the underlying OAuth token, which
stays with Composio.

#### Methods

##### list()

```ts
list(workspaceId: string): Promise<IntegrationToolkit[]>;
```

Enabled toolkits and this workspace's connection status for each.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |

###### Returns

`Promise`\<[`IntegrationToolkit`](/docs/reference/core#interface-integrationtoolkit)[]\>

***

##### initiateConnect()

```ts
initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
```

Starts a Composio-hosted connection for one toolkit. The returned
`redirectUrl` is where the browser completes the provider's OAuth
screen, then Composio bounces it back to `returnUrl`.

**The connection is not active when this returns.** Treat the toolkit
as `pending` until a later `list()` call reports `active` - only
Connect's own webhook from Composio confirms that, never the redirect
itself. Reconnecting a disconnected/failed toolkit calls this same
method - there is no separate reconnect.

Meant to be called on behalf of a signed-in workspace admin (this is what
the bridge's fixed `POST /integrations/connect` route does), not as a bare
background action with no admin present to complete the OAuth screen. That
is why `userId` is part of the input rather than inferred - see
`InitiateIntegrationConnectionInput`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `input` | [`InitiateIntegrationConnectionInput`](#interface-initiateintegrationconnectioninput) |

###### Returns

`Promise`\<[`InitiateIntegrationConnectionResponse`](/docs/reference/core#interface-initiateintegrationconnectionresponse)\>

***

##### disconnect()

```ts
disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
```

Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `toolkitSlug` | `string` |

###### Returns

`Promise`\<[`DisconnectIntegrationResponse`](/docs/reference/core#interface-disconnectintegrationresponse)\>

***

##### request()

```ts
request(workspaceId: string, input: ProxyIntegrationRequestInput): Promise<ProxyIntegrationRequestResponse>;
```

One raw request to a connected provider's own API, for an endpoint the
toolkit has no action for (Zoho Books ships no "create sales order").

**Resolves whenever Connect relayed the request**, whatever the provider
answered: read `status` and `data` for that. A provider-side 4xx is a
business answer ("that GSTIN is invalid") rather than a transport
failure, so it is not thrown as one.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `input` | [`ProxyIntegrationRequestInput`](#interface-proxyintegrationrequestinput) |

###### Returns

`Promise`\<[`ProxyIntegrationRequestResponse`](/docs/reference/core#interface-proxyintegrationrequestresponse)\>

***

##### execute()

```ts
execute(workspaceId: string, input: ExecuteIntegrationActionInput): Promise<ExecuteIntegrationActionResponse>;
```

Calls a Composio action against this workspace's connected account for the named toolkit.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `input` | [`ExecuteIntegrationActionInput`](#interface-executeintegrationactioninput) |

###### Returns

`Promise`\<[`ExecuteIntegrationActionResponse`](/docs/reference/core#interface-executeintegrationactionresponse)\>

***

### Interface: KvResource [#interface-kvresource]

Your application's key/value store - small values addressed by namespace and
key, per workspace.

Use it for what a document store is bad at: a feature flag, a cached
computation, a counter, a draft nobody needs to query. Anything you want to
*find* rather than *fetch* belongs in `data`.

Namespaces are declared in the Console exactly like collections. Keys are
`[A-Za-z0-9_-]` up to 64 characters, and values are JSON up to 128 KB.

`asUser` behaves exactly as it does on `DataResource` - pass a signed-in
user's `sessionToken` and Connect derives the user and workspace from it,
checking the namespace's declared permission against their live roles.

#### Methods

##### get()

```ts
get(input: {
  workspaceId?: string;
  namespace: string;
  key: string;
  asUser?: string;
}): Promise<KvEntry>;
```

Reads one key. Throws `ConnectError` with status 404 when the key is
absent **or expired** - expiry is evaluated on read, so there is no window
where a stale value is still served.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `namespace`: `string`; `key`: `string`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.namespace` | `string` |
| `input.key` | `string` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`KvEntry`](/docs/reference/core#interface-kventry)\>

***

##### list()

```ts
list(input: {
  workspaceId?: string;
  namespace: string;
  limit?: number;
  cursor?: string | null;
  asUser?: string;
}): Promise<KvPage>;
```

Pages a namespace in key order.

Expired entries are filtered out after paging, so a page can come back
shorter than `limit` while still having a `nextCursor` - treat
`nextCursor` as the only signal that more keys exist, never an empty page.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `namespace`: `string`; `limit?`: `number`; `cursor?`: `string` \| `null`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.namespace` | `string` |
| `input.limit?` | `number` |
| `input.cursor?` | `string` \| `null` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`KvPage`](/docs/reference/core#interface-kvpage)\>

***

##### set()

```ts
set(input: {
  workspaceId?: string;
  namespace: string;
  key: string;
  value: unknown;
  ttlSeconds?: number;
  asUser?: string;
}): Promise<KvEntry>;
```

Sets a value, replacing whatever was there. `ttlSeconds` expires it; omit for no expiry.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `namespace`: `string`; `key`: `string`; `value`: `unknown`; `ttlSeconds?`: `number`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.namespace` | `string` |
| `input.key` | `string` |
| `input.value` | `unknown` |
| `input.ttlSeconds?` | `number` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<[`KvEntry`](/docs/reference/core#interface-kventry)\>

***

##### delete()

```ts
delete(input: {
  workspaceId?: string;
  namespace: string;
  key: string;
  asUser?: string;
}): Promise<void>;
```

Idempotent - deleting an absent key succeeds.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId?`: `string`; `namespace`: `string`; `key`: `string`; `asUser?`: `string`; \} |
| `input.workspaceId?` | `string` |
| `input.namespace` | `string` |
| `input.key` | `string` |
| `input.asUser?` | `string` |

###### Returns

`Promise`\<`void`\>

***

### Interface: ListBookingsInput [#interface-listbookingsinput]

#### Extends

- `ActingAsOption`

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `asUser?` | `string` | The signed-in member's opaque Connect session token. | `ActingAsOption.asUser` |
| `workspaceId` | `string` | - | - |
| `status?` | [`BookingStatus`](/docs/reference/core#type-alias-bookingstatus) | - | - |
| `hostMemberId?` | `string` | A `ConnectWorkspaceMember.memberId` - only bookings that committed them. **Not a `userId`.** Pass one and the filter matches nothing, which comes back as an empty diary rather than an error. | - |
| `from?` | `string` \| `Date` | - | - |
| `to?` | `string` \| `Date` | - | - |
| `order?` | `"asc"` \| `"desc"` | `asc` for a forward diary, `desc` (the default) for a history. | - |
| `cursor?` | `string` | - | - |
| `limit?` | `number` | - | - |

***

### Interface: ListCallsInput [#interface-listcallsinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `channelId?` | `string` | Narrow to one channel. A channel belonging to another application simply matches nothing. |
| `placedByApplication?` | `boolean` | `true` for the member calls your UI placed (`<ConnectCallButton>`) instead of the calls on your channels. Those leave from the workspace's own number, so the default list never contains them - this is the list to reconcile a timeline from when a webhook went to another deployment. |
| `workspaceId?` | `string` | With `placedByApplication`, one workspace's calls. |
| `cursor?` | `string` | From a previous page's `nextCursor`. |
| `limit?` | `number` | 1-100, default 50. |

***

### Interface: ListChannelsInput [#interface-listchannelsinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId?` | `string` | Narrow to one workspace. Omit to list every channel assigned to your application. |
| `cursor?` | `string` | From a previous page's `nextCursor`. |
| `limit?` | `number` | 1-100, default 50. |

***

### Interface: LogsResource [#interface-logsresource]

Push feature events into the Console's audit log, filterable to your application.

#### Methods

##### write()

```ts
write(input: WriteLogInput): Promise<WriteLogResponse>;
```

Never throws. A logging call that takes down the code path it was
observing is worse than a lost line, so a failure here is swallowed and
reported through the return value.

Pass `sessionToken` to attribute the event to the end user it belongs to.
Both scopes on the stored entry come from something verified - the application
from your API key, the user from that token, which Connect checks was
minted for your application before attributing anything. `attributed` in the
result says whether it resolved; a `false` there still means the line was
stored, just without a user on it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`WriteLogInput`](/docs/reference/core#interface-writeloginput) |

###### Returns

`Promise`\<[`WriteLogResponse`](/docs/reference/core#interface-writelogresponse)\>

***

### Interface: LookupContactInput [#interface-lookupcontactinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `phone` | `string` | Any reasonable spelling. `+919000000001`, `919000000001` and `+91 90000 00001` all resolve to the same contact - the number is normalized to E.164 before the lookup, because comparing unnormalized is how a contact that exists reads as absent. |
| `asUser?` | `string` | See `ActingAsOption` on the scheduling resource - same contract. |

***

### Interface: OtpResource [#interface-otpresource]

WhatsApp/SMS phone verification.

#### Methods

##### send()

```ts
send(input: SendOtpInput, options?: {
  idempotencyKey?: string;
}): Promise<SendOtpResponse>;
```

Send a code. As with `billing.consumeAddon`, an `Idempotency-Key` always
goes along - a retried send would otherwise deliver a second message and
invalidate the first code the user is looking at.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`SendOtpInput`](/docs/reference/core#interface-sendotpinput) |
| `options` | \{ `idempotencyKey?`: `string`; \} |
| `options.idempotencyKey?` | `string` |

###### Returns

`Promise`\<[`SendOtpResponse`](/docs/reference/core#interface-sendotpresponse)\>

***

##### verify()

```ts
verify(input: VerifyOtpInput): Promise<VerifyOtpResponse>;
```

Check a 6-digit code against a `verificationId`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`VerifyOtpInput`](/docs/reference/core#interface-verifyotpinput) |

###### Returns

`Promise`\<[`VerifyOtpResponse`](/docs/reference/core#interface-verifyotpresponse)\>

***

### Interface: PlatformResource [#interface-platformresource]

Facts about the platform itself, rather than about a workspace or a user.

One method today, and the resource exists anyway rather than hanging
`getCompanyProfile` off `identity` or `workspaces`: neither of those is
what it is about. This answers "who operates the thing my application runs
on", which is a different subject from every other resource here, and a
misfiled method is harder to find than an obvious one-method namespace.

#### Methods

##### getCompanyProfile()

```ts
getCompanyProfile(): Promise<CompanyProfile>;
```

The operating entity's legal identity and its DPDP grievance channel.

Takes no `workspaceId` and takes no `asUser` - the answer is the same for
every tenant and every caller, because it is a fact about the platform.

Returns the inner `companyProfile` rather than the envelope, matching
`apps.list()` and `billing.listPlans()`: the wrapper is a wire detail, and
every caller would destructure it on the next line.

**Do not copy the result into your own configuration.** Cache it if you
render it on a hot path - Connect already sends
`s-maxage=3600, stale-while-revalidate=86400` - but the reason this is an
endpoint at all is that a grievance officer copied into `.env` goes stale
without anyone finding out.

###### Returns

`Promise`\<[`CompanyProfile`](/docs/reference/core#interface-companyprofile)\>

***

### Interface: ProxyIntegrationRequestInput [#interface-proxyintegrationrequestinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `toolkitSlug` | `string` | - |
| `method` | [`IntegrationProxyMethod`](/docs/reference/core#type-alias-integrationproxymethod) | - |
| `endpoint` | `string` | A path relative to the provider's API base - `/salesorders`, not a URL. Connect refuses anything with a scheme, a host, a `?` or a dot segment, because whoever picks the host picks where the workspace's credential goes. Parameters belong in `query`. |
| `query?` | `Record`\<`string`, `string`\> | - |
| `body?` | `unknown` | - |
| `onBehalfOfUserId?` | `string` \| `null` | Whose connection to act through - omitted or `null` for the workspace's shared one. |

***

### Interface: RescheduleBookingInput [#interface-reschedulebookinginput]

#### Extends

- `ActingAsOption`

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `asUser?` | `string` | The signed-in member's opaque Connect session token. | `ActingAsOption.asUser` |
| `workspaceId` | `string` | - | - |
| `bookingId` | `string` | - | - |
| `startAt` | `string` \| `Date` | The new time, from a `findSlots` result, passed back unchanged. | - |
| `notes?` | `string` \| `null` | Replaces the original note when given; it carries over otherwise. | - |

***

### Interface: SchedulingResource [#interface-schedulingresource]

A subscribing workspace's own calendar.

**There is one calendar, not one per application.** A booking made here
appears on the workspace's own screen immediately, and a slot their
receptionist just took is gone from your next `findSlots`. That is the
reason to read it through Connect rather than keeping a copy: two calendars
double-book, and nothing reconciles them afterwards.

Every method needs a live subscription to your application in the workspace
you name. The key says which application is calling; the subscription is
what makes you welcome in a given tenant.

#### Methods

##### listEventTypes()

```ts
listEventTypes(workspaceId: string, options?: ActingAsOption): Promise<BookableEventType[]>;
```

What this workspace offers as bookable. Active appointment types only.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `options` | `ActingAsOption` |

###### Returns

`Promise`\<[`BookableEventType`](/docs/reference/core#interface-bookableeventtype)[]\>

***

##### listSchedules()

```ts
listSchedules(workspaceId: string, options?: ActingAsOption): Promise<AvailabilitySchedule[]>;
```

The working-hours templates an appointment type can point at.

Read this before offering to create one - a workspace that already has
hours set does not need a second "Working hours" from your integration.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `options` | `ActingAsOption` |

###### Returns

`Promise`\<[`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)[]\>

***

##### createSchedule()

```ts
createSchedule(input: CreateScheduleInput): Promise<AvailabilitySchedule>;
```

Gives a workspace its first working hours.

Narrower than what the workspace's own Calendar settings can do - no date
overrides, and never owned by one person (`ownerMemberId` stays `null`
server-side). Anything beyond that is a Console edit, not a second write
path for your integration to maintain.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `CreateScheduleInput` |

###### Returns

`Promise`\<[`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)\>

***

##### createEventType()

```ts
createEventType(input: CreateEventTypeInput): Promise<ManageableEventType>;
```

The first thing a workspace can be booked for.

Needs a schedule - create or pick one with `listSchedules`/`createSchedule`
first. The slug for a future public booking link is derived from `name`
server-side; you cannot choose one.

Answers the richer *manageable* shape, not `BookableEventType` - you just
created this row and are about to manage it, not book against it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `CreateEventTypeInput` |

###### Returns

`Promise`\<[`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)\>

***

##### listManageableEventTypes()

```ts
listManageableEventTypes(workspaceId: string, options?: ActingAsOption): Promise<ManageableEventType[]>;
```

The richer read `createEventType`/`updateEventType`'s edit mode needs -
everything `listEventTypes`'s booking catalogue deliberately withholds,
because this is the opposite audience: a member managing their own
calendar, not a widget rendering someone else's. Includes inactive rows,
since an edit screen is exactly where one gets reactivated.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `options` | `ActingAsOption` |

###### Returns

`Promise`\<[`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)[]\>

***

##### updateSchedule()

```ts
updateSchedule(input: UpdateScheduleInput): Promise<AvailabilitySchedule>;
```

Edits an existing availability schedule.

Narrower than what the workspace's own Calendar settings can do, the
same way `createSchedule` is: `ownerMemberId` and date overrides stay
untouched, since this surface never sets either.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `UpdateScheduleInput` |

###### Returns

`Promise`\<[`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)\>

***

##### updateEventType()

```ts
updateEventType(input: UpdateEventTypeInput): Promise<ManageableEventType>;
```

Edits an existing appointment type.

`hostMemberId` omitted leaves the host untouched, including a *group*
host this surface cannot represent - so an edit that says nothing about
the host can never accidentally overwrite a team assignment made in
Connect.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `UpdateEventTypeInput` |

###### Returns

`Promise`\<[`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)\>

***

##### findSlots()

```ts
findSlots(input: FindSlotsInput): Promise<BookingSlots>;
```

Free times for one appointment type over one window.

**Advisory by construction.** Between this answer and `createBooking`,
anyone else on that calendar can take any of these - a member, a voice
agent mid-call, someone on the workspace's public booking link. That is
why `createBooking` can answer `409`, and why that is an ordinary outcome
rather than a fault.

The window is clamped silently to now and to the appointment type's own
booking horizon, so asking for a year of a sixty-day type returns sixty
days rather than an error.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`FindSlotsInput`](#interface-findslotsinput) |

###### Returns

`Promise`\<[`BookingSlots`](/docs/reference/core#interface-bookingslots)\>

***

##### getBooking()

```ts
getBooking(input: ActingAsOption & {
  workspaceId: string;
  bookingId: string;
}): Promise<Booking>;
```

One appointment, by the id you stored.

**This is the read to make when you kept a booking id on a record of your
own** - a meeting on a deal, an appointment on a case. `listBookings`
cannot serve it: that is a window over `startAt`, and the `startAt` you
cached is exactly what is wrong once someone has moved the appointment on
the workspace's own calendar, so the window you build from it comes back
without the row.

A retired booking still resolves. `status: "rescheduled"` answers with
`rescheduledToId`, so an id stored months ago leads to the appointment
that exists today; `cancelled` answers with its reason. `not_found` means
this workspace never had that booking.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `ActingAsOption` & \{ `workspaceId`: `string`; `bookingId`: `string`; \} |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

##### listBookings()

```ts
listBookings(input: ListBookingsInput): Promise<ListBookingsPage>;
```

One page of the workspace's diary.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`ListBookingsInput`](#interface-listbookingsinput) |

###### Returns

`Promise`\<[`ListBookingsPage`](/docs/reference/core#interface-listbookingspage)\>

***

##### createBooking()

```ts
createBooking(input: CreateBookingInput): Promise<Booking>;
```

Takes one of the times `findSlots` offered.

Holds and confirms in one call - you already have the attendee, so there
is no half-finished state for your integration to remember. The slot is
re-checked inside a transaction, so a time taken since you read it throws
a `ConnectError` with `code: "conflict"` rather than double-booking.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`CreateBookingInput`](#interface-createbookinginput) |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

##### holdSlot()

```ts
holdSlot(input: HoldSlotInput): Promise<Booking>;
```

Takes a slot now, for a booking whose attendee you will learn afterwards -
a voice agent reads three times aloud, the caller picks one, *then* gives a
name. If you already have the attendee, `createBooking` does both at once.

The answer is a real booking with `status: "hold"`: from this moment the
slot is gone from everyone else's `findSlots`, including the workspace's
own public booking page. A time taken since you read it throws a
`ConnectError` with `code: "conflict"`.

It lapses at `holdExpiresAt` and frees the slot the moment it does, so an
abandoned call holds nothing. Only your application can confirm it, with
`confirmHold`. Say nothing to the caller about being booked until that
has returned.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `HoldSlotInput` |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

##### confirmHold()

```ts
confirmHold(input: ConfirmHoldInput): Promise<Booking>;
```

Turns a hold from `holdSlot` into a booking, and tells the attendee.

Idempotent: confirming a booking that is already confirmed returns it
unchanged and notifies nobody, so retrying after a dropped response is
safe. A hold that lapsed throws `conflict` - hold a slot again. An id your
application did not hold throws `not_found`.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `ConfirmHoldInput` |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

##### rescheduleBooking()

```ts
rescheduleBooking(input: RescheduleBookingInput): Promise<RescheduledBooking>;
```

Moves an appointment to a different time.

**The original is retired, not edited.** It stays on the diary as
`rescheduled` with `rescheduledToId` pointing at the replacement, and both
come back here - so a booking id you cached still resolves, and still says
where the appointment went.

A refused move changes nothing: the new slot is claimed before the old one
is released, so a `conflict` leaves the customer with the appointment they
already had. Re-read the slots (with `excludeBookingId`) and offer another.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`RescheduleBookingInput`](#interface-reschedulebookinginput) |

###### Returns

`Promise`\<[`RescheduledBooking`](/docs/reference/core#interface-rescheduledbooking)\>

***

##### cancelBooking()

```ts
cancelBooking(input: ActingAsOption & {
  workspaceId: string;
  bookingId: string;
  reason?: string | null;
}): Promise<Booking>;
```

Frees the slot and records why.

Not a deletion: the booking stays on the diary as `cancelled` with the
reason, which is what makes a late cancellation something anyone can look
up afterwards.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `ActingAsOption` & \{ `workspaceId`: `string`; `bookingId`: `string`; `reason?`: `string` \| `null`; \} |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

##### recordAttendance()

```ts
recordAttendance(input: ActingAsOption & {
  workspaceId: string;
  bookingId: string;
  attended: boolean;
}): Promise<Booking>;
```

Records whether the attendee turned up: `attended: false` marks the
booking `no_show`, `true` takes a mistaken no-show back to `confirmed`.

There is no "held" to set - a `confirmed` booking whose time has passed
already is one. Answers `409` for a booking that is not `confirmed` or
`no_show`, or has not started yet. The attendee is not notified.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `ActingAsOption` & \{ `workspaceId`: `string`; `bookingId`: `string`; `attended`: `boolean`; \} |

###### Returns

`Promise`\<[`Booking`](/docs/reference/core#interface-booking)\>

***

### Interface: SessionsResource [#interface-sessionsresource]

This Connect account's own login sessions - device, approximate location,
timestamps - the same list Connect's own account settings page shows the
user themselves.

**Account-wide, not scoped to this application.** A Connect session is one
browser's login to Connect, and it fans out to every partner application's own
`sessionToken` - Connect has no per-application session concept to filter by.
`list()` returns every session on the account, including ones that
originated from a completely different, unrelated application. Requires the
`sessions:manage` scope on your API key - unlike `identity`'s base `sso`
capability, this is never granted to a zero-scope legacy key by default.

Authenticated like `identity.*` - `client_id`+`client_secret`, never a
Bearer key - because every call here is really "resolve identity from a
`sessionToken`", the same shape `identity.getUser`/`identity.logout` are.

#### Methods

##### list()

```ts
list(sessionToken: string): Promise<ConnectUserSession[]>;
```

Every active session on the account behind `sessionToken`. `isCurrent` marks the one it was minted from.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `sessionToken` | `string` |

###### Returns

`Promise`\<[`ConnectUserSession`](/docs/reference/core#interface-connectusersession)[]\>

***

##### revoke()

```ts
revoke(sessionToken: string, sessionId: string): Promise<void>;
```

Ends one specific session by id - not necessarily the one `sessionToken`
itself was minted from. Cascades to every partner-application sign-in that
originated from that same browser, same as Connect's own "sign out this
device".

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `sessionToken` | `string` |
| `sessionId` | `string` |

###### Returns

`Promise`\<`void`\>

***

##### revokeOthers()

```ts
revokeOthers(sessionToken: string): Promise<RevokeOtherSessionsResponse>;
```

"Sign out everywhere else" - keeps the session `sessionToken` was minted from, ends every other one.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `sessionToken` | `string` |

###### Returns

`Promise`\<[`RevokeOtherSessionsResponse`](/docs/reference/core#interface-revokeothersessionsresponse)\>

***

### Interface: TasksResource [#interface-tasksresource]

Background work, delivered back to your own endpoint by Google Cloud Tasks.

Declare a queue in Console - its URL, its rate, its retries - then enqueue
here. Cloud Tasks `POST`s `{ workspaceId, queue, payload }` to the queue's
URL with a Google-signed OIDC token; verify it with `verifyConnectTask` (or
let `createConnectTaskHandler` do it) before trusting a byte of the body.

Deliveries are **at least once**. Make each step safe to run twice, and
give each enqueue a `name` derived from the step so a retried enqueue is a
`duplicate` rather than a second task.

#### Methods

##### enqueue()

```ts
enqueue(input: EnqueueTaskInput): Promise<EnqueueTaskResponse>;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`EnqueueTaskInput`](/docs/reference/core#interface-enqueuetaskinput) |

###### Returns

`Promise`\<[`EnqueueTaskResponse`](/docs/reference/core#interface-enqueuetaskresponse)\>

***

### Interface: TelephonyResource [#interface-telephonyresource]

Calls on the numbers your application handles, for an application that
answers its own calls.

**Connect stays the carrier.** It holds the carrier account, the number
inventory and the rental; your application owns everything that happens on
a call. Every carrier callback for a number you handle reaches your
telephony webhook as a signed `TelephonyCarrierEvent` (verify it with
`verifyTelephonyEvent`), and for `answer`/`fallback` the XML you respond
with is executed on the call. These methods are the other direction - acts
on Connect's carrier account, each checked against the number being handled
by your application in the workspace you name.

#### Methods

##### dial()

```ts
dial(input: TelephonyDialInput): Promise<TelephonyDialResult>;
```

Rings `to` from a number you handle. The answer arrives at your webhook as
an `answer` event whose `params.RequestUUID` is the `providerRequestId`
returned here. Refused with `code: "do_not_call"` for a number on the
workspace's do-not-call list.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`TelephonyDialInput`](/docs/reference/core#interface-telephonydialinput) |

###### Returns

`Promise`\<[`TelephonyDialResult`](/docs/reference/core#interface-telephonydialresult)\>

***

##### hangup()

```ts
hangup(providerCallUuid: string, input: {
  workspaceId: string;
}): Promise<void>;
```

Ends a live call you own. `code: "not_found"` once it has already ended.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `providerCallUuid` | `string` |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<`void`\>

***

##### browserToken()

```ts
browserToken(input: BrowserTokenInput): Promise<BrowserTokenResult>;
```

A carrier credential a browser places calls with. Calls it places are
answered by your webhook - `channel: null`, and `params.From` carrying
the namespaced subject.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | [`BrowserTokenInput`](/docs/reference/core#interface-browsertokeninput) |

###### Returns

`Promise`\<[`BrowserTokenResult`](/docs/reference/core#interface-browsertokenresult)\>

***

##### archiveRecording()

```ts
archiveRecording(recordingId: string, input: ArchiveRecordingInput): Promise<ArchivedRecording>;
```

Moves a carrier recording into the workspace's Drive, as a file your
application uploaded, and deletes it from the carrier. Idempotent on
`recordingId` - a retry answers the first archive. Pass the `fileId` to
`connect.ai.analyzeCall` for a transcript.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `recordingId` | `string` |
| `input` | [`ArchiveRecordingInput`](/docs/reference/core#interface-archiverecordinginput) |

###### Returns

`Promise`\<[`ArchivedRecording`](/docs/reference/core#interface-archivedrecording)\>

***

### Interface: UploadInput [#interface-uploadinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `file` | `Blob` | Any `Blob`/`File`. In Node, `new Blob([buffer])` or `fs.openAsBlob(path)`. |
| `fileName?` | `string` | Defaults to the `Blob`'s own name when it has one. |
| `parentId?` | `string` | Target folder. Omit for the Drive root. |

***

### Interface: VerifyConnectTaskOptions [#interface-verifyconnecttaskoptions]

#### Extended by

- [`ConnectTaskHandlerOptions`](#interface-connecttaskhandleroptions)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `invoker?` | `string` | The invoker service account's email - the one the platform gives you with your API key. Defaults to `CONNECT_TASK_INVOKER`. |
| `audience?` | `string` | Your own origin, which Connect sets as the token's audience. Defaults to `CONNECT_TASK_AUDIENCE`, then to the request URL's origin. |
| `fetch?` | [`FetchLike`](#type-alias-fetchlike) | Injectable for tests. Defaults to the global `fetch`. |
| `now?` | () => `number` | Injectable for tests. Milliseconds since the epoch. |

***

### Interface: WorkspacesResource [#interface-workspacesresource]

#### Methods

##### get()

```ts
get(input: {
  workspaceId: string;
}): Promise<ConnectWorkspaceProfile>;
```

One subscribing workspace's identity - name, legal name, account type,
and its owner's display name and email - for your own server, with nobody
signed in. What an agent greets a caller with; where a note to the owner
goes.

Needs `workspace-profile:read` and your application installed in that
workspace. Cache it - a name changes about never.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |

###### Returns

`Promise`\<[`ConnectWorkspaceProfile`](/docs/reference/core#interface-connectworkspaceprofile)\>

***

##### list()

```ts
list(options: {
  asUser: string;
}): Promise<UserWorkspace[]>;
```

The workspaces this user could switch to.

Rows carry `inAudience` and `subscribed` so a switcher can show a
workspace it cannot select, with the reason. Hiding it instead reads as
data loss to the person who owns it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`UserWorkspace`](/docs/reference/core#interface-userworkspace)[]\>

***

##### create()

```ts
create(input: CreateUserWorkspaceInput, options: {
  asUser: string;
}): Promise<UserWorkspace>;
```

Opens another workspace under the tenant the signed-in user already
owns - a branch office, a second business unit.

Every guard `CreateWorkspaceUseCase` enforces still applies: only the
tenant's owner may call this, `type: "business"` still needs a non-free
email domain, and the tenant's plan still decides whether it may hold
another workspace at all - a `PreconditionError` surfaces as a thrown
`ConnectError` with a message written to be shown ("Your current plan
covers a single workspace...", "You've used all 3 of the workspaces
your plans include..."). There is no `tenantId` parameter: the caller
names no tenant, same as every other method on this resource names no
workspace.

`isActive`/`subscribed` on the result are always `false` - this does not
switch the session, and the new workspace has no subscription yet. Chain
`switch({workspaceId: result.workspaceId})` to move into it, but only
when `result.inAudience` - a workspace outside this application's
audience is refused by `switch` the same way `selectable()` in
`<ConnectWorkspaceSwitcher>` already treats it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `CreateUserWorkspaceInput` |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`UserWorkspace`](/docs/reference/core#interface-userworkspace)\>

***

##### switch()

```ts
switch(input: {
  workspaceId: string;
}, options: {
  asUser: string;
}): Promise<WorkspaceSwitchResult>;
```

Moves this session to another workspace, returning a **new session
token**. The one you passed is revoked.

Persist the result before navigating - dropping it signs the user out,
because the old token no longer works.

Throws with status 404 when the workspace does not exist *or* the user is
not a member (deliberately indistinguishable), and 403 when it is outside
your audience, requires an MFA factor they have not enrolled, or is the
workspace they are already on.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `workspaceId`: `string`; \} |
| `input.workspaceId` | `string` |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`WorkspaceSwitchResult`](/docs/reference/core#interface-workspaceswitchresult)\>

***

##### billingProfile()

```ts
billingProfile(options: {
  asUser: string;
}): Promise<WorkspaceBillingProfile>;
```

The billing identity of the workspace this session is on - its legal
name, GSTIN, registered address and entity form.

**For prefilling your own settings, once.** See
`WorkspaceBillingProfile`, which carries the whole argument: this is the
*Bill To* side, yours is the *Bill From* side, and they are the same facts
for most customers without being the same field. There is no write form.

```ts
// app/settings/page.tsx - offer "Use my billing details"
const token = await getConnectSessionToken();
const billing = token ? await connect.workspaces.billingProfile({ asUser: token }) : null;
```

**Needs two things, not one.** `workspaces:read` on your key, *and*
`billing:read` on the signed-in member - a tax identity belongs to
whoever can already see it in Connect's billing screens, so an ordinary
member gets a 403 here where they get a roster from `members()`. That
cannot be granted through a role on your application, deliberately.

So **treat the 403 as a normal branch**, not an error: offer the prefill
to whoever can use it and let everyone else type their own details. A
workspace admin is the person who sets this up anyway.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`WorkspaceBillingProfile`](/docs/reference/core#interface-workspacebillingprofile)\>

***

##### members()

```ts
members(options: {
  asUser: string;
  includeUnassigned?: boolean;
}): Promise<ConnectWorkspaceMember[]>;
```

The team of the workspace this session is on.

Members carry permissions already resolved for *your* application, not raw
Connect roles - so you never reimplement Connect's role resolution, and
you are told nothing about the customer's Connect account beyond what your
application needs.

**Seat holders only, by default.** A workspace member with no seat on your
application cannot sign in to it, so they are not part of your team and
do not belong in an owner picker or an assignee list. Pass
`includeUnassigned: true` from the screen where an admin *gives* a seat -
it needs the people who do not have one yet. That form needs the signed-in
member to be able to change access (a 403 otherwise), and every row
carries `assigned` so the screen can tell the two apart.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | \{ `asUser`: `string`; `includeUnassigned?`: `boolean`; \} |
| `options.asUser` | `string` |
| `options.includeUnassigned?` | `boolean` |

###### Returns

`Promise`\<[`ConnectWorkspaceMember`](/docs/reference/core#interface-connectworkspacemember)[]\>

***

##### access()

```ts
access(options: {
  asUser: string;
}): Promise<ConnectMemberAccess>;
```

Seats, what the caller may do with them, and the invitations already
holding one - everything a team screen needs to decide what to draw.

Behind `members:manage` rather than `workspaces:read`, and gated on the
caller's own `member:invite`: it names people who are not members yet.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`ConnectMemberAccess`](/docs/reference/core#interface-connectmemberaccess)\>

***

##### invite()

```ts
invite(input: {
  inviteeEmail: string;
  roleKey?: string | null;
}, options: {
  asUser: string;
}): Promise<ConnectInvitation>;
```

Invite someone into the workspace, with access to your application.

`roleKey` names a role from your own catalog (or the reserved `"admin"`);
omit it to add someone to the workspace with no access to your
application. You cannot send a Connect role - the grant is built server
side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
express reaches outside your application.

Spends a member seat when the invitation is created, not when it is
accepted. Throws `ConnectError` with status 409 and a message written to
be shown when there is no seat, and 403 when the caller is inviting
someone with access they do not hold themselves.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `inviteeEmail`: `string`; `roleKey?`: `string` \| `null`; \} |
| `input.inviteeEmail` | `string` |
| `input.roleKey?` | `string` \| `null` |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`ConnectInvitation`](/docs/reference/core#interface-connectinvitation)\>

***

##### setAccess()

```ts
setAccess(input: {
  userId: string;
  roleKey: string | null;
}, options: {
  asUser: string;
}): Promise<ConnectMemberAccessResult>;
```

Grant or revoke **your application's** role for an existing member.
`roleKey: null` revokes it.

Moves only your own grant: Connect recomputes the member's roles as what
they hold now, minus any grant naming your application, plus the one you
asked for. Their workspace role and every other application they use are
untouched, and there is no form of this that removes them from the
workspace.

Re-sending the role they already have is a no-op rather than an error, so
a form that submits whatever is on screen is safe.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `userId`: `string`; `roleKey`: `string` \| `null`; \} |
| `input.userId` | `string` |
| `input.roleKey` | `string` \| `null` |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<[`ConnectMemberAccessResult`](/docs/reference/core#interface-connectmemberaccessresult)\>

***

##### revokeInvitation()

```ts
revokeInvitation(input: {
  invitationId: string;
}, options: {
  asUser: string;
}): Promise<void>;
```

Withdraw a pending invitation your application sent, freeing its seat.

Only yours - an invitation carrying another application's grant is a 404,
deliberately indistinguishable from one that does not exist.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | \{ `invitationId`: `string`; \} |
| `input.invitationId` | `string` |
| `options` | \{ `asUser`: `string`; \} |
| `options.asUser` | `string` |

###### Returns

`Promise`\<`void`\>
## Type Aliases

### Type Alias: ConnectWebhookHandlers [#type-alias-connectwebhookhandlers]

```ts
type ConnectWebhookHandlers = { [K in ConnectWebhookEventType]?: (data: ConnectWebhookEventMap[K], envelope: ConnectWebhookEnvelope<K>) => void | Promise<void> };
```

One handler per event type. Every one is optional; unhandled types are acknowledged and ignored.

***

### Type Alias: FetchLike [#type-alias-fetchlike]

```ts
type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `input` | `string` |
| `init?` | `RequestInit` |

#### Returns

`Promise`\<`Response`\>
## Variables

### Variable: DEFAULT\_BASE\_URL [#variable-default_base_url]

```ts
const DEFAULT_BASE_URL: "https://connect.cosmoops.com" = "https://connect.cosmoops.com";
```

Server-only half of `@cosmoops/connect-sdk`.

Everything here handles the API key, so none of it may run in a browser.
The browser half is `@cosmoops/connect-sdk/client`, which talks to your own
origin and reaches Connect through `createConnectHandler` below.

***

### Variable: SDK\_VERSION [#variable-sdk_version]

```ts
const SDK_VERSION: "0.56.0" = "0.56.0";
```

Kept in step with `package.json` by `sdk.test.ts`.
## Functions

### Function: createConnectHandler() [#function-createconnecthandler]

```ts
function createConnectHandler(options: ConnectHandlerOptions): (request: Request) => Promise<Response>;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | [`ConnectHandlerOptions`](#interface-connecthandleroptions) |

#### Returns

(`request`: `Request`) => `Promise`\<`Response`\>

***

### Function: createConnectTaskHandler() [#function-createconnecttaskhandler]

```ts
function createConnectTaskHandler<TPayload>(options: ConnectTaskHandlerOptions<TPayload>): (request: Request) => Promise<Response>;
```

A ready-made `POST` route for a queue's URL:

```ts
// app/api/tasks/campaign/route.ts
export const POST = createConnectTaskHandler<{ campaignId: string }>({
  handle: async ({ workspaceId, payload }) => runCampaignPage(workspaceId, payload.campaignId),
});
```

`401` for anything that does not verify, `500` when `handle` throws (or
Google's keys cannot be fetched), `200` otherwise.

#### Type Parameters

| Type Parameter | Default type |
| ------ | ------ |
| `TPayload` | `unknown` |

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | [`ConnectTaskHandlerOptions`](#interface-connecttaskhandleroptions)\<`TPayload`\> |

#### Returns

(`request`: `Request`) => `Promise`\<`Response`\>

***

### Function: createConnectWebhookHandler() [#function-createconnectwebhookhandler]

```ts
function createConnectWebhookHandler(options: ConnectWebhookHandlerOptions): (request: Request) => Promise<Response>;
```

A ready-made `POST` route for Connect's webhooks.

```ts
export const POST = createConnectWebhookHandler({
  secret: process.env.CONNECT_WEBHOOK_SECRET!,
  on: {
    "workspace.deletion_scheduled": ({ workspaceId, purgeAfter }) =>
      freeze(workspaceId, purgeAfter),
    "workspace.deletion_cancelled": ({ workspaceId }) => unfreeze(workspaceId),
    "workspace.deleted": ({ workspaceId }) => purge(workspaceId),
  },
})
```

Three behaviours worth knowing, because each of them is a decision rather
than an accident:

**An unverified request gets a 401 and nothing else happens.** No parsing,
no logging of the body, no handler. The endpoint is a public URL, so
anything reachable before the signature check is reachable by anyone.

**An unhandled event type is a 200.** Connect adds event types over time,
and answering 4xx to one you have not written code for would dead-letter
it immediately and, after enough of them, disable your endpoint. Silence
is the correct answer to "we do not care about this one".

**A throwing handler produces a 500, on purpose.** That is what makes
Connect retry. Swallowing the error would acknowledge an event you failed
to process, and the retry ladder is the only thing standing between a
transient failure and a permanently missed deletion.

Delivery is at-least-once and unordered. Dedupe on `envelope.id`, and
reconcile against the readiness endpoint rather than trusting arrival
order - the pull channel is authoritative, this one is fast.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `options` | [`ConnectWebhookHandlerOptions`](#interface-connectwebhookhandleroptions) |

#### Returns

(`request`: `Request`) => `Promise`\<`Response`\>

***

### Function: createState() [#function-createstate]

```ts
function createState(byteLength?: number): string;
```

A URL-safe random string, suitable for `state` or a PKCE verifier.

#### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `byteLength` | `number` | `32` |

#### Returns

`string`

***

### Function: verifyConnectTask() [#function-verifyconnecttask]

```ts
function verifyConnectTask<TPayload>(request: Request, options?: VerifyConnectTaskOptions): Promise<ConnectTaskDelivery<TPayload>>;
```

Verify a delivery and return it, or throw `ConnectTaskVerificationError`.

Reads the body, so call it once and use what it returns.

#### Type Parameters

| Type Parameter | Default type |
| ------ | ------ |
| `TPayload` | `unknown` |

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `request` | `Request` |
| `options` | [`VerifyConnectTaskOptions`](#interface-verifyconnecttaskoptions) |

#### Returns

`Promise`\<[`ConnectTaskDelivery`](/docs/reference/core#interface-connecttaskdelivery)\<`TPayload`\>\>

***

### Function: verifyConnectWebhook() [#function-verifyconnectwebhook]

```ts
function verifyConnectWebhook(
   rawBody: string, 
   signatureHeader: string | null | undefined, 
   secret: string, 
   now?: Date): boolean;
```

Verifies a `Connect-Signature` header against the raw request body.

Exported on its own for anyone wiring this into a framework
`createConnectWebhookHandler` does not cover. The **raw** body matters:
verify before parsing, and never re-serialise - `JSON.stringify(await
req.json())` produces a different string whenever key order or number
formatting differs, and the signature will not match.

Returns a boolean and never explains which check failed. Reporting "bad
timestamp" separately from "bad signature" tells whoever is probing which
half of a forgery to keep working on.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `rawBody` | `string` |
| `signatureHeader` | `string` \| `null` \| `undefined` |
| `secret` | `string` |
| `now` | `Date` |

#### Returns

`boolean`

***

### Function: verifyTelephonyEvent() [#function-verifytelephonyevent]

```ts
function verifyTelephonyEvent(
   rawBody: string, 
   signatureHeader: string | null | undefined, 
   secret: string, 
   now?: Date): TelephonyCarrierEvent;
```

Verifies and parses a carrier event Connect relayed to your telephony
webhook.

`secret` is your application's webhook endpoint secret - the one
`verifyConnectWebhook` takes. Connect signs a relayed event with **every**
active endpoint secret your application holds (one `v1=` each), so this
accepts the header if *any* signature matches the secret you hold.

Pass the **raw** body, read as text before parsing. Throws `ConnectError`
(`status: 401`, `code: "invalid_signature"`) for a signature that does not
verify or a stale timestamp, and `code: "malformed_event"` for a verified
body that is not a carrier event - never saying which half of a forgery
failed.

```ts
export async function POST(request: Request) {
  const event = verifyTelephonyEvent(await request.text(), request.headers.get("connect-signature"), secret);
  if (event.event === "answer") return new Response(answerXml(event), { headers: { "content-type": "application/xml" } });
  return new Response(null, { status: 200 });
}
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `rawBody` | `string` |
| `signatureHeader` | `string` \| `null` \| `undefined` |
| `secret` | `string` |
| `now` | `Date` |

#### Returns

[`TelephonyCarrierEvent`](/docs/reference/core#interface-telephonycarrierevent)