Generated from `@cosmoops/connect-sdk`'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: ConnectError [#class-connecterror]

Every failure the Connect application API can report, as one class.

The API speaks two error dialects and this collapses them: the
`/api/v1/application/*` endpoints use `{ error: { code, message } }`, while the
three `/sso/*` ones use OAuth2's `{ error, error_description }` (RFC 6749
§5.2). Both land here with `code` populated, so integrating code branches on
one field either way.

#### Extends

- `Error`

#### Constructors

##### Constructor

```ts
new ConnectError(init: {
  message: string;
  status: number;
  code: string;
  endpoint: string;
  retryAfterMs?: number | null;
  body?: unknown;
  cause?: unknown;
}): ConnectError;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `init` | \{ `message`: `string`; `status`: `number`; `code`: `string`; `endpoint`: `string`; `retryAfterMs?`: `number` \| `null`; `body?`: `unknown`; `cause?`: `unknown`; \} |
| `init.message` | `string` |
| `init.status` | `number` |
| `init.code` | `string` |
| `init.endpoint` | `string` |
| `init.retryAfterMs?` | `number` \| `null` |
| `init.body?` | `unknown` |
| `init.cause?` | `unknown` |

###### Returns

`ConnectError`

###### Overrides

```ts
Error.constructor
```

#### Properties

##### name

```ts
readonly name: "ConnectError" = "ConnectError";
```

###### Overrides

```ts
Error.name
```

***

##### status

```ts
readonly status: number;
```

HTTP status. `0` when the request never got a response (network failure, timeout).

***

##### code

```ts
readonly code: string;
```

`rate_limited`, `forbidden`, `invalid_token`, ... - branch on this, never on `message`.

***

##### retryAfterMs

```ts
readonly retryAfterMs: number | null;
```

From `Retry-After` on a 429, in milliseconds. `null` otherwise.

***

##### endpoint

```ts
readonly endpoint: string;
```

The endpoint that failed, e.g. `POST /sso/token` - present on every error.

***

##### body

```ts
readonly body: unknown;
```

Parsed response body, when there was one.

***

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

***

##### message

```ts
message: string;
```

###### Inherited from

```ts
Error.message
```

***

##### stack?

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

###### Inherited from

```ts
Error.stack
```

#### Accessors

##### isRetryable

###### Get Signature

```ts
get isRetryable(): boolean;
```

Whether trying the exact same request again could plausibly succeed -
throttling, a transient server fault, or no response at all. The SDK
already retries these internally (see `ConnectClientOptions.maxRetries`);
this is for callers deciding whether to queue their own retry after the
SDK has given up.

**A 503 is retryable only when it carries `Retry-After`.** Connect sends
that header on every 503 it means to be retried - a dependency blip, a
contended transaction - and leaves it off when the dependency is
*misconfigured* (a revoked provider key, say), where every retry gets the
identical answer. Retrying those anyway cost a page three round trips and
two sleeps per read to learn nothing.

###### Returns

`boolean`

***

##### isSessionExpired

###### Get Signature

```ts
get isSessionExpired(): boolean;
```

Whether the user's Connect sign-in is over and they need to go back
through `identity.authorizeUrl()`. Collapses every session-side failure,
matching how the API reports them - unknown, expired, revoked, or issued
to a different application all arrive as one `invalid_token`.

###### Returns

`boolean`

***

##### isKeyRejected

###### Get Signature

```ts
get isKeyRejected(): boolean;
```

Whether *your own API key* is dead - revoked or past its expiry - rather
than the request being wrong.

Connect answers `403` for every credential failure and keeps the code
uniform (`forbidden`) whenever telling them apart would help someone
guessing keys. These two are the exception: reaching them required
presenting a key whose hash matched, so you already hold it, and what you
need to know is that no retry and no scope change will fix this - the key
has to be reissued from the application's Connect client section.

Worth alerting on. It is the one failure that will not resolve itself and
takes down every call your integration makes.

###### Returns

`boolean`

#### 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: AddonUsage [#interface-addonusage]

One consumable add-on's remaining balance on a workspace's subscription.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `addonId` | `string` | - |
| `name` | `string` | - |
| `unit` | `string` | What one unit is called - "messages", "credits", "minutes". |
| `includedPerCycle` | `number` | Units granted at the start of each billing cycle. |
| `balance` | `number` | Units left to spend. Can exceed `includedPerCycle` when `carryForward`, and goes negative only when `overagePolicy` is `"bill"` - that is accrued overage, floored at `-maxOverageUnits`. |
| `carryForward` | `boolean` | - |
| `overagePolicy` | `"reject"` \| `"bill"` | - |
| `overagePricePerUnit` | `number` \| `null` | GST-exclusive paise per unit past the balance. `null` under `"reject"`. |
| `maxOverageUnits` | `number` \| `null` | - |

***

### Interface: AddonUsageResponse [#interface-addonusageresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `applicationId` | `string` | - |
| `addons` | [`AddonUsage`](#interface-addonusage)[] | Empty when the workspace has no subscription, the plan has no consumable add-ons, or none are selected. All three mean "nothing to meter" and none is an error. Note there is no "used this cycle" figure: with `carryForward` a balance survives the cycle boundary, so `includedPerCycle - balance` can go negative and would render as a meter running backwards. Show `balance` against `includedPerCycle`. |
| `resetsAt` | `string` \| `null` | ISO 8601. End of the current billing cycle, when balances are re-granted. |

***

### Interface: AiActionsInput [#interface-aiactionsinput]

#### Properties

| Property | Type |
| ------ | ------ |
| `title?` | `string` |
| `actions` | \{ `label`: `string`; `href`: `string`; `detail?`: `string`; \}[] |

***

### Interface: AiChartInput [#interface-aichartinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `title?` | `string` | - |
| `kind` | `"bar"` \| `"line"` \| `"area"` \| `"pie"` | - |
| `xLabel?` | `string` | - |
| `yLabel?` | `string` | - |
| `valueFormat` | [`AiValueFormat`](#type-alias-aivalueformat) | - |
| `series` | \{ `name`: `string`; `points`: \{ `x`: `string`; `y`: `number` \| `null`; \}[]; \}[] | Every series shares the same x values, in the same order. |
| `caption?` | `string` | - |

***

### Interface: AiConversation [#interface-aiconversation]

One row of Ask AI history.

A conversation exists only if the turns that made it carried a
`conversationId` *and* were sent on a user's behalf - see `AiResource.chat`.
It belongs to one person in one of your workspaces, and never leaves your
application: a workspace subscribing to two products keeps two lists.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `title` | `string` | Derived from the first thing the user said - no model call, so it costs nothing. |
| `createdAt` | `string` | ISO-8601. |
| `updatedAt` | `string` | ISO-8601, and the sort key - a conversation returned to a week later is at the top, not where it was created. |

***

### Interface: AiConversationDetail [#interface-aiconversationdetail]

#### Properties

| Property | Type |
| ------ | ------ |
| `conversation` | [`AiConversation`](#interface-aiconversation) |
| `turns` | [`AiConversationTurn`](#interface-aiconversationturn)[] |

***

### Interface: AiConversationList [#interface-aiconversationlist]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `conversations` | [`AiConversation`](#interface-aiconversation)[] | - |
| `nextCursor` | `string` \| `null` | Pass back as `cursor` for the next page; `null` at the end. |

***

### Interface: AiConversationTurn [#interface-aiconversationturn]

One stored turn, in the AI SDK's own shape.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `role` | `"assistant"` \| `"user"` | - |
| `parts` | `unknown`[] | The message's parts exactly as the model produced them - prose, reasoning and tool calls, in the order they arrived. Opaque on purpose. The shape is the AI SDK's and moves with it, so a type restating it here would start rejecting transcripts the moment the SDK adds a part kind. Flattening it to text is the other mistake: the order is the model's own argument, and a flattened turn cannot be resumed. |
| `sequence` | `number` | Position in the conversation. Turns arrive in this order. |
| `createdAt` | `string` | ISO-8601. |

***

### Interface: AiDetailsInput [#interface-aidetailsinput]

#### Properties

| Property | Type |
| ------ | ------ |
| `title?` | `string` |
| `fields` | \{ `label`: `string`; `value`: [`AiValue`](#type-alias-aivalue); `format`: [`AiValueFormat`](#type-alias-aivalueformat); `tone`: [`AiTone`](#type-alias-aitone); \}[] |

***

### Interface: AiGenerateInput [#interface-aigenerateinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | Whose AI budget this spends. Your application must be installed there. |
| `model?` | `string` | One of the models Connect allows. Omitted means Connect's default. |
| `system?` | `string` \| `null` | - |
| `messages` | [`AiGenerateMessage`](#type-alias-aigeneratemessage)[] | - |
| `tools?` | [`AiGenerateTool`](#interface-aigeneratetool)[] | - |
| `toolChoice?` | \| `"none"` \| \{ `tool`: `string`; \} \| `"auto"` \| `"required"` | - |
| `responseSchema?` | `Record`\<`string`, `unknown`\> \| `null` | A JSON Schema. When given, the parsed answer is `output`. |
| `maxOutputTokens?` | `number` | Defaults to 2048; at most 8192. |
| `temperature?` | `number` \| `null` | - |
| `thinkingLevel?` | `"medium"` \| `"low"` \| `"high"` \| `null` | - |

***

### Interface: AiGenerateResult [#interface-aigenerateresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `model` | `string` | - |
| `text` | `string` | - |
| `output` | `unknown` | The parsed answer when `responseSchema` was given and the model produced one; `null` otherwise. |
| `toolCalls` | [`AiGenerateToolCall`](#interface-aigeneratetoolcall)[] | Tools the model asked for. Run them and send the results back on your next call. |
| `finishReason` | `string` | `stop`, `length`, `tool-calls`, `content-filter`, ... |
| `usage` | \{ `inputTokens`: `number`; `outputTokens`: `number`; \} | What this call cost, as counted against the workspace's monthly budget. |
| `usage.inputTokens` | `number` | - |
| `usage.outputTokens` | `number` | - |

***

### Interface: AiGenerateTool [#interface-aigeneratetool]

A tool the model may ask you to run - see `ai.generate`. Never executed by Connect.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | Letters, digits, `_` and `-`, up to 64. |
| `description` | `string` | - |
| `inputSchema` | `Record`\<`string`, `unknown`\> | A JSON Schema object for the tool's input. |

***

### Interface: AiGenerateToolCall [#interface-aigeneratetoolcall]

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `name` | `string` |
| `input` | `unknown` |

***

### Interface: AiMetricsInput [#interface-aimetricsinput]

#### Properties

| Property | Type |
| ------ | ------ |
| `title?` | `string` |
| `metrics` | \{ `label`: `string`; `value`: [`AiValue`](#type-alias-aivalue); `format`: [`AiValueFormat`](#type-alias-aivalueformat); `delta?`: `number` \| `null`; `deltaMeaning`: `"neutral"` \| `"up-is-good"` \| `"down-is-good"`; `hint?`: `string`; \}[] |

***

### Interface: AiSourcesInput [#interface-aisourcesinput]

#### Properties

| Property | Type |
| ------ | ------ |
| `sources` | \{ `title`: `string`; `uri?`: `string`; `snippet?`: `string`; \}[] |

***

### Interface: AiStepsInput [#interface-aistepsinput]

#### Properties

| Property | Type |
| ------ | ------ |
| `title?` | `string` |
| `steps` | \{ `title`: `string`; `detail?`: `string`; \}[] |

***

### Interface: AiTableInput [#interface-aitableinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `title?` | `string` | - |
| `columns` | \{ `label`: `string`; `format`: [`AiValueFormat`](#type-alias-aivalueformat); \}[] | - |
| `rows` | [`AiValue`](#type-alias-aivalue)[][] | Positional: one array per row, values in the same order as `columns`. |
| `caption?` | `string` | - |

***

### Interface: AiTimelineInput [#interface-aitimelineinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `title?` | `string` | - |
| `events` | \{ `at`: `string`; `title`: `string`; `detail?`: `string`; `tone`: [`AiTone`](#type-alias-aitone); \}[] | Oldest first. |

***

### Interface: AnalyticsPoint [#interface-analyticspoint]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `day` | `string` | `YYYY-MM-DD`, UTC. |
| `value` | `number` \| `null` | **`null` is not zero.** It means the metric was not measurable that day - `usage.active_users` is a distinct count that only exists after Connect's nightly rollup, so today's is always `null`, as is any future day. Render it as a gap or a dash, never as a zero. |

***

### Interface: AnalyticsQuery [#interface-analyticsquery]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `metrics` | [`ConnectMetric`](#type-alias-connectmetric)[] | - |
| `from` | `string` | Inclusive `YYYY-MM-DD`, UTC. The range must be shorter than 90 days. |
| `to` | `string` | - |

***

### Interface: AnalyticsResponse [#interface-analyticsresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `from` | `string` |
| `to` | `string` |
| `series` | [`AnalyticsSeries`](#interface-analyticsseries)[] |

***

### Interface: AnalyticsSeries [#interface-analyticsseries]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `metric` | [`ConnectMetric`](#type-alias-connectmetric) | - |
| `points` | [`AnalyticsPoint`](#interface-analyticspoint)[] | - |
| `total` | `number` \| `null` | Sum across measurable days only, or `null` if no day in the range was measurable. |
| `partial` | `boolean` | True when at least one day was not measurable - caveat the total when this is set. |

***

### Interface: ApplicationPermission [#interface-applicationpermission]

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `label` | `string` |
| `description` | `string` |

***

### Interface: ApplicationRoleDefinition [#interface-applicationroledefinition]

#### Properties

| Property | Type |
| ------ | ------ |
| `key` | `string` |
| `name` | `string` |
| `description` | `string` |
| `permissions` | `string`[] |

***

### Interface: ApplicationSubscriber [#interface-applicationsubscriber]

One row of `subscribers.list()` - a workspace subscribed to your application.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `subscriptionId` | `string` | - |
| `workspaceId` | `string` | - |
| `workspaceName` | `string` \| `null` | `null` when the workspace has been purged and only its tombstone remains. |
| `workspaceStatus` | `string` \| `null` | `"active"`, `"suspended"`, `"pending_deletion"`, `"deleted"` - or `null` alongside a null `workspaceName`. |
| `planId` | `string` | - |
| `planName` | `string` \| `null` | `null` if the plan was hard-deleted rather than archived. |
| `status` | `"created"` \| `"authenticated"` \| `"active"` \| `"paused"` \| `"cancelled"` | - |
| `seatCount` | `number` | The last-billed seat snapshot, not the live count `SubscriptionDetail` carries: a page of subscribers cannot afford one member scan per row. It is `0` unless the plan is `"per-seat"`, which is the only model that bills by seat. Read the single workspace's subscription for "who is assigned right now". |
| `subscribedAt` | `string` | ISO 8601. |
| `nextBillingDate` | `string` \| `null` | ISO 8601, or `null` for a cancelled/paused subscription. |

***

### Interface: AppliedDiscount [#interface-applieddiscount]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `source` | `"coupon"` \| `"admin"` | - |
| `code` | `string` \| `null` | Display code - `null` for an admin-applied discount with no coupon behind it. |
| `discountType` | `"fixed"` \| `"percentage"` | - |
| `value` | `number` | 1-90 for `"percentage"`; GST-exclusive paise for `"fixed"`. |
| `duration` | `"once"` \| `"repeating"` \| `"forever"` | - |
| `cyclesRemaining` | `number` \| `null` | Decremented each captured cycle. `null` only for `"forever"`. |

***

### Interface: ArchivedRecording [#interface-archivedrecording]

A recording moved into the workspace's Drive, as a file your application uploaded.

#### Properties

| Property | Type |
| ------ | ------ |
| `fileId` | `string` |
| `sizeBytes` | `number` |
| `contentType` | `string` |

***

### Interface: ArchiveRecordingInput [#interface-archiverecordinginput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `callId` | `string` | Your own call id - kept with the archive and in the file name. |
| `recordingUrl` | `string` | The carrier's URL for the recording, from the `recording` event. |

***

### Interface: AssessmentDraft [#interface-assessmentdraft]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `assessmentKey` | `string` | - |
| `title` | `string` | - |
| `sections` | [`AssessmentSection`](#interface-assessmentsection)[] | The definition's sections, in the order they should be asked. |
| `answers` | [`AssessmentAnswers`](#type-alias-assessmentanswers) | Suggested answers - a strawman to edit, never a finished answer. |

***

### Interface: AssessmentFinding [#interface-assessmentfinding]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `severity` | [`AssessmentFindingSeverity`](#type-alias-assessmentfindingseverity) | - |
| `sections` | `string`[] | Section keys this finding concerns - usually two, since the useful findings are tensions between sections. |
| `title` | `string` | - |
| `detail` | `string` | - |
| `fix` | `string` | The specific change to make. |

***

### Interface: AssessmentReview [#interface-assessmentreview]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `assessmentKey` | `string` | - |
| `findings` | [`AssessmentFinding`](#interface-assessmentfinding)[] | - |
| `riskiestAssumption` | \{ `section`: `string`; `assumption`: `string`; `experiment`: `string`; \} | The one assumption everything rests on, and a cheap way to test it. Lead with this. |
| `riskiestAssumption.section` | `string` | - |
| `riskiestAssumption.assumption` | `string` | - |
| `riskiestAssumption.experiment` | `string` | - |
| `score` | `number` | 0-100, advisory and coarse - it shows movement between revisions. |

***

### Interface: AssessmentSection [#interface-assessmentsection]

One question in an assessment, as the platform defines it. Echoed back on a
draft so the questions can be rendered without a second round trip.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | Stable identifier - the key answers are filed under. |
| `question` | `string` | The question as a person reads it. |
| `hint?` | `string` | What belongs here. Written to steer generation, but useful as helper text. |
| `min?` | `number` | Fewest answers a draft offers. |
| `max?` | `number` | Most answers a draft offers, and the cap worth enforcing on edits. |

***

### Interface: AvailabilitySchedule [#interface-availabilityschedule]

A working-hours template an appointment type points at.

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `name` | `string` |
| `timezone` | `string` |
| `weeklyHours` | [`WeeklyHours`](#interface-weeklyhours) |

***

### Interface: BookableEventType [#interface-bookableeventtype]

One bookable thing a workspace offers - "15-minute intro call", "site
survey".

`locationKind` and never the location itself: the address or the meeting
link belongs to whoever booked, and this is a catalogue.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `slug` | `string` | Stable and readable, unique within the workspace - branch on this, not `id`. |
| `name` | `string` | - |
| `description` | `string` \| `null` | - |
| `durationMinutes` | `number` | - |
| `locationKind` | `"phone"` \| `"address"` \| `"link"` | - |

***

### Interface: Booking [#interface-booking]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `eventTypeId` | `string` | - |
| `hostMemberIds` | `string`[] | The workspace members committed to this appointment. These are `ConnectWorkspaceMember.memberId` values - what `workspaces.members()` returns as `memberId` and what `listBookings`' `hostMemberId` filter takes, so a host column is a lookup against a roster you already have rather than another round trip. **Not `userId`.** An earlier version of this comment said these were the ids `workspaces.listMembers` returns, which was wrong twice over: that method is `workspaces.members()`, and until `memberId` was added to its rows it returned no id these could be matched against at all. Resolved when the slot was held and frozen there: a round-robin event type picks one host, a collective one commits the whole group, and re-pointing the event type afterwards does not move an appointment already made. |
| `status` | [`BookingStatus`](#type-alias-bookingstatus) | - |
| `startAt` | `string` | ISO-8601 instants. |
| `endAt` | `string` | - |
| `timezone` | `string` | The zone the two parties agreed the time in, frozen at booking. |
| `attendee` | \{ `name`: `string`; `phone`: `string`; `email`: `string` \| `null`; \} | - |
| `attendee.name` | `string` | - |
| `attendee.phone` | `string` | - |
| `attendee.email` | `string` \| `null` | - |
| `notes` | `string` \| `null` | - |
| `source` | [`BookingSource`](#type-alias-bookingsource) | - |
| `sourceCallId` | `string` \| `null` | The call or conversation this was booked during - the `sourceCallId` its maker passed, verbatim - or `null`. Your own call id, when you booked it. |
| `holdExpiresAt` | `string` \| `null` | ISO-8601. When an unconfirmed hold lapses and frees the slot - set only while `status` is `hold`. See `scheduling.holdSlot`. |
| `cancelledReason` | `string` \| `null` | - |
| `rescheduledToId` | `string` \| `null` | Set when `status` is `rescheduled` - the booking that replaced this one. Rescheduling retires a booking rather than moving its `startAt`, so an id you cached still resolves and still tells you where the appointment went. |
| `createdAt` | `string` | - |

***

### Interface: BookingAttendee [#interface-bookingattendee]

#### Properties

| Property | Type |
| ------ | ------ |
| `name` | `string` |
| `phone` | `string` |
| `email?` | `string` \| `null` |

***

### Interface: BookingCancelledEvent [#interface-bookingcancelledevent]

An appointment was cancelled. The slot is free and any prep against it is void.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `bookingId` | `string` | - |
| `eventTypeId` | `string` | - |
| `eventTypeName` | `string` | - |
| `startAt` | `string` | ISO-8601. The time it *was* going to happen. |
| `attendeeName` | `string` | - |
| `contactId` | `string` \| `null` | - |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: BookingConfirmedEvent [#interface-bookingconfirmedevent]

An appointment was booked and is holding its slot.

Raised however it was booked - the customer's portal, the public booking
page, a voice agent mid-call, or your own API call - so you get one shape
for all four and never have to reconcile "did we create this, or did they?".
Dedupe on `bookingId`, not on the envelope alone.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `bookingId` | `string` | - |
| `eventTypeId` | `string` | - |
| `eventTypeName` | `string` | - |
| `startAt` | `string` | ISO-8601, UTC. Render it in `timezone`, which is the workspace's, not the viewer's. |
| `endAt` | `string` | - |
| `timezone` | `string` | IANA zone the appointment was booked in. |
| `attendeeName` | `string` | - |
| `attendeeEmail` | `string` \| `null` | - |
| `attendeePhone` | `string` \| `null` | E.164. |
| `contactId` | `string` \| `null` | - |
| `hostMemberIds` | `string`[] | Connect user ids of the hosts. |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: BookingRescheduledEvent [#interface-bookingrescheduledevent]

An appointment moved.

Connect retires the old booking and writes a new one rather than mutating
it, so this carries **both ids**: `bookingId` is the row that now exists and
`previousBookingId` is the one you are already holding. Reconcile on the
previous id or you will end up with two appointments.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `bookingId` | `string` | - |
| `eventTypeId` | `string` | - |
| `eventTypeName` | `string` | - |
| `startAt` | `string` | ISO-8601, the new time. |
| `endAt` | `string` | - |
| `previousBookingId` | `string` | - |
| `previousStartAt` | `string` | ISO-8601, the time it was before. |
| `timezone` | `string` | - |
| `attendeeName` | `string` | - |
| `contactId` | `string` \| `null` | - |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: BookingSlot [#interface-bookingslot]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `startAt` | `string` | ISO-8601 instant. Pass this back to `createBooking` unchanged. |
| `endAt` | `string` | - |

***

### Interface: BookingSlots [#interface-bookingslots]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `timezone` | `string` | The **business's** IANA timezone, not yours and not your user's - what to render a slot's wall time in. A slot shown in the viewer's zone is an hour the business may not be open. |
| `durationMinutes` | `number` | - |
| `slots` | [`BookingSlot`](#interface-bookingslot)[] | - |

***

### Interface: BrandCitation [#interface-brandcitation]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `source` | [`BrandCitationSource`](#type-alias-brandcitationsource) | - |
| `url` | `string` | - |
| `retrievedAt` | `string` | ISO-8601. |

***

### Interface: BrandKit [#interface-brandkit]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `logos` | [`BrandLogo`](#interface-brandlogo)[] | - |
| `palette` | [`PaletteEntry`](#interface-paletteentry)[] | - |
| `typography` | [`TypefaceEntry`](#interface-typefaceentry)[] | - |
| `voice` | \{ `tagline`: `string` \| `null`; `tone`: `string`[]; \} | - |
| `voice.tagline` | `string` \| `null` | - |
| `voice.tone` | `string`[] | A few adjectives for the register the copy is written in. |

***

### Interface: BrandLogo [#interface-brandlogo]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `url` | `string` \| `null` | A **short-lived signed URL**, minted fresh on every read. Do not store it and do not bake it into a cached page - it will expire and become a broken image with a plausible-looking address. Render it directly, or re-read the profile, which on a cache hit is one document read. `null` when Connect holds no cached copy, in which case `sourceUrl` is all there is. |
| `sourceUrl` | `string` | Where it lives on the company's own site. Stable, and the citation. |
| `kind` | [`LogoKind`](#type-alias-logokind) | - |
| `format` | `string` | Lowercase extension without the dot: `svg`, `png`, `webp`. |
| `width` | `number` \| `null` | - |
| `height` | `number` \| `null` | - |

***

### Interface: BrandProfile [#interface-brandprofile]

What a company looks like, measured off its own website.

The important property: **the numbers and the colours here were parsed, not
generated.** Every hex came out of a stylesheet and every scale step is a
count of real declarations. A model wrote only the prose (`about`,
`industry`, `businessModel`, `offerings`, `voice`) and assigned `role` to
values it was shown. Ask any model directly for a brand colour and it
answers `#3B82F6`; that is the failure this shape exists to avoid.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `domain` | `string` | Normalised - lowercase, no scheme, no `www.`. |
| `knownAs` | `string` \| `null` | The trading name, as the company writes it. |
| `officialName` | `string` \| `null` | - |
| `officialNameSource` | [`OfficialNameSource`](#type-alias-officialnamesource) \| `null` | - |
| `about` | `string` \| `null` | - |
| `industry` | `string` \| `null` | - |
| `businessModel` | [`BusinessModel`](#type-alias-businessmodel) \| `null` | - |
| `offerings` | [`Offering`](#interface-offering)[] | - |
| `brandKit` | [`BrandKit`](#interface-brandkit) | - |
| `designSystem` | [`DesignSystem`](#interface-designsystem) | - |
| `confidence` | `"medium"` \| `"low"` \| `"high"` | Derived from how many aspects the sources actually filled - not a model's self-report. |
| `citations` | [`BrandCitation`](#interface-brandcitation)[] | - |
| `retrievedAt` | `string` | ISO-8601. Age the profile against this. |

***

### Interface: BrowserCallingToken [#interface-browsercallingtoken]

A carrier access token a member's browser signs in to the phone network with. Valid for an hour.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `token` | `string` | - |
| `expiresAt` | `string` | ISO-8601. |

***

### Interface: BrowserTokenInput [#interface-browsertokeninput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `subject` | `string` | 1-48 letters and digits. Connect prefixes it with a 19-character tag naming your application and the workspace. |
| `ttlSeconds` | `number` | 60-3600. |

***

### Interface: BrowserTokenResult [#interface-browsertokenresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `token` | `string` | - |
| `expiresAt` | `string` | ISO-8601. |

***

### Interface: CallActionItem [#interface-callactionitem]

Something somebody committed to on a recorded call. `owner` is who said
they would do it - `rep` for your user's side, `customer` for the person
called. Both are worth a follow-up: one to do, one to chase.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `title` | `string` | - |
| `dueDate` | `string` \| `null` | `YYYY-MM-DD` when a day was named or clearly implied, else `null`. |
| `owner` | `"rep"` \| `"customer"` | - |

***

### Interface: CallAnalysis [#interface-callanalysis]

Connect's reading of a call recording. `rep` is the business side.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `turns` | \{ `speaker`: `"rep"` \| `"customer"`; `text`: `string`; \}[] | - |
| `summary` | `string` | - |
| `intent` | `string` | - |
| `sentiment` | `"positive"` \| `"neutral"` \| `"negative"` | - |
| `actionItems` | \{ `title`: `string`; `dueDate`: `string` \| `null`; `owner`: `"rep"` \| `"customer"`; \}[] | - |
| `language` | `string` \| `null` | BCP-47, e.g. `hi-IN`. |

***

### Interface: CallAnalysisInput [#interface-callanalysisinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `fileId` | `string` | A Drive file your application uploaded in that workspace - e.g. `ArchivedRecording.fileId`. |
| `direction` | `"inbound"` \| `"outbound"` | - |
| `repName` | `string` \| `null` | The business-side speaker's name, when known. |
| `callDate` | `string` | `YYYY-MM-DD` - what relative due dates are worked out from. |

***

### Interface: CallAnalyzedEvent [#interface-callanalyzedevent]

A recorded call, transcribed and read. Sent minutes after `call.completed`,
and only for calls placed while the workspace records them - file the call
on `call.completed` and enrich that row here. The transcript itself is the
call's turns on `calls.get`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `callId` | `string` | - |
| `channelId` | `string` \| `null` | - |
| `contactPhone` | `string` \| `null` | E.164 - the person called on an outbound call, the caller on an inbound one. |
| `direction` | [`ConnectCallDirection`](#type-alias-connectcalldirection) | - |
| `handler` | [`CallHandler`](#type-alias-callhandler) | - |
| `placedByUserId` | `string` \| `null` | - |
| `placedByName` | `string` \| `null` | - |
| `summary` | `string` \| `null` | Two or three sentences, in English whatever language was spoken. |
| `intent` | `string` \| `null` | Why they were on the call, in a few lowercase words - "pricing enquiry". |
| `sentiment` | [`CallSentiment`](#type-alias-callsentiment) \| `null` | - |
| `language` | `string` \| `null` | The main language spoken, BCP-47 - `hi-IN`, `en-IN`. |
| `actionItems` | [`CallActionItem`](#interface-callactionitem)[] | - |
| `recordingFileId` | `string` \| `null` | The audio in the workspace's Drive - read it with a `drive:read` key. |
| `occurredAt` | `string` | ISO-8601 - when the call started. |

***

### Interface: CallCompletedEvent [#interface-callcompletedevent]

A call ended.

The event an external system of record is built on: it is what turns a
conversation that happened in Connect's telephony into a row in your own
timeline with nobody retyping it. Fires for every completed call, whether an
agent answered it or a person did.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `callId` | `string` | - |
| `channelId` | `string` \| `null` | - |
| `contactPhone` | `string` \| `null` | E.164, and the only identity a call event carries. There is deliberately no `contactId`: Connect resolves a caller to a contact by number, and at the moment a call ends it may not have done so at all - the field would be `null` on most deliveries, which is a field that silently does nothing. Match on this instead. It is the same canonical form `contacts.lookupByPhone` takes, so resolving it is one call if you want the contact. |
| `direction` | [`ConnectCallDirection`](#type-alias-connectcalldirection) | - |
| `outcome` | `string` | How it ended, as Connect's telephony reported it - `"completed"`, `"transferred"`, … |
| `durationSeconds` | `number` | - |
| `extraction` | `Record`\<`string`, `unknown`\> | What the agent's skills captured during the conversation, keyed by the capture fields the agent declares. Shapeless on purpose - the keys and the value types are whatever that agent's `extractionSchema` declares, which Connect does not constrain. `{}` on most calls, since an unanswered or human-answered call extracts nothing, so treat every key as optional rather than checking the object for presence. |
| `handler?` | [`CallHandler`](#type-alias-callhandler) | `member` when a person placed this call from a browser - `<ConnectCallButton>` or Connect's own portal - rather than an agent handling it. Absent on events sent before member calling existed; treat absent as `agent`. |
| `placedByUserId?` | `string` \| `null` | The member who placed a `member` call - their Connect user id, the same id a workspace roster carries. The one to attribute the call to. `null` on an agent's call. |
| `placedByName?` | `string` \| `null` | That member's display name, else their email - so the call can be attributed without a roster read. |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: CallDetail [#interface-calldetail]

A call and its transcript. Long calls are capped at 2,000 turns.

#### Properties

| Property | Type |
| ------ | ------ |
| `call` | [`ConnectCall`](#interface-connectcall) |
| `turns` | [`CallTranscriptTurn`](#interface-calltranscriptturn)[] |

***

### Interface: CallingAvailability [#interface-callingavailability]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `status` | [`CallingAvailabilityStatus`](#type-alias-callingavailabilitystatus) | - |
| `number` | `string` \| `null` | The workspace's business number, E.164, when it holds one. |
| `available` | `number` | Prepaid balance a new call may spend, in paise, pre-tax. |
| `availableMinutes` | `number` | About how many minutes that buys. |
| `ratePaisePerMinute` | `number` | - |
| `lowBalance` | `boolean` | Under about 55 minutes - worth a warning. |
| `canTopUp` | `boolean` | Whether this member may buy minutes. |
| `canManageNumber` | `boolean` | Whether this member may add the number add-on or retry a pending number. |
| `offer` | \| \{ `subscriptionId`: `string`; `applicationId`: `string`; `addonId`: `string`; `addonName`: `string`; `price`: `number`; \} \| `null` | Where a business number can be bought, when the workspace has none. |

***

### Interface: CallingReadiness [#interface-callingreadiness]

Where the workspace stands on the steps Indian telecom rules ask of a
business before it calls - `connect.calls.getCallingReadiness()`. Read
`runs`, not `steps`, to decide what to offer: Connect evaluates them with
the gates the dialer refuses on.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `intent` | `"service_only"` \| `"service_and_sales"` \| `null` | - |
| `progress` | \{ `done`: `number`; `required`: `number`; \} | - |
| `progress.done` | `number` | - |
| `progress.required` | `number` | - |
| `steps` | [`CallingReadinessStep`](#interface-callingreadinessstep)[] | - |
| `runs` | [`CallingReadinessRun`](#interface-callingreadinessrun)[] | - |
| `setupUrl` | `string` | Connect's journey screen, carrying your `client_id` when SSO is on. |
| `canManage` | `boolean` | Whether this member may take the steps - their `channel:manage`. |

***

### Interface: CallingReadinessRun [#interface-callingreadinessrun]

One kind of calling, and whether the workspace may run it today.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `purpose` | `"service"` \| `"promotional"` | `service` covers existing customers; `promotional` is any sales call. |
| `mode` | `"preview"` \| `"automatic"` | `preview`: a rep presses dial. `automatic`: software paces the dialing. |
| `allowed` | `boolean` | - |
| `reason` | `string` \| `null` | The first thing in the way, worded for the member. `null` when allowed. |

***

### Interface: CallingReadinessStep [#interface-callingreadinessstep]

A step of Connect's calling compliance journey - `connect.calls.getCallingReadiness()`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | \| `"number"` \| `"business"` \| `"intent"` \| `"header"` \| `"autodialer"` \| `"consent"` \| `"rules"` | - |
| `label` | `string` | - |
| `status` | `"done"` \| `"not_started"` \| `"submitted"` \| `"returned"` | - |
| `required` | `boolean` | Whether the business needs it for the calls it said it makes. |
| `note` | `string` \| `null` | A returned step's reason, or what the numbers say. |

***

### Interface: CallMissedEvent [#interface-callmissedevent]

A call reached a channel and nothing answered it.

Also raised for a call a member placed that the person rang did not pick
up (`handler: "member"`, `direction: "outbound"`) - somebody to try again.

Its own event rather than an `outcome` on the one above, because the action
differs in kind: a completed call is something to file, a missed one is
somebody to ring back. Treating this as a lead is the right thing to do
with it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `callId` | `string` | - |
| `channelId` | `string` \| `null` | - |
| `contactPhone` | `string` \| `null` | E.164, and the only identity this carries - see `CallCompletedEvent.contactPhone`. |
| `direction` | [`ConnectCallDirection`](#type-alias-connectcalldirection) | - |
| `handler?` | [`CallHandler`](#type-alias-callhandler) | `member` when a person placed this call from a browser - `<ConnectCallButton>` or Connect's own portal - rather than an agent handling it. Absent on events sent before member calling existed; treat absent as `agent`. |
| `placedByUserId?` | `string` \| `null` | The member who placed a `member` call - their Connect user id, the same id a workspace roster carries. The one to attribute the call to. `null` on an agent's call. |
| `placedByName?` | `string` \| `null` | That member's display name, else their email - so the call can be attributed without a roster read. |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: CallSettings [#interface-callsettings]

The workspace's calling settings, as the signed-in member sees them -
`connect.calls.getCallSettings()`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `recordingEnabled` | `boolean` | Whether member calls are recorded, transcribed and read (`call.analyzed`). |
| `recordingRatePaisePerMinute` | `number` | Paise, pre-tax, recording adds to every billed minute - show it beside the switch. |
| `canManage` | `boolean` | Whether this member may change it - their `channel:manage`. Draw the switch disabled when not. |

***

### Interface: CallsPage [#interface-callspage]

One page of calls, newest first. Page until `hasMore` is `false` - do not stop on an empty `items`.

#### Properties

| Property | Type |
| ------ | ------ |
| `items` | [`ConnectCall`](#interface-connectcall)[] |
| `nextCursor` | `string` \| `null` |
| `hasMore` | `boolean` |

***

### Interface: CallTokenUsage [#interface-calltokenusage]

What the model leg of a call cost.

#### Properties

| Property | Type |
| ------ | ------ |
| `promptTokens` | `number` |
| `responseTokens` | `number` |
| `totalTokens` | `number` |

***

### Interface: CallToolInvocation [#interface-calltoolinvocation]

One tool the agent called mid-conversation. Arguments and results are JSON strings, not parsed objects.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | - |
| `argumentsJson` | `string` | - |
| `resultJson` | `string` \| `null` | - |
| `durationMs` | `number` \| `null` | - |
| `startedAt` | `string` \| `null` | `null` for calls closed out before the runtime reported it - which is not zero. |
| `error` | `string` \| `null` | - |

***

### Interface: CallTranscriptTurn [#interface-calltranscriptturn]

One transcript turn. Order by `sequence`, never by `startedAt` - two turns
can share a millisecond.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `sequence` | `number` | - |
| `role` | [`CallTurnRole`](#type-alias-callturnrole) | - |
| `text` | `string` | - |
| `startedAt` | `string` \| `null` | - |
| `interrupted` | `boolean` | True only ever for an `agent` turn - the caller talked over it. |

***

### Interface: ChannelRouting [#interface-channelrouting]

Where inbound conversations on a channel go.

`destination` is **yours**: an arbitrary JSON object Connect stores verbatim
and never reads into. Which agent answers, business hours, overflow - all of
it is your application's model, not Connect's.

#### Properties

| Property | Type |
| ------ | ------ |
| `destination` | `Record`\<`string`, `unknown`\> |
| `updatedAt` | `string` |

***

### Interface: ChannelRoutingRecord [#interface-channelroutingrecord]

What both routing writes echo back - the stored record, not the whole
channel. `routing` is `null` after `clearRouting()`, which is the same shape
a channel that was never routed has: to the answer path "cleared" and "never
set" are the same fact.

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `routing` | [`ChannelRouting`](#interface-channelrouting) \| `null` |

***

### Interface: ChannelsPage [#interface-channelspage]

One page of channels. Page until `hasMore` is `false`.

#### Properties

| Property | Type |
| ------ | ------ |
| `items` | [`ConnectChannel`](#interface-connectchannel)[] |
| `nextCursor` | `string` \| `null` |
| `hasMore` | `boolean` |

***

### Interface: ChatMessage [#interface-chatmessage]

#### Indexable

```ts
[field: string]: unknown
```

#### Properties

| Property | Type |
| ------ | ------ |
| `role` | `"assistant"` \| `"user"` \| `"system"` |

***

### Interface: ClaimedChannel [#interface-claimedchannel]

What a claim answers with - narrower than `ConnectChannel`, because a
just-claimed number has no label and no routing yet. Read it back with
`channels.get(id)` once you have written routing.

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `kind` | [`ChannelKind`](#type-alias-channelkind) |
| `address` | `string` |
| `workspaceId` | `string` |
| `status` | [`ChannelStatus`](#type-alias-channelstatus) |

***

### Interface: CompanyProfile [#interface-companyprofile]

Who operates the platform your application runs on, and how a user
complains to them.

You are a Data Processor on this notice, not a second Data Fiduciary - so
this is the block your privacy page and your consent capture print, rather
than one of your own kept in environment variables. That is the whole point
of reading it: a statutory contact copied into `.env` goes stale silently,
and every application holding a copy publishes a channel that reaches
nobody.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `legalName` | `string` | - |
| `brandName` | `string` | Trading name, for prose the full registered name reads oddly in. Editable by the platform operator since SDK 0.23. It was a hardcoded constant on Connect's side before that - the one company fact no operator could correct without a deploy. |
| `registeredAddress` | `string` | - |
| `contactEmail` | `string` | - |
| `website` | `string` \| `null` | - |
| `gstNumber` | `string` \| `null` | GSTIN, `null` if the operating entity is not GST-registered. |
| `legalJurisdiction` | `string` \| `null` | The seat a governing-law clause names - `"Chennai, Tamil Nadu"`. **Not the registered address**, and the distinction is the reason it is its own field: an entity registered in one state routinely names another as the venue for disputes, and the two copies of this that applications used to keep disagreed about which. `null` when the operator has not published one, in which case a terms page should omit the venue rather than reach for `registeredAddress` instead. |
| `grievance` | [`GrievanceChannel`](#interface-grievancechannel) | - |

***

### Interface: CompanyProfileResponse [#interface-companyprofileresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `companyProfile` | [`CompanyProfile`](#interface-companyprofile) |

***

### Interface: ConnectCall [#interface-connectcall]

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

`durationSeconds` and `billDurationSeconds` are the two durations Connect
stands behind - the carrier's own measure of the audio is deliberately not
published, since a third near-identical number only invites a billing
argument settled off the wrong one.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `channelId` | `string` | - |
| `workspaceId` | `string` | - |
| `direction` | [`CallDirection`](#type-alias-calldirection) | - |
| `fromNumber` | `string` | - |
| `toNumber` | `string` | - |
| `status` | [`CallStatus`](#type-alias-callstatus) | - |
| `rejectReason` | \| `string` & \{ \} \| [`CallRejectReason`](#type-alias-callrejectreason) \| `null` | - |
| `disconnectReason` | \| `string` & \{ \} \| [`CallDisconnectReason`](#type-alias-calldisconnectreason) \| `null` | - |
| `startedAt` | `string` | - |
| `answeredAt` | `string` \| `null` | - |
| `endedAt` | `string` \| `null` | - |
| `durationSeconds` | `number` \| `null` | - |
| `billDurationSeconds` | `number` \| `null` | - |
| `recordingStatus` | [`CallRecordingStatus`](#type-alias-callrecordingstatus) | - |
| `recordingFileId` | `string` \| `null` | A Drive file id - read it with a `drive:read` key. `null` until the carrier posts the audio. |
| `transcriptFileId` | `string` \| `null` | - |
| `turnCount` | `number` \| `null` | - |
| `tokenUsage` | [`CallTokenUsage`](#interface-calltokenusage) \| `null` | - |
| `toolInvocations` | [`CallToolInvocation`](#interface-calltoolinvocation)[] | - |
| `extraction` | `Record`\<`string`, `unknown`\> \| `null` | Your agent's own post-call structured output, keyed by its extraction schema. Connect stores it without interpreting it. |
| `agentId` | `string` \| `null` | - |
| `agentVersion` | `number` \| `null` | - |
| `handler` | [`CallHandler`](#type-alias-callhandler) | `member` for a call a person placed with `<ConnectCallButton>`. Such a call has no agent; it has a recording, a transcript and an `analysis` only when the workspace records calls. |
| `placedByUserId` | `string` \| `null` | The member who placed a `member` call - their Connect user id. |
| `talkSeconds` | `number` \| `null` | How long the person called was on the line, for a `member` call. Read this rather than `durationSeconds`, which there measures the member's own leg, ringing included. `null` until the carrier reports it, and for a call nobody answered. |
| `dialledAt` | `string` \| `null` | When Connect asked the carrier to ring the person called, on a `member` call. Tells apart the two calls `unansweredReason: "cancelled"` covers: set when the member hung up while it rang (the person sees a missed call - Connect sends `call.missed`), `null` when they gave up before anyone was rung (no call happened, and no event is sent). |
| `unansweredReason` | \| `string` & \{ \} \| [`CallUnansweredReason`](#type-alias-callunansweredreason) \| `null` | Why nobody answered - set only when `status` is `unanswered`. |
| `analysis` | [`ConnectCallAnalysis`](#interface-connectcallanalysis) \| `null` | The reading of a recorded member call - summary, intent, action items. `null` for a call that was not recorded; `status: "pending"` while it is being read. The transcript is `CallDetail.turns`, the member as `agent` and the person called as `user`. |

***

### Interface: ConnectCallAnalysis [#interface-connectcallanalysis]

#### Properties

| Property | Type |
| ------ | ------ |
| `status` | `"pending"` \| `"failed"` \| `"done"` |
| `summary` | `string` \| `null` |
| `intent` | `string` \| `null` |
| `sentiment` | [`CallSentiment`](#type-alias-callsentiment) \| `null` |
| `actionItems` | [`CallActionItem`](#interface-callactionitem)[] |
| `language` | `string` \| `null` |

***

### Interface: ConnectChannel [#interface-connectchannel]

A number assigned to your application, as your application sees it.

Two things are deliberately withheld and will not appear later:
`createdBy` names a Connect operator, and the carrier-side record (which
carrier, which provider resource, what Connect pays per month) is Connect's
cost and Connect's credential surface. An application reads what was
assigned to it; it never provisions, prices or releases a number.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `kind` | [`ChannelKind`](#type-alias-channelkind) | - |
| `address` | `string` | E.164 for a voice channel - the number a caller dials. |
| `label` | `string` \| `null` | - |
| `workspaceId` | `string` | - |
| `status` | [`ChannelStatus`](#type-alias-channelstatus) | - |
| `routing` | [`ChannelRouting`](#interface-channelrouting) \| `null` | `null` when nothing has been routed yet, and after a `clearRouting()`. |
| `handlerApplicationId` | `string` \| `null` | The application that answers this number's calls itself through Connect's carrier edge - yours, when your application has a telephony webhook configured at the time it claimed the number. `null` means Connect answers the number. |

***

### Interface: ConnectContact [#interface-connectcontact]

Someone a workspace knows how to reach - **the telephony identity**, not a
CRM record. See `ContactsResource` for why the difference decides whether
you reference this or copy it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `primaryPhone` | `string` | E.164, and immutable - the key this contact is identified by. |
| `phones` | [`ContactChannelValue`](#interface-contactchannelvalue)[] | - |
| `emails` | [`ContactChannelValue`](#interface-contactchannelvalue)[] | - |
| `name` | \{ `display`: `string` \| `null`; `given`: `string` \| `null`; `family`: `string` \| `null`; \} | - |
| `name.display` | `string` \| `null` | - |
| `name.given` | `string` \| `null` | - |
| `name.family` | `string` \| `null` | - |
| `business` | \{ `company`: `string` \| `null`; `role`: `string` \| `null`; `gstin`: `string` \| `null`; `website`: `string` \| `null`; `address`: `string` \| `null`; \} | - |
| `business.company` | `string` \| `null` | - |
| `business.role` | `string` \| `null` | - |
| `business.gstin` | `string` \| `null` | - |
| `business.website` | `string` \| `null` | - |
| `business.address` | `string` \| `null` | - |
| `tags` | `string`[] | - |
| `source` | [`ContactSource`](#type-alias-contactsource) | - |
| `createdAt` | `string` | - |
| `updatedAt` | `string` | - |

***

### Interface: ConnectDataClassNotice [#interface-connectdataclassnotice]

One kind of data your application holds, and what your policy said happens to it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | Stable key, as declared in Console. Safe to switch on. |
| `label` | `string` | What the customer was shown - e.g. "Call records". |
| `disposition` | [`ConnectDataDisposition`](#type-alias-connectdatadisposition) | - |
| `retainDays` | `number` \| `null` | How many days this class outlives the workspace **in your own archive**, or `null` when it goes with the purge. The one number in either deletion event you have to act on, and the reason `policy` is on the wire at all: `purgeAfter` is Connect's clock, this is yours. Read it rather than hardcoding the figure you typed into Console - that copy drifts the first time somebody edits the policy, and this one is the version the customer was actually promised. It is Connect's clamped value, so it is never longer than the platform ceiling even if your stored policy says otherwise. |
| `basis` | `string` \| `null` | The clause or rule you named as requiring it, shown to the customer verbatim. `null` for a class that is erased. |

***

### Interface: ConnectDeletionPolicyNotice [#interface-connectdeletionpolicynotice]

Your application's own data policy, as it stood when this deletion was
confirmed - the declaration the customer read before pressing the button.

Carried on both deletion events so your handler can be driven by the
declaration rather than by a second copy of it in your code. Two things
about it are worth relying on:

**It is frozen.** An operator editing the policy while a workspace is
already pending deletion does not change what that deletion runs under;
`version` is which one it is, so a deletion certificate can name it.

**It is yours alone.** A deletion fans out to every application the
workspace subscribed to, and each one receives only its own policy.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `version` | `number` | The policy version in force when the deletion was confirmed. |
| `fiduciaryRole` | [`ConnectFiduciaryRole`](#type-alias-connectfiduciaryrole) | - |
| `exportBeforePurge` | `boolean` | Whether your policy promised the customer an export before the window closed. |
| `policyUrl` | `string` \| `null` | - |
| `dataClasses` | [`ConnectDataClassNotice`](#interface-connectdataclassnotice)[] | Empty when your application has declared no policy - which the customer was shown as "this application hasn't told us", not as "nothing is kept". |
| `resourceReleases` | [`ConnectResourceReleaseNotice`](#interface-connectresourcereleasenotice)[] | - |

***

### Interface: ConnectDoNotCallActor [#interface-connectdonotcallactor]

Who set or lifted a do-not-call entry.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `source` | `"portal"` \| `"application"` | - |
| `applicationId` | `string` \| `null` | The application the change came through. |
| `userId` | `string` \| `null` | A Connect user id - only when Connect verified it from a session token. |
| `externalActorId` | `string` \| `null` | Your own `actor.id` from a server-to-server write - your claim, not a Connect user. |
| `name` | `string` \| `null` | - |

***

### Interface: ConnectDoNotCallChange [#interface-connectdonotcallchange]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `changed` | `boolean` | `false` for a repeat set, or a clear of a number not listed. |
| `status` | [`ConnectDoNotCallStatus`](#interface-connectdonotcallstatus) | - |

***

### Interface: ConnectDoNotCallEvent [#interface-connectdonotcallevent]

#### Properties

| Property | Type |
| ------ | ------ |
| `action` | `"set"` \| `"cleared"` |
| `reason` | \| [`ConnectDoNotCallReason`](#type-alias-connectdonotcallreason) \| `null` |
| `note` | `string` \| `null` |
| `actor` | [`ConnectDoNotCallActor`](#interface-connectdonotcallactor) |
| `at` | `string` |

***

### Interface: ConnectDoNotCallLookup [#interface-connectdonotcalllookup]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `status` | [`ConnectDoNotCallStatus`](#interface-connectdonotcallstatus) | - |
| `history` | [`ConnectDoNotCallEvent`](#interface-connectdonotcallevent)[] \| `null` | Newest first - only when asked for with `history: true`. |

***

### Interface: ConnectDoNotCallStatus [#interface-connectdonotcallstatus]

A number's standing on a workspace's do-not-call list. Connect refuses
every outbound call to a listed number, so this is for showing the entry,
never for deciding whether to dial. A number never listed is
`doNotCall: false` with every other field `null`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `phoneNumber` | `string` | E.164, normalised. |
| `doNotCall` | `boolean` | - |
| `reason` | \| [`ConnectDoNotCallReason`](#type-alias-connectdonotcallreason) \| `null` | - |
| `note` | `string` \| `null` | - |
| `setAt` | `string` \| `null` | When they first asked, ISO-8601. A repeat set does not move it. |
| `setBy` | [`ConnectDoNotCallActor`](#interface-connectdonotcallactor) \| `null` | - |
| `clearedAt` | `string` \| `null` | The last time it was lifted, if it ever was. |
| `clearedBy` | [`ConnectDoNotCallActor`](#interface-connectdonotcallactor) \| `null` | - |
| `clearNote` | `string` \| `null` | - |

***

### Interface: ConnectInvitation [#interface-connectinvitation]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `invitationId` | `string` | - |
| `inviteeEmail` | `string` | - |
| `expiresAt` | `string` | ISO 8601. |
| `inviteUrl` | `string` | Always present, so you can offer copy/share when the email did not go out. |
| `emailSent` | `boolean` | - |

***

### Interface: ConnectMemberAccess [#interface-connectmemberaccess]

#### Properties

| Property | Type |
| ------ | ------ |
| `capacity` | [`ConnectMemberCapacity`](#interface-connectmembercapacity) |
| `viewer` | [`ConnectMemberViewer`](#interface-connectmemberviewer) |
| `invitations` | [`ConnectPendingInvitation`](#interface-connectpendinginvitation)[] |

***

### Interface: ConnectMemberAccessResult [#interface-connectmemberaccessresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `userId` | `string` | - |
| `roleKey` | `string` \| `null` | The role on your application after the change - `null` when access was revoked. |
| `granted` | `string`[] | Your application's permissions this member now holds, re-resolved. |

***

### Interface: ConnectMemberCapacity [#interface-connectmembercapacity]

How many member seats the workspace has, and whether there is another.

The number is the **workspace's**, not your plan's: a baseline plus
`allowances.members` from every subscription it holds, folded across
applications - so a seat bought through another product is a seat you may
fill. `included: null` means unlimited. `used` counts active members *and*
pending invitations, because an invitation is a seat already spoken for.

Advisory. The authoritative check runs inside `invite`, which is where two
people claiming the last seat at once is settled.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `included` | `number` \| `null` | - |
| `used` | `number` | - |
| `canAdd` | `boolean` | - |
| `reason` | `"no_allowance"` \| `"exhausted"` \| `"hard_ceiling"` \| `null` | `no_allowance` - nothing the workspace subscribes to grants member seats, and the answer is a plan. `exhausted` - every seat is taken, and the answer is an upgrade or a removal. They read alike and need different next steps, so word them differently. |

***

### Interface: ConnectMemberViewer [#interface-connectmemberviewer]

What the **signed-in caller** may do with the roster, from their Connect
permissions - not your application's.

Render your Invite and Change-role controls from this. Without it the only
way to find out is to try, which is a button that exists to fail.

#### Properties

| Property | Type |
| ------ | ------ |
| `canInvite` | `boolean` |
| `canManageAccess` | `boolean` |

***

### Interface: ConnectPendingInvitation [#interface-connectpendinginvitation]

An invitation that has been sent and not yet accepted - and is holding a seat.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `invitationId` | `string` | - |
| `inviteeEmail` | `string` | - |
| `roleKey` | `string` \| `null` | The role on *your* application it carries, or `null`. |
| `expiresAt` | `string` | ISO 8601. Seven days from when it was sent. |

***

### Interface: ConnectResourceReleaseNotice [#interface-connectresourcereleasenotice]

An identifier allocated to the workspace that must be held before reissue - a third clock, neither the grace window nor a retention floor.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | - |
| `label` | `string` | - |
| `quarantineDays` | `number` | Days to hold it before handing it to somebody else. |

***

### Interface: ConnectScreen [#interface-connectscreen]

One screen in your application, described for the assistant.

#### Why you declare these rather than Connect discovering them

Connect has no idea what your app is. It cannot know that `/deals` is a
pipeline, that a "lead" becomes a "contact", or that the thing your users
call a quote lives under Settings. Without that, an assistant sitting inside
your product answers from general knowledge about software - which is exactly
how it reads.

Declaring a screen is the cheapest possible fix: one line of prose per route,
and questions like "what can I do here?" start answering in your product's
own words.

#### What Connect does and does not do with them

They are **prose and links, and nothing else**. Connect puts your
descriptions into the model's context so it can answer and so `showActions`
can link to your screens. It never uses them to decide what anyone may do -
your app's own authorization is untouched and unconsulted here - and it never
executes anything on your behalf.

They also arrive from a browser, so Connect treats them as untrusted text:
bounded in count and length, and flattened to single lines before they reach
a prompt.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `path` | `string` | The route, as your router writes it. Dynamic segments in brackets - `/deals/[dealId]` - so a live path with a real id still matches. |
| `purpose` | `string` | One line: what a person does here, in your product's words. |
| `suggestions?` | `string`[] | Opening questions offered as chips on an empty conversation, phrased as your users would type them. Two or three; a fourth is a menu. |

***

### Interface: ConnectTaskDelivery\<TPayload\> [#interface-connecttaskdelivery]

What Cloud Tasks POSTs to a queue's URL - and what `verifyConnectTask` returns.

#### Type Parameters

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

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | The workspace the enqueue was gated on - trust this, not anything in `payload`. |
| `queue` | `string` | - |
| `payload` | `TPayload` | - |
| `retryCount` | `number` | From Cloud Tasks' own headers: 0 on the first attempt. |
| `taskName` | `string` \| `null` | Cloud Tasks' task id, stable across retries. |

***

### Interface: ConnectUser [#interface-connectuser]

Wire types for the `/api/v1/application/*` surface - the application-partner API,
and the only thing this SDK covers.

Hand-written rather than generated so the published package carries no
dependency on Connect's internals. `src/conformance.test.ts` type-checks
these against the server's own DTOs, so drift fails the build here instead
of surfacing as a runtime surprise in a partner's integration.

Dates cross the wire as ISO 8601 strings and are left that way - parsing
them into `Date` would be a lossy guess about what the caller wants, and
`new Date(value)` is right there.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `userId` | `string` | - |
| `email` | `string` \| `null` | - |
| `emailVerified` | `boolean` | - |
| `displayName` | `string` \| `null` | - |
| `photoUrl` | `string` \| `null` | The member's profile picture, or `null` when they have not set one. Deliberately nullable rather than a generated placeholder: only you know what your fallback should look like. `<ConnectUserMenu>` renders initials via `<Avatar.Fallback>`, which also covers the URL loading slowly or failing outright - so pair the two rather than branching on `null`. |
| `workspaceId` | `string` \| `null` | The workspace this session is scoped to - the tenant to key your own data on. **`null` only for a Connect platform staff session**, where `staff` is `true`; see `SessionAccess`. Nullable since 0.11.0, and the breaking change in that release. If you do not turn staff access on for your application (it is off by default and per-application), this is never `null` and the only work is a type narrow. If you do, decide first what your tenanting does with a session that has no tenant - typically: serve your operator console off `staff`, and nothing else. |
| `workspaceName` | `string` \| `null` | `null` on a staff session, for the same reason as `workspaceId`. |
| `workspaceType` | [`WorkspaceType`](#type-alias-workspacetype) \| `null` | Whether this is a one-seat personal account or a team. The one fact about the signed-in workspace you cannot look up yourself - the application API has no workspace read - so branch on it rather than inferring an account's shape from whether it happens to have a second member. An `"individual"` workspace has exactly one seat and can never have another, so a members table, an invite button or a seat-count meter is dead UI for it. Re-read it from `identity.getUser()`; do not cache it past the sign-in. An `"individual"` workspace can convert to `"business"` under a live session (the reverse never happens). `null` on a staff session - there is no workspace to have a type. |

***

### Interface: ConnectUserSession [#interface-connectusersession]

One login to the Connect account behind the `sessionToken` you called with -
not scoped to this application. A Connect session is one browser's login to
Connect itself, and it fans out to every partner application's own session -
Connect has no per-application session concept to filter by. `sessions.list()`
returns every one of them, exactly like Connect's own account settings
page does for the user themselves.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `createdAt` | `string` | ISO 8601. |
| `lastActiveAt` | `string` | ISO 8601. |
| `expiresAt` | `string` | ISO 8601. |
| `device` | [`SessionDevice`](#interface-sessiondevice) | - |
| `ip` | `string` \| `null` | - |
| `ipCity` | `string` \| `null` | - |
| `ipCountry` | `string` \| `null` | - |
| `isCurrent` | `boolean` | Whether this is the session your `sessionToken` was minted from. |

***

### Interface: ConnectWebhookEnvelope\<T\> [#interface-connectwebhookenvelope]

The envelope, as it arrives on the wire.

#### Type Parameters

| Type Parameter | Default type |
| ------ | ------ |
| `T` *extends* [`ConnectWebhookEventType`](#type-alias-connectwebhookeventtype) | [`ConnectWebhookEventType`](#type-alias-connectwebhookeventtype) |

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | Unique per delivery. **Dedupe on this.** Delivery is at-least-once and unordered: a partner that assumes exactly-once, or assumes order, will eventually be wrong. |
| `type` | `T` | - |
| `createdAt` | `string` | ISO-8601. |
| `apiVersion` | `string` | Bumped only for a breaking payload change - never for a new event type. |
| `data` | [`ConnectWebhookEventMap`](#interface-connectwebhookeventmap)\[`T`\] | - |

***

### Interface: ConnectWebhookEventMap [#interface-connectwebhookeventmap]

Maps each event type to the shape of its `data`.

#### Properties

| Property | Type |
| ------ | ------ |
| `workspace.deletion_scheduled` | [`WorkspaceDeletionScheduledEvent`](#interface-workspacedeletionscheduledevent) |
| `workspace.deletion_cancelled` | [`WorkspaceDeletionCancelledEvent`](#interface-workspacedeletioncancelledevent) |
| `workspace.deleted` | [`WorkspaceDeletedEvent`](#interface-workspacedeletedevent) |
| `subscription.cancelled` | [`SubscriptionCancelledEvent`](#interface-subscriptioncancelledevent) |
| `subscription.activated` | [`SubscriptionActivatedEvent`](#interface-subscriptionactivatedevent) |
| `call.completed` | [`CallCompletedEvent`](#interface-callcompletedevent) |
| `call.missed` | [`CallMissedEvent`](#interface-callmissedevent) |
| `call.analyzed` | [`CallAnalyzedEvent`](#interface-callanalyzedevent) |
| `booking.confirmed` | [`BookingConfirmedEvent`](#interface-bookingconfirmedevent) |
| `booking.cancelled` | [`BookingCancelledEvent`](#interface-bookingcancelledevent) |
| `booking.rescheduled` | [`BookingRescheduledEvent`](#interface-bookingrescheduledevent) |
| `contact.created` | [`ContactCreatedEvent`](#interface-contactcreatedevent) |
| `contact.updated` | [`ContactUpdatedEvent`](#interface-contactupdatedevent) |

***

### Interface: ConnectWorkspaceMember [#interface-connectworkspacemember]

One member of the workspace a session is on.

`granted` is resolved for *your* application - permission ids from your own
catalog, not the member's Connect roles.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `memberId` | `string` | The id the **calendar** speaks in - `Booking.hostMemberIds` holds these, and so does `listBookings`' `hostMemberId` filter. Carried beside `userId` because the two are different identifiers and scheduling is the one surface that uses this one: joining a diary to a roster, filtering it to one host, or putting a name against an appointment all go through `memberId`. Everywhere else a member is a `userId`. Opaque and workspace-scoped. Do not derive it from `userId` or address a member by it elsewhere in this API. |
| `userId` | `string` | - |
| `assigned` | `boolean` | Whether this member holds a seat on your application. Only assigned members can sign in to it; unassigned rows appear only when you asked for them with `members({ includeUnassigned: true })`, to assign from. |
| `email` | `string` | - |
| `displayName` | `string` \| `null` | - |
| `photoUrl` | `string` \| `null` | - |
| `status` | `string` | - |
| `granted` | `string`[] | - |
| `role` | `string` \| `null` | - |

***

### Interface: ConnectWorkspaceProfile [#interface-connectworkspaceprofile]

One subscribing workspace's identity, read from your server with nobody
signed in - `workspaces.get`. Needs the `workspace-profile:read` scope.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `name` | `string` | What the workspace calls itself - the name a greeting says. |
| `legalName` | `string` \| `null` | The self-declared billing name, or `null` until the customer has needed an invoice. |
| `type` | [`WorkspaceType`](#type-alias-workspacetype) | - |
| `status` | `"active"` \| `"suspended"` \| `"pending_deletion"` \| `"deleted"` | - |
| `owner` | \{ `displayName`: `string` \| `null`; `email`: `string` \| `null`; \} | - |
| `owner.displayName` | `string` \| `null` | - |
| `owner.email` | `string` \| `null` | The owner's sign-in email. |

***

### Interface: ConsumeAddonInput [#interface-consumeaddoninput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `addonId` | `string` | - |
| `quantity` | `number` | Whole units of the add-on's `unit`. |
| `description?` | `string` | - |

***

### Interface: ConsumeAddonResponse [#interface-consumeaddonresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `addonId` | `string` | - |
| `consumed` | `number` | - |
| `balance` | `number` | Remaining immediately after this call. Negative only when `overagePolicy` is `"bill"`. |
| `overageUnits` | `number` | - |

***

### Interface: ConsumeCreditInput [#interface-consumecreditinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `amount` | `number` | Paise, pre-tax. |
| `idempotencyKey` | `string` | Required - your own id for the unit of work. A retry under it moves nothing. |
| `description` | `string` | Shown to the customer on their ledger. |
| `reference?` | `string` \| `null` | - |

***

### Interface: ContactChannelValue [#interface-contactchannelvalue]

A phone or email on a contact.

`verified` means a human confirmed it reaches them - only a manual source
may ever set it. Branch on it: an unverified value is one an extraction
heard on a call and nobody has checked since.

#### Properties

| Property | Type |
| ------ | ------ |
| `value` | `string` |
| `label` | `string` \| `null` |
| `verified` | `boolean` |

***

### Interface: ContactCreatedEvent [#interface-contactcreatedevent]

Connect learned about a person.

The event with no equivalent anywhere else in this API: most often an agent
captured them mid-call through the `contact-capture` skill, so the contact
exists in Connect and your product has never heard of them. Without this the
capture is invisible where the customer actually works.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `contactId` | `string` | - |
| `name` | `string` \| `null` | - |
| `phone` | `string` \| `null` | E.164. |
| `email` | `string` \| `null` | - |
| `company` | `string` \| `null` | - |
| `source` | [`ConnectContactSource`](#type-alias-connectcontactsource) | - |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: ContactFactsInput [#interface-contactfactsinput]

What an interaction learned about a person, for `contacts.upsertFromInteraction`.

Every key optional: an absent key says nothing, an explicit `null` says
"unknown" - and neither clears a value a person typed, because automated
facts only fill blanks. `verified` on a phone or email is ignored; only a
human can verify one.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name?` | \{ `display?`: `string` \| `null`; `given?`: `string` \| `null`; `family?`: `string` \| `null`; \} | - |
| `name.display?` | `string` \| `null` | - |
| `name.given?` | `string` \| `null` | - |
| `name.family?` | `string` \| `null` | - |
| `business?` | \{ `company?`: `string` \| `null`; `role?`: `string` \| `null`; `gstin?`: `string` \| `null`; `website?`: `string` \| `null`; `address?`: `string` \| `null`; \} | - |
| `business.company?` | `string` \| `null` | - |
| `business.role?` | `string` \| `null` | - |
| `business.gstin?` | `string` \| `null` | - |
| `business.website?` | `string` \| `null` | - |
| `business.address?` | `string` \| `null` | - |
| `phones?` | \{ `value`: `string`; `label?`: `string` \| `null`; `verified?`: `boolean`; \}[] | E.164 values. Added to the contact's list, never replacing it. |
| `emails?` | \{ `value`: `string`; `label?`: `string` \| `null`; `verified?`: `boolean`; \}[] | - |
| `tags?` | `string`[] | - |
| `notes?` | `string` \| `null` | - |

***

### Interface: ContactInteractionResult [#interface-contactinteractionresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `contact` | [`ConnectContact`](#interface-connectcontact) | - |
| `created` | `boolean` | Whether this write created the contact. |
| `changedFields` | `string`[] | Dotted paths that actually changed - what landed, not what you sent. Empty when nothing did. |

***

### Interface: ContactUpdatedEvent [#interface-contactupdatedevent]

A known contact's details changed.

`changed` names the fields that actually moved, so you can apply a patch
rather than overwriting fields your own users may have curated - a customer
who corrected a company name in your product should not have it replaced by
what an agent heard over a bad line.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `contactId` | `string` | - |
| `name` | `string` \| `null` | - |
| `phone` | `string` \| `null` | E.164. |
| `email` | `string` \| `null` | - |
| `company` | `string` \| `null` | - |
| `changed` | `string`[] | The field names that changed on this update. |
| `source` | [`ConnectContactSource`](#type-alias-connectcontactsource) | - |
| `occurredAt` | `string` | ISO-8601. |

***

### Interface: CreditBalance [#interface-creditbalance]

A workspace's prepaid credit with your application - see `CreditResource`.
Every amount is **paise, pre-tax**.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `applicationId` | `string` | - |
| `label` | `string` | What the customer sees the balance as - "Calling credit". |
| `currency` | `"INR"` | - |
| `balance` | `number` | Negative only while an overdraft Connect granted you is in use. |
| `spendable` | `number` | What `consume` may take right now: `balance` plus any overdraft. |
| `overdraftLimit` | `number` | - |
| `toppedUp` | `number` | - |
| `consumed` | `number` | - |
| `refunded` | `number` | - |
| `topUpAvailable` | `boolean` | `false` while your credit program is switched off - existing credit can still be spent. |
| `topUpPath` | `string` | Pass to `useConnect().openConnect()` to send the customer to buy more. |
| `updatedAt` | `string` \| `null` | ISO 8601. |

***

### Interface: CreditLedgerEntry [#interface-creditledgerentry]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `type` | [`CreditEntryType`](#type-alias-creditentrytype) | - |
| `amount` | `number` | Paise, signed: positive adds credit, negative spends it. |
| `balanceAfter` | `number` | - |
| `idempotencyKey` | `string` | - |
| `description` | `string` | - |
| `reference` | `string` \| `null` | - |
| `reversesEntryId` | `string` \| `null` | On a `refund`: the consume entry it credited back. |
| `createdAt` | `string` | ISO 8601. |

***

### Interface: CreditLedgerPage [#interface-creditledgerpage]

#### Properties

| Property | Type |
| ------ | ------ |
| `items` | [`CreditLedgerEntry`](#interface-creditledgerentry)[] |
| `nextCursor` | `string` \| `null` |

***

### Interface: CreditMovement [#interface-creditmovement]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `duplicate` | `boolean` | `true` when this idempotency key had already been applied - nothing moved this time. |
| `entry` | [`CreditLedgerEntry`](#interface-creditledgerentry) | - |
| `balance` | `number` | - |
| `spendable` | `number` | - |

***

### Interface: DesignSystem [#interface-designsystem]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `radiusScale` | [`ScaleStep`](#interface-scalestep)[] | - |
| `spacingScale` | [`ScaleStep`](#interface-scalestep)[] | - |
| `shadows` | `string`[] | Raw `box-shadow` values, most used first. |
| `breakpoints` | `string`[] | `@media` breakpoint widths as written, ascending. |
| `customProperties` | \{ `name`: `string`; `value`: `string`; \}[] | Every `--*` declaration on `:root` - the company's own token vocabulary, verbatim. |
| `framework` | [`CssFramework`](#type-alias-cssframework) \| `null` | - |

***

### Interface: DisconnectIntegrationResponse [#interface-disconnectintegrationresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `success` | `true` |

***

### Interface: DriveFile [#interface-drivefile]

A file your application uploaded - see `connect.drive.list`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `fileName` | `string` | - |
| `mimeType` | `string` | - |
| `size` | `number` | - |
| `uploadedAt` | `string` | ISO-8601. |

***

### Interface: DriveFileList [#interface-drivefilelist]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `files` | [`DriveFile`](#interface-drivefile)[] | Newest first. |
| `truncated` | `boolean` | More files exist than the `limit` returned. There is no cursor. |

***

### Interface: DriveUsage [#interface-driveusage]

How full a workspace's Drive is. The limit is the one an upload is refused
against, so a meter built on this agrees with `quota_exceeded`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `usedBytes` | `number` | - |
| `limitBytes` | `number` \| `null` | `null` = unlimited. |

***

### Interface: EmitEventInput [#interface-emiteventinput]

One event you push into Connect's analytics pipeline.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | Dot-namespaced and lowercase, e.g. `checkout.completed`. Prefixes Connect emits itself - `api.`, `billing.`, `addon.`, `subscription.`, `connect.` - are rejected, so your events can never inflate a metered number. |
| `occurredAt?` | `string` \| `Date` | Defaults to now. Clamped server-side to within 48 hours. |
| `sessionToken?` | `string` | Attributes the event to the signed-in user and their workspace. |
| `value?` | `number` | A whole number in the smallest unit of `unit` - paise, bytes, milliseconds. |
| `unit?` | `string` | - |
| `props?` | `Record`\<`string`, `unknown`\> | - |

***

### Interface: EmitEventsResponse [#interface-emiteventsresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `delivered` | `boolean` | False when the call failed outright - see `AnalyticsResource.emit`. |
| `accepted` | `number` | - |
| `attributed` | `number` | - |

***

### Interface: EnqueueTaskInput [#interface-enqueuetaskinput]

One task, onto one of this application's declared task queues, for one
workspace it serves. Hand-written like every wire type here;
`conformance.test.ts` holds it to `EnqueueApplicationTaskInputSchema`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `queue` | `string` | A queue declared for this application in Console. |
| `payload?` | `unknown` | Any JSON. Delivered back as `payload`. Keep it to ids - 100 KB at most. |
| `name?` | `string` | A dedup key. A second enqueue with the same name for the same workspace answers `duplicate` and queues nothing - name a self-chaining step after the step (`campaign-42-page-3`) so two runners cannot both queue it. |
| `scheduleAt?` | `string` | ISO-8601, at most 30 days ahead. Omit to run as soon as the queue's rate allows. |

***

### Interface: EnqueueTaskResponse [#interface-enqueuetaskresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `status` | `"queued"` \| `"duplicate"` \| `"unavailable"` | `unavailable` means this Connect deployment has no task queue (local development). Nothing was queued; fall back to whatever you would do without one. |
| `scheduleAt` | `string` \| `null` | - |

***

### Interface: EnrichedAddress [#interface-enrichedaddress]

Every field nullable, including the ones your own record calls required.

An enrichment is partial by nature: a site with no address printed on it
yields no address. A shape that demanded a city would only be forcing one to
be invented.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `line1` | `string` \| `null` | - |
| `line2` | `string` \| `null` | - |
| `city` | `string` \| `null` | - |
| `state` | `string` \| `null` | The state or region's *name* as written, not a code. |
| `postalCode` | `string` \| `null` | - |
| `country` | `string` \| `null` | ISO 3166-1 alpha-2, uppercase. |

***

### Interface: EnrichedCompany [#interface-enrichedcompany]

What Connect read about a company, and where each part came from.

Every value is a claim from a page named in `citations`. Nothing here is
recalled by a model: a field the pages did not state is `null`, which is why
`null` is common and should be rendered as "not found" rather than as an
empty input somebody has to notice.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `domain` | `string` | Normalised - lowercase, no scheme, no `www.`. |
| `name` | `string` \| `null` | - |
| `legalName` | `string` \| `null` | The registered entity name. Always `null` today. It is reserved for a statutory register, and deliberately never filled from a website - a footer says "Acme" where the register says "Acme Industries Private Limited", and this is the one field here that would end up on a contract. |
| `description` | `string` \| `null` | - |
| `industry` | `string` \| `null` | - |
| `sizeBand` | [`CompanySizeBand`](#type-alias-companysizeband) \| `null` | - |
| `phone` | `string` \| `null` | - |
| `email` | `string` \| `null` | - |
| `website` | `string` \| `null` | - |
| `address` | [`EnrichedAddress`](#interface-enrichedaddress) \| `null` | - |
| `confidence` | `"medium"` \| `"low"` \| `"high"` | Derived from how many fields the pages actually supported - not the model's opinion of itself, which reflects the confidence of the prose it read. Treat `low` as "offer this", `high` as "safe to pre-fill". |
| `citations` | [`EnrichmentCitation`](#interface-enrichmentcitation)[] | - |
| `retrievedAt` | `string` | ISO-8601. Age the profile against this. |

***

### Interface: EnrichmentCitation [#interface-enrichmentcitation]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `source` | [`EnrichmentSource`](#type-alias-enrichmentsource) | - |
| `url` | `string` \| `null` | The exact page read. `null` only for `domain`. |
| `retrievedAt` | `string` | ISO-8601. |

***

### Interface: ExecuteIntegrationActionResponse [#interface-executeintegrationactionresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `data` | `unknown` |
| `successful` | `boolean` |

***

### Interface: GrievanceChannel [#interface-grievancechannel]

The platform's DPDP grievance channel - `companyProfile.grievance`.

Flat and non-nullable: Connect resolves every field against its published
defaults before answering, so there is no "not configured yet" state to
branch on here. See `PlatformResource.getCompanyProfile`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `officerName` | `string` | - |
| `officerEmail` | `string` | - |
| `hours` | `string` | Free text, e.g. `"Monday - Friday, 10:00 AM - 6:00 PM IST"`. |
| `responseDays` | `number` | The redressal window the published notice commits to, in days. |

***

### Interface: IdTokenClaims [#interface-idtokenclaims]

Verified claims from an `idToken`.

#### Indexable

```ts
[claim: string]: unknown
```

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `sub` | `string` | Connect user id. |
| `aud` | `string` | Your `client_id`. |
| `iss` | `string` | - |
| `exp` | `number` | - |
| `iat` | `number` | - |
| `org_id` | `string` | - |
| `email?` | `string` \| `null` | - |
| `email_verified?` | `boolean` | - |

***

### Interface: InitiateIntegrationConnectionResponse [#interface-initiateintegrationconnectionresponse]

The connection isn't active yet when this returns - see `IntegrationsResource.initiateConnect`'s doc comment.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `redirectUrl` | `string` | Send the browser here to complete the provider's OAuth screen. |
| `connectionId` | `string` | - |

***

### Interface: IntegrationsListResponse [#interface-integrationslistresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `toolkits` | [`IntegrationToolkit`](#interface-integrationtoolkit)[] |

***

### Interface: IntegrationToolkit [#interface-integrationtoolkit]

A Composio-hosted third-party toolkit (Google Calendar, Slack, Twitter/X,
...) this workspace can see - only ever one a platform admin enabled in
Connect's own Console, joined with this workspace's own connection status.
Connections are workspace-shared: one covers every member, the way a Slack app
install is shared rather than per-user.

#### Properties

| Property | Type |
| ------ | ------ |
| `slug` | `string` |
| `name` | `string` |
| `description` | `string` |
| `category` | `string` \| `null` |
| `logoUrl` | `string` \| `null` |
| `status` | [`IntegrationConnectionStatus`](#type-alias-integrationconnectionstatus) |

***

### Interface: KvEntry [#interface-kventry]

One entry from the `kv` store.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | - |
| `value` | `unknown` | - |
| `expiresAt` | `string` \| `null` | ISO-8601, or `null` when the entry never expires. |
| `updatedAt` | `string` | - |

***

### Interface: KvPage [#interface-kvpage]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | [`KvEntry`](#interface-kventry)[] | - |
| `nextCursor` | `string` \| `null` | Pass as the next `cursor`. `null` is the end of the namespace. A page can be shorter than `limit` and still have one - expired entries are filtered after paging - so this, not an empty `items`, is the signal to stop. |

***

### Interface: ListBookingsPage [#interface-listbookingspage]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `bookings` | [`Booking`](#interface-booking)[] | - |
| `nextCursor` | `string` \| `null` | Pass back as `cursor`. A cursor belongs to one set of filters. |
| `hasMore` | `boolean` | - |
| `totalCount` | `number` | - |

***

### Interface: ManageableEventType [#interface-manageableeventtype]

One appointment type, as a management UI sees it - richer than
`BookableEventType`'s booking-catalogue shape, which deliberately
withholds `location.value` and everything else here. Fetched from
`listManageableEventTypes`, never the booking flow's `listEventTypes`.

`host` collapses a group host to `{kind:"group"}` with nothing else -
there is no groups read in this API, so a team host is representable only
as "not this, edit it in Connect".

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `slug` | `string` |
| `name` | `string` |
| `description` | `string` \| `null` |
| `durationMinutes` | `number` |
| `bufferBeforeMinutes` | `number` |
| `bufferAfterMinutes` | `number` |
| `host` | \| \{ `kind`: `"member"`; `memberId`: `string`; \} \| \{ `kind`: `"group"`; \} |
| `scheduleId` | `string` |
| `location` | \{ `kind`: `"phone"` \| `"address"` \| `"link"`; `value`: `string` \| `null`; \} |
| `location.kind` | `"phone"` \| `"address"` \| `"link"` |
| `location.value` | `string` \| `null` |
| `active` | `boolean` |
| `publicBookingEnabled` | `boolean` |

***

### Interface: Offering [#interface-offering]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | - |
| `category` | `string` \| `null` | The company's own grouping when it has one. |
| `description` | `string` \| `null` | - |
| `url` | `string` \| `null` | - |

***

### Interface: PaletteEntry [#interface-paletteentry]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `hex` | `string` | Lowercase `#rrggbb`. |
| `role` | [`PaletteRole`](#type-alias-paletterole) \| `null` | Assigned by a model over measured values - never invented by one. |
| `source` | [`PaletteSource`](#type-alias-palettesource) | - |
| `variableName` | `string` \| `null` | The CSS custom property it came from, when `source` is `css-var`. |
| `occurrences` | `number` | - |

***

### Interface: Plan [#interface-plan]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `name` | `string` | - |
| `planGroup` | `string` | The *tier* this plan prices, independent of how often it bills. A tier's monthly and annual plans share one - so `"growth"` covers both the "Growth" monthly row and the "Growth" annual row. This is what `ConnectPlans` groups its cards by. Before it existed the grid paired plans by matching `name`, which Connect neither enforces to be unique nor keeps stable across a rename or a clone - so a catalog could silently split one tier across two cards, or show a saving computed from two plans that had nothing to do with each other. |
| `description` | `string` | - |
| `price` | `number` | GST-exclusive, in paise. |
| `currency` | `"INR"` | - |
| `billingInterval` | [`BillingInterval`](#type-alias-billinginterval) | - |
| `gstRate` | `number` | - |
| `addons` | [`PlanAddon`](#interface-planaddon)[] | - |
| `seats` | [`PlanSeats`](#interface-planseats) | - |
| `storage` | [`PlanStorage`](#interface-planstorage) | - |
| `trialDays` | `number` \| `null` | - |
| `features` | [`PlanFeature`](#interface-planfeature)[] | What this plan unlocks, labelled for display - see `PlanFeature`. |
| `limits` | [`ResolvedPlanLimit`](#interface-resolvedplanlimit)[] | How much of each metered resource this plan allows, labelled for display - see `ResolvedPlanLimit`. |

***

### Interface: PlanAddon [#interface-planaddon]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `name` | `string` | - |
| `price` | `number` | GST-exclusive, in paise. Per plan cycle, or per year when `billingCadence` is `"annual"`. |
| `consumable?` | [`PlanAddonConsumable`](#interface-planaddonconsumable) \| `null` | - |
| `billingCadence?` | `"plan"` \| `"annual"` \| `null` | When the add-on is billed. `"annual"` = once on the first cycle that holds it, then on each anniversary - on a monthly plan too - so render its price as "/year". `"plan"` (or absent, from a Connect that predates the field) = every plan cycle, as before. |

***

### Interface: PlanAddonConsumable [#interface-planaddonconsumable]

#### Properties

| Property | Type |
| ------ | ------ |
| `unit` | `string` |
| `includedPerCycle` | `number` |
| `carryForward` | `boolean` |
| `overagePolicy` | `"reject"` \| `"bill"` |
| `overagePricePerUnit` | `number` \| `null` |
| `maxOverageUnits` | `number` \| `null` |

***

### Interface: PlanFeature [#interface-planfeature]

A plan feature with its label, as the **catalogue** read reports it.

Note the asymmetry with `SubscribedPlan.features`, which is bare
`string[]` and stays that way. That field is a gating contract - your
backend calls `hasFeature(workspaceId, "campaign-module")` against it, and
`ConnectFeatureGate` matches on it - so turning it into objects would break
every check for a label nobody gates on. This one exists to be rendered,
and rendering a bare key puts "campaign-module" on a pricing card where it
meant "Campaign module".

`key` is carried here too, so a component doing both needs no second lookup.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | The same value `SubscribedPlan.features` holds and `hasFeature` gates on. |
| `name` | `string` | Human label. Falls back to `key` for a plan predating the application's feature catalog. |
| `description` | `string` | Empty string when the catalog carries no description. |

***

### Interface: PlanLimit [#interface-planlimit]

What a plan allows of one metered resource - the **gating** shape, which is
what `getLimit` returns and `planLimitAllows` takes.

**Three states rather than a nullable number, deliberately.** The obvious
encoding - a `number | null` where `null` means unlimited - makes
`if (!limit) refuse()` read as correct while refusing every subscriber on
the most generous tier, which is the one case nobody tests. Use
`planLimitAllows` rather than comparing by hand: `count < value` is off by
one when adding more than one row, and `value ?? Infinity` grants unlimited
to a plan that allows none.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `model` | `"unlimited"` \| `"none"` \| `"limited"` | - `"none"`: this plan does not allow the resource at all. Also what a key the plan carries no answer for reads as - see `SubscribedPlan.limits`. - `"limited"`: a ceiling of `value`. - `"unlimited"`: no ceiling. |
| `value` | `number` \| `null` | The ceiling. Non-null iff `model` is `"limited"`, and always >= 1 there - zero is spelled `"none"`. |

***

### Interface: PlanSeats [#interface-planseats]

#### Properties

| Property | Type |
| ------ | ------ |
| `model` | `"unlimited"` \| `"fixed"` \| `"per-seat"` |
| `included` | `number` \| `null` |
| `pricePerExtraSeat` | `number` \| `null` |

***

### Interface: PlansResponse [#interface-plansresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `plans` | [`Plan`](#interface-plan)[] |

***

### Interface: PlanStorage [#interface-planstorage]

#### Properties

| Property | Type |
| ------ | ------ |
| `model` | `"unlimited"` \| `"fixed"` |
| `includedBytes` | `number` \| `null` |

***

### Interface: PresenceCategoryScore [#interface-presencecategoryscore]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `category` | [`PresenceCategory`](#type-alias-presencecategory) | - |
| `score` | `number` | 0-100 within the category, so a weak one is legible beside a strong one. |
| `weight` | `number` | - |
| `earned` | `number` | - |

***

### Interface: PresenceCriterion [#interface-presencecriterion]

One check, its verdict and the evidence behind it.

`weight` and `earned` are both published so the total can be reconstructed:
a `partial` that cost two points and one that cost six look identical
without them, and a score nobody can reconstruct is a score nobody trusts.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | Stable across scans, so two reports of one domain can be diffed. |
| `category` | [`PresenceCategory`](#type-alias-presencecategory) | - |
| `label` | `string` | - |
| `status` | [`PresenceCriterionStatus`](#type-alias-presencecriterionstatus) | - |
| `weight` | `number` | The check's full weight in the rubric. |
| `earned` | `number` | The share of `weight` actually earned. |
| `observed` | `string` | What was seen, in one line. |
| `citation` | `string` \| `null` | The URL that settles it, when there is one. |
| `fix` | `string` \| `null` | What to change. `null` when the check passed. |

***

### Interface: PresenceRecommendation [#interface-presencerecommendation]

One prioritised thing to do. `fix` is Connect's own reviewed text; `why` is written for this business.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `criterionKey` | `string` | Names the check this came from, so advice is always traceable to evidence. |
| `category` | [`PresenceCategory`](#type-alias-presencecategory) | - |
| `priority` | `number` | 1 is what to do first. Always 1..n with no gaps. |
| `title` | `string` | - |
| `why` | `string` | What it costs this business specifically. Empty when the narrative could not be generated. |
| `fix` | `string` | - |

***

### Interface: PresenceScan [#interface-presencescan]

How visible a business is online.

The important property, and the one that makes this worth showing a
customer: **the score was computed from the checks, and the checks were
measured.** Every point comes from something a fetcher observed on the site.
A model contributed only the ordering of `recommendations` and the prose in
`why` and `summary`; it cannot alter a status, a weight or the score.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `domain` | `string` | Normalised - lowercase, no scheme, no `www.`. |
| `score` | `number` | 0-100. |
| `band` | [`PresenceBand`](#type-alias-presenceband) | - |
| `categories` | [`PresenceCategoryScore`](#interface-presencecategoryscore)[] | - |
| `criteria` | [`PresenceCriterion`](#interface-presencecriterion)[] | Every check, passed and failed. The audit trail behind the number. |
| `social` | [`PresenceSocialProfile`](#interface-presencesocialprofile)[] | Every profile found, including networks the rubric does not weight individually. |
| `summary` | `string` | Two or three sentences on the overall state. Empty when it could not be generated - the scan is complete without it. |
| `recommendations` | [`PresenceRecommendation`](#interface-presencerecommendation)[] | What to do, in order. Render this before `criteria`. |
| `scannedAt` | `string` | ISO-8601. Age the scan against this. |

***

### Interface: PresenceSocialProfile [#interface-presencesocialprofile]

A profile the subject's own site linked.

**Claimed, never verified.** Nothing in a scan proves the account belongs to
that business - that needs a credential for each network. Render it as
"linked from their site", never as a confirmed account.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `network` | [`PresenceNetwork`](#type-alias-presencenetwork) | - |
| `url` | `string` | - |
| `handle` | `string` \| `null` | - |
| `machineReadable` | `boolean` | Whether the site declared it in `schema.org` `sameAs` markup rather than only as a link. The one to read closely. `false` means a real presence with none of the discoverability - a search engine has to infer the connection from a footer icon - and it is among the most common findings a scan produces. |

***

### Interface: ProxyIntegrationRequestResponse [#interface-proxyintegrationrequestresponse]

See `IntegrationsResource.request` - `status` is the provider's, not Connect's.

#### Properties

| Property | Type |
| ------ | ------ |
| `status` | `number` |
| `data` | `unknown` |

***

### Interface: PushKeyResponse [#interface-pushkeyresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `configured` | `boolean` | `false` when the platform has no VAPID keys - say so rather than prompting. |
| `publicKey` | `string` | The `applicationServerKey` for `pushManager.subscribe()`. Empty when unconfigured. |

***

### Interface: ReadinessAnswerability [#interface-readinessanswerability]

Whether an assistant could answer a buyer's question from the site's own pages.

The one judged rather than measured part of the report - a model read the
pages and decided. Everything else in `checks` was parsed from real responses.

#### Properties

| Property | Type |
| ------ | ------ |
| `offering` | `boolean` |
| `pricing` | `boolean` |
| `howToBuy` | `boolean` |
| `contact` | `boolean` |
| `summary` | `string` |
| `gaps` | `string`[] |

***

### Interface: ReadinessBlocker [#interface-readinessblocker]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `code` | \| [`ReadinessBlockerCode`](#type-alias-readinessblockercode) \| `string` & \{ \} | Branch on this. Typed as a union for the codes that exist today, widened with `(string & {})` because the server adds codes without a major version - an unrecognised one should be treated as blocking. |
| `severity` | `"blocking"` \| `"advisory"` | - |
| `message` | `string` | Human-facing and subject to rewording. Show it, do not parse it. |
| `resolveUrl` | `string` \| `null` | Where in Connect the customer resolves this. `null` when nothing they do helps (e.g. a review in progress). |
| `graceEndsAt?` | `string` \| `null` | When this blocker resolves itself automatically - currently only set on `payment_retry_pending`, the deadline dunning suspends the subscription. `undefined`/`null` for every other code. |
| `requiredWorkspaceType?` | `"individual"` \| `"business"` \| `null` | The account type this application requires - set only on `audience_mismatch`, `undefined`/`null` otherwise. Branch on this rather than reading it out of `message`. |

***

### Interface: ReadinessCheck [#interface-readinesscheck]

One measured fact, with the evidence that settles it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | Stable dotted id, e.g. `mcp.server`. Safe to key on; findings cite it. |
| `dimension` | [`ReadinessDimension`](#type-alias-readinessdimension) | - |
| `kind` | [`ReadinessCheckKind`](#type-alias-readinesscheckkind) | - |
| `title` | `string` | - |
| `status` | [`ReadinessCheckStatus`](#type-alias-readinesscheckstatus) | - |
| `weight` | `number` | Relative importance inside its dimension. Only ratios are meaningful. |
| `observed` | `string` | What was actually observed, in one line - specific enough to show the user verbatim. |
| `evidence` | `string` \| `null` | The URL that proves it, where there is one. |

***

### Interface: ReadinessDimensionScore [#interface-readinessdimensionscore]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `dimension` | [`ReadinessDimension`](#type-alias-readinessdimension) | - |
| `score` | `number` | 0-100. |
| `weight` | `number` | This dimension's share of the overall score. |
| `unmeasured` | `number` | Core checks that could not be measured. Show this rather than implying full coverage. |

***

### Interface: ReadinessFinding [#interface-readinessfinding]

One thing to do about it, anchored in the checks that motivated it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `severity` | `"critical"` \| `"warning"` \| `"note"` | - |
| `checkIds` | `string`[] | The checks this is about. Every id here appears in `checks`. |
| `title` | `string` | - |
| `detail` | `string` | - |
| `fix` | `string` | - |
| `effort` | `"medium"` \| `"low"` \| `"high"` | - |

***

### Interface: ReadinessReport [#interface-readinessreport]

How ready a site is for AI agents.

The important property, and the one that makes this worth showing a customer:
**the score was computed from the checks, and the checks were measured.**
Whether an MCP server exists is a completed JSON-RPC handshake, not a file
that happened to answer 200; whether assistants are blocked is that site's
`robots.txt` evaluated by its real rules. A model contributed only
`answerability` and the prose in `findings`, and it cannot alter a status or
the score.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `domain` | `string` | Normalised - lowercase, no scheme, no `www.`. |
| `score` | `number` | 0-100, weighted across dimensions. |
| `grade` | [`ReadinessGrade`](#type-alias-readinessgrade) | - |
| `dimensions` | [`ReadinessDimensionScore`](#interface-readinessdimensionscore)[] | - |
| `checks` | [`ReadinessCheck`](#interface-readinesscheck)[] | - |
| `findings` | [`ReadinessFinding`](#interface-readinessfinding)[] | Ordered by consequence, most serious first. |
| `answerability` | [`ReadinessAnswerability`](#interface-readinessanswerability) \| `null` | - |
| `topFix` | \| \{ `checkId`: `string`; `title`: `string`; `fix`: `string`; \} \| `null` | The one thing to do first. Drawn from `findings`, so it never disagrees with them. |
| `assessedAt` | `string` | ISO-8601. Age the report against this. |

***

### Interface: ReadinessResponse [#interface-readinessresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `applicationId` | `string` | - |
| `ready` | `boolean` | `false` if any blocker is `"blocking"`. |
| `readiness` | \{ `source`: `"connect"`; `blockers`: [`ReadinessBlocker`](#interface-readinessblocker)[]; \} | - |
| `readiness.source` | `"connect"` | - |
| `readiness.blockers` | [`ReadinessBlocker`](#interface-readinessblocker)[] | - |

***

### Interface: RefundCreditInput [#interface-refundcreditinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `consumeIdempotencyKey` | `string` | The `idempotencyKey` the consume was sent with. |
| `amount` | `number` | - |
| `idempotencyKey` | `string` | - |
| `description` | `string` | - |

***

### Interface: RegisterPushDeviceInput [#interface-registerpushdeviceinput]

Exactly what `PushSubscription.toJSON()` produces, plus the agent string for the device list.

#### Properties

| Property | Type |
| ------ | ------ |
| `endpoint` | `string` |
| `keys` | \{ `p256dh`: `string`; `auth`: `string`; \} |
| `keys.p256dh` | `string` |
| `keys.auth` | `string` |
| `userAgent` | `string` |

***

### Interface: RegisterPushDeviceResponse [#interface-registerpushdeviceresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | Hand this to `revokeDevice` to turn the same browser off again. |

***

### Interface: ReportedUsage [#interface-reportedusage]

One count Connect has on record for a workspace - see `EntitlementsResource.reportUsage`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `value` | `number` | - |
| `reportedAt` | `string` | ISO 8601. When this count was last reported - shown to the customer as its age. |

***

### Interface: ReportUsageResponse [#interface-reportusageresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `counts` | `Record`\<`string`, [`ReportedUsage`](#interface-reportedusage)\> | Every count on record for the workspace after this report, keyed by limit key - not only the ones just sent. |

***

### Interface: RescheduledBooking [#interface-rescheduledbooking]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `booking` | [`Booking`](#interface-booking) | The new appointment. This is the live one. |
| `previous` | [`Booking`](#interface-booking) | The retired original, now `rescheduled` and pointing at `booking`. |

***

### Interface: ResolvedPlanLimit [#interface-resolvedplanlimit]

How much of one metered resource a plan allows, as the **catalogue** read
reports it: the application's own declaration (`key`, `name`, `unit`) joined
to this plan's answer.

The limits counterpart of `PlanFeature`, and it carries the same asymmetry
against `SubscribedPlan.limits` for the same reason - that one is a contract
keyed for lookup, this one is a row to render.

There is one entry per limit the application declares, including the ones a
tier allows none of (`model: "none"`). "Starter: 1 site" only means anything
beside "Growth: 5 sites", so a comparison table needs the row even where the
answer is nothing; a card showing only what a tier includes filters on
`model !== "none"`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `key` | `string` | What `getLimit` and `SubscribedPlan.limits` are keyed by. |
| `name` | `string` | Human label, e.g. "Sites". |
| `unit` | `string` | The noun the number needs, e.g. "sites" - render as `${value} ${unit}`. |
| `description` | `string` | Empty string when the application's catalog carries no description. |
| `model` | `"unlimited"` \| `"none"` \| `"limited"` | - |
| `value` | `number` \| `null` | Non-null iff `model` is `"limited"`. |

***

### Interface: RevokeOtherSessionsResponse [#interface-revokeothersessionsresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `success` | `true` |
| `revokedCount` | `number` |

***

### Interface: RolesResponse [#interface-rolesresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `applicationId` | `string` |
| `applicationKey` | `string` \| `null` |
| `permissionCatalog` | [`ApplicationPermission`](#interface-applicationpermission)[] |
| `roles` | [`ApplicationRoleDefinition`](#interface-applicationroledefinition)[] |

***

### Interface: ScaleStep [#interface-scalestep]

One step of a measured scale.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `value` | `number` | - |
| `unit` | `string` | `px`, `rem`, `em`, `%`. |
| `occurrences` | `number` | - |

***

### Interface: ScannedStoreDocument [#interface-scannedstoredocument]

One row from a cross-workspace scan - carries the workspace it belongs to.

#### Extends

- [`StoreDocument`](#interface-storedocument)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `id` | `string` | - | [`StoreDocument`](#interface-storedocument).[`id`](#interface-storedocument) |
| `data` | `Record`\<`string`, `unknown`\> | The document's own fields, plus Connect's bookkeeping under `_`: `_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy`, and - on anything that came from a read - `_updateTime`. `_updateTime` is the value a `{ updateTime }` precondition on `commit` is compared against. It is Firestore's own write timestamp rather than the `_updatedAt` field beside it, because a transform in the same commit can move the field and cannot touch the write timestamp. None of them may be written back - strip every `_`-prefixed key before sending a document you read. | [`StoreDocument`](#interface-storedocument).[`data`](#interface-storedocument) |
| `workspaceId` | `string` | - | - |

***

### Interface: SendEmailInput [#interface-sendemailinput]

One email, sent on behalf of a workspace that subscribes to your
application - see `EmailResource`.

Hand-written here like every wire type in this file - see `SendPushInput`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | The subscribing workspace this is sent on behalf of. |
| `to` | `string`[] | 1-10 addresses. |
| `cc?` | `string`[] | Up to 10 addresses. A copy of somebody already in `to` is dropped. |
| `subject` | `string` | Up to 300 characters, no line breaks. |
| `html` | `string` | Up to 256 KB. |
| `text` | `string` | The plain-text alternative - required, up to 256 KB. |
| `replyTo?` | `string` | Where the recipient's reply goes - your inbound address. |
| `fromName?` | `string` | The display name, up to 80 characters. Defaults to the workspace's name; the address is always Connect's. |
| `headers?` | \{ `messageId?`: `string`; `inReplyTo?`: `string`; `references?`: `string`; \} | Threading headers. Each value is `<id@domain>`; `references` is space-separated. No line breaks. |
| `headers.messageId?` | `string` | A Message-ID to propose. Connect's mail provider replaces it - store the response's `messageId` instead. |
| `headers.inReplyTo?` | `string` | - |
| `headers.references?` | `string` | - |

***

### Interface: SendEmailResponse [#interface-sendemailresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `messageId` | `string` | The Message-ID the email left under, angle brackets included. A reply names it in `In-Reply-To`. |
| `suppressed` | `string`[] | Recipients skipped because they hard-bounced or marked Connect's mail as spam. Empty on an ordinary send; every recipient when nothing was sent. |

***

### Interface: SendOtpInput [#interface-sendotpinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `phoneNumber` | `string` | E.164, e.g. `+919876543210`. |
| `channel?` | [`OtpChannel`](#type-alias-otpchannel) | - |

***

### Interface: SendOtpResponse [#interface-sendotpresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `verificationId` | `string` | - |
| `status` | `"pending"` | - |
| `channel` | [`OtpChannel`](#type-alias-otpchannel) | - |
| `expiresIn` | `number` | - |
| `code?` | `string` | Test mode only. |

***

### Interface: SendPushInput [#interface-sendpushinput]

One notification, to members of one workspace.

Hand-written here rather than imported from `@connect/data`, like every wire
type in this file: the published package carries none of Connect's
internals, and `conformance.test.ts` type-checks these against the server's
own DTOs so drift fails the build in Connect rather than in a partner's
integration.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `userIds` | `string`[] | Connect user ids. Ids naming somebody who is not an active member are dropped rather than refused - a partner legitimately holds a stale id for somebody who has left, and failing the whole call for one of them would mean a reminder nobody gets. |
| `title` | `string` | - |
| `body` | `string` | - |
| `url` | `string` | Absolute, and on an origin this application registered as an SSO redirect URI. |

***

### Interface: SendPushResponse [#interface-sendpushresponse]

What a fan-out actually did.

Reported honestly rather than collapsed to a boolean, because the
interesting number is usually `silenced`: a partner seeing zero delivered
needs to know whether nobody has a device or everybody muted them, and those
call for opposite responses.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `recipients` | `number` | Members reached after preference filtering. |
| `silenced` | `number` | Members dropped because they silenced notifications from their apps. |
| `delivered` | `number` | Devices the notification actually reached, across every recipient. |
| `failed` | `number` | - |

***

### Interface: SendWhatsAppInput [#interface-sendwhatsappinput]

One of your application's own Meta-approved templates, to one customer.

Hand-written here like every wire type in this file - see `SendPushInput`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | The subscribing workspace this is sent on behalf of. |
| `template` | `string` | The template's slug within your application, e.g. `quote-issued` - never its Meta name. |
| `language?` | `string` | Meta language code the template was approved in. Defaults to `en`. |
| `to` | `string` | E.164, e.g. `+919876543210`. |
| `values?` | `Record`\<`string`, `string`\> | One value per template variable, by name. A missing one refuses the send. |
| `consent?` | \{ `collectedVia`: `string`; \} | The opt-in your application collected, recorded only when Connect has none on file for the number. A number that withdrew stays withdrawn. |
| `consent.collectedVia` | `string` | - |

***

### Interface: SendWhatsAppResponse [#interface-sendwhatsappresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `status` | `"sent"` |
| `messageId` | `string` |

***

### Interface: SessionAccess [#interface-sessionaccess]

Who the signed-in account is to you, in three flat fields.

Replaces the `applicationRole` (`{role, granted}`) and `platformAccess`
(`{roles}`) blocks, which were mutually exclusive by construction: a
Connect platform account belongs to no workspace, so one of the two was
always the empty answer and every integration wrote the same null-check
twice.

Read `staff` first - it decides what the other two mean.

**`staff` is not a client-side security boundary.** `<ConnectPlatformGate>`
and the hooks around it decide what to *render*; the browser can ignore all
of it. Re-check it server-side with `client.identity.getUser(sessionToken)`
before serving anything an operator console exposes.

#### Extended by

- [`TokenResponse`](#interface-tokenresponse)
- [`UserResponse`](#interface-userresponse)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `role` | `string` \| `null` | The one role this session holds, or `null`. For a member (`staff: false`): the role matched on *your* application, resolved for your `client_id` rather than their workspace-wide roles - `null` when their permissions match no single role you defined, including when they have no access to your application at all. For staff (`staff: true`): the highest-authority Connect platform role **your application accepts** - never Connect's full role list for that person. An application accepting only `developer` is told `"developer"` about a `platform_admin` who also holds it. |
| `permissions` | `string`[] | What to gate on. Permission ids from *your* own `permissionCatalog` for a member; Connect `platform:*` ids for staff. One vocabulary at a time, decided by `staff` - a member session never carries a `platform:*` id and a staff session never carries one of yours. |
| `staff` | `boolean` | `true` when this is one of Connect's own operators rather than your customer. Only ever `true` when a Connect platform account signs in **and** your application has staff access turned on for that role. It is off by default; a platform admin enables it per application in the Connect Console, so until then this is always `false`. A staff session is shaped differently, and every difference is deliberate: `user.workspaceId`/`workspaceName`/`workspaceType` are `null` (these accounts belong to no workspace, by enforcement rather than by accident) and `subscription` is `null` (nobody is paying for staff to be here). Serve your operator console off this session and nothing else. |

***

### Interface: SessionAudience [#interface-sessionaudience]

Whether the signed-in workspace is inside your application's audience -
returned alongside `user` by both `exchangeCode()` and `getUser()`.

`user.workspaceType` answers what the account *is*; this answers what your
application is *sold to*, and whether the two agree. Both ship together so
the check stays `!audience.matches` rather than a hardcoded audience
constant in your own code that goes stale the moment the catalogue entry
changes.

`matches: false` means readiness will report an `audience_mismatch` blocker
and any attempt to subscribe will be refused, so treat it as a hard block:
`<ConnectAccessGate>` already does. What it is NOT is a reason to sign the
user out - they have a valid Connect account, just the wrong kind of one
for this application.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `application` | [`ApplicationAudience`](#type-alias-applicationaudience) | Your catalogue entry's setting - `"both"` if you have never narrowed it. |
| `requiredWorkspaceType` | [`WorkspaceType`](#type-alias-workspacetype) \| `null` | The single `workspaceType` `application` demands - `null` for `"both"`, which accepts either. |
| `matches` | `boolean` | `false` means this user is outside your audience. |
| `enforcedAtSignIn` | `boolean` | Whether Connect refuses the sign-in itself on a mismatch. When `true`, a `matches: false` can only reach you on a session minted before the setting was turned on. When `false`, gating is entirely yours to do - which is what `<ConnectAccessGate>` is for. |

***

### Interface: SessionDevice [#interface-sessiondevice]

#### Properties

| Property | Type |
| ------ | ------ |
| `userAgent` | `string` |
| `browser` | `string` \| `null` |
| `browserVersion` | `string` \| `null` |
| `os` | `string` \| `null` |
| `osVersion` | `string` \| `null` |
| `deviceType` | [`SessionDeviceType`](#type-alias-sessiondevicetype) |

***

### Interface: SessionsListResponse [#interface-sessionslistresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `items` | [`ConnectUserSession`](#interface-connectusersession)[] |

***

### Interface: SessionSubscription [#interface-sessionsubscription]

The compact subscription block returned alongside identity - `null` when the workspace has never subscribed to this application.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `planId` | `string` | - |
| `status` | [`SubscriptionStatus`](#type-alias-subscriptionstatus) | - |
| `nextBillingDate` | `string` \| `null` | - |
| `isTrialing` | `boolean` | `status` alone cannot tell trialing from paying; both are `"active"`. |

***

### Interface: StartedBrowserCall [#interface-startedbrowsercall]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `callId` | `string` | - |
| `toNumber` | `string` | The number being rung, E.164. |
| `fromNumber` | `string` | The workspace's own number, which the person called will see. |
| `headers` | `Record`\<`string`, `string`\> | - |

***

### Interface: StoreAggregatePage [#interface-storeaggregatepage]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `values` | `Record`\<`string`, `number` \| `null`\> | One number per alias. A `sum` or `avg` over nothing is `null`, not `0` - "nothing matched" and "the total is zero" are different facts. A `count` is always a number. |

***

### Interface: StoreAggregation [#interface-storeaggregation]

One aggregation over a filtered collection.

`alias` is what the result is keyed by, so a single request can carry a
count and a sum without the response shape depending on argument order.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `op` | [`StoreAggregationOperation`](#type-alias-storeaggregationoperation) | - |
| `field?` | `string` \| `null` | Required for `sum`/`avg`. `count` counts documents and takes none. |
| `alias` | `string` | - |

***

### Interface: StoreCollectionDeclaration [#interface-storecollectiondeclaration]

One collection as an application declares it, and the helpers that read a
declaration back.

The declaration is the same shape Connect stores on `Application.storeSchema`
and the same one `scripts/seed-*-application.ts` writes there, so an app that
builds its schema from its own query matrix can hand the result straight to
anything here without a second type of its own. Connect remains the
authority - this is a local copy of what was declared, useful for deriving
things *from* the declaration, never for deciding access.

Isomorphic and dependency-free, like the rest of the root entry.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | - |
| `kind` | `"data"` \| `"kv"` | - |
| `scope` | `"application"` \| `"workspace"` | - |
| `description` | `string` | Prose. Connect shows it in the Console; `deriveStoreTools` gives it to a model. |
| `readPermission` | `string` \| `null` | The per-user permission Connect checks when a call carries `asUser`. `null` means any member. |
| `writePermission` | `string` \| `null` | - |
| `crossWorkspace` | `boolean` | Whether `data.scan` may read this collection across every workspace. |
| `appendOnly` | `boolean` | Create-only. Connect refuses a replace, a merge or a delete for every caller. |
| `queryableFields` | `string`[] | Nothing may be filtered, ordered or aggregated on a field not listed. |
| `indexes` | \{ `fields`: [`StoreIndexField`](#interface-storeindexfield)[]; \}[] | - |

***

### Interface: StoreCommitOperation [#interface-storecommitoperation]

One write inside a commit. Each names its own collection - a commit may span several.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `op` | [`StoreCommitMode`](#type-alias-storecommitmode) | - |
| `collection` | `string` | - |
| `id?` | `string` \| `null` | Omit only on `create`, to have an id allocated. |
| `data?` | `Record`\<`string`, `unknown`\> \| `null` | Absent on `delete`. Top-level values may be `StoreFieldTransform`s. |
| `precondition?` | [`StorePrecondition`](#type-alias-storeprecondition) \| `null` | Not accepted on `create`, which already means "and it must not exist". |

***

### Interface: StoreCommitResult [#interface-storecommitresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `ids` | `string`[] | The document id each operation wrote, positionally aligned with the operations sent. |

***

### Interface: StoreDocument [#interface-storedocument]

One document from the `data` store.

Connect's own bookkeeping rides *inside* `data` under a `_` prefix -
`_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy` - rather than as
sibling fields, so a document round-trips through your code unchanged. You
cannot write a `_`-prefixed field; that is what makes `_createdBy`
trustworthy. It is `"application"` for a server-to-server write, or the
Connect user id when a signed-in user drove it.

#### Extended by

- [`ScannedStoreDocument`](#interface-scannedstoredocument)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `data` | `Record`\<`string`, `unknown`\> | The document's own fields, plus Connect's bookkeeping under `_`: `_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy`, and - on anything that came from a read - `_updateTime`. `_updateTime` is the value a `{ updateTime }` precondition on `commit` is compared against. It is Firestore's own write timestamp rather than the `_updatedAt` field beside it, because a transform in the same commit can move the field and cannot touch the write timestamp. None of them may be written back - strip every `_`-prefixed key before sending a document you read. |

***

### Interface: StoreGetManyResult [#interface-storegetmanyresult]

What `data.getMany` answers - see `POST /data-get-many`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | ([`StoreDocument`](#interface-storedocument) \| `null`)[] | Positionally aligned with the ids asked for, `null` where a document does not exist. A missing document is an ordinary answer here, not a 404. |

***

### Interface: StoreIndexField [#interface-storeindexfield]

#### Properties

| Property | Type |
| ------ | ------ |
| `field` | `string` |
| `direction` | [`StoreIndexDirection`](#type-alias-storeindexdirection) |

***

### Interface: StoreOrderBy [#interface-storeorderby]

#### Properties

| Property | Type |
| ------ | ------ |
| `field` | `string` |
| `direction` | `"asc"` \| `"desc"` |

***

### Interface: StoreQueryFilter [#interface-storequeryfilter]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `field` | `string` | Must be one of the collection's declared `queryableFields`. |
| `op` | [`StoreQueryOperator`](#type-alias-storequeryoperator) | - |
| `value` | `unknown` | - |

***

### Interface: StoreQueryPage [#interface-storequerypage]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | [`StoreDocument`](#interface-storedocument)[] | - |
| `nextCursor` | `string` \| `null` | Pass as the next `cursor`. `null` is the end of the collection. |

***

### Interface: StoreScanPage [#interface-storescanpage]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | [`ScannedStoreDocument`](#interface-scannedstoredocument)[] | - |
| `nextCursor` | `string` \| `null` | `{workspaceId}/{documentId}`. Pass as the next `cursor`; `null` is the end. |

***

### Interface: SubscribedPlan [#interface-subscribedplan]

The plan as attached to a live subscription - a narrower view than the catalog's `Plan`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `name` | `string` | - |
| `billingInterval` | [`BillingInterval`](#type-alias-billinginterval) | - |
| `price` | `number` | - |
| `currency` | `"INR"` | - |
| `gstRate` | `number` | - |
| `seats` | [`PlanSeats`](#interface-planseats) | - |
| `features` | `string`[] | Feature **keys** this plan unlocks. Your backend gates plan-restricted modules on this, and `ConnectFeatureGate` matches on it. Deliberately bare keys, unlike the catalogue's `Plan.features` - see `PlanFeature`. This is a contract, not a label: a key is stable and comparable, a display name is neither. |
| `limits` | `Record`\<`string`, [`PlanLimit`](#interface-planlimit)\> | How much of each metered resource this workspace may have, keyed by the limit key your application declared - see `PlanLimit`. **Effective, not the tier's raw number**: whatever the add-ons on this subscription raise a limit by is already folded in, so this is the figure to compare your own count against. The tier's base number and the add-ons that raised it are deliberately not broken out here - that is the join Connect does so nobody has to redo it per resource, per partner. **Sparse, and that is the thing to get right**: a key absent here is not "unlimited" and not an error, it is `{ model: "none" }`. Read it through `getLimit`/`planLimitFor` rather than indexing the map, which is what turns a missing key into `undefined` and then, one `?.` later, into an unbounded allowance. Same posture as `features`: Connect cannot intercept calls to your backend, so your backend compares its own count against this number. Connect stores the answer and hands it over. |

***

### Interface: SubscribersResponse [#interface-subscribersresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | [`ApplicationSubscriber`](#interface-applicationsubscriber)[] | - |
| `nextBefore` | `string` \| `null` | Pass back as `before` for the next page. `null` on the last page - which is the only reliable end-of-list signal, since a full page can still be the last one. |

***

### Interface: SubscriptionActivatedEvent [#interface-subscriptionactivatedevent]

A subscription became active for the first time: a free plan or a trial
started, or a paid plan's first charge was captured. Grant what the plan
includes. Not raised on a renewal, nor when a paused subscription recovers.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `subscriptionId` | `string` | - |
| `applicationId` | `string` | - |
| `planId` | `string` | - |
| `trialEndsAt` | `string` \| `null` | ISO-8601 when it started on a trial, `null` otherwise. |
| `activatedAt` | `string` | ISO-8601. |

***

### Interface: SubscriptionCancelledEvent [#interface-subscriptioncancelledevent]

A subscription ended. Fires on its own, not only as part of a deletion.

#### Properties

| Property | Type |
| ------ | ------ |
| `workspaceId` | `string` |
| `subscriptionId` | `string` |
| `applicationId` | `string` |

***

### Interface: SubscriptionDetail [#interface-subscriptiondetail]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | Deep-link target: `/billing/subscriptions/{id}` manages this subscription. |
| `status` | [`SubscriptionStatus`](#type-alias-subscriptionstatus) | - |
| `cancelAtPeriodEnd` | `boolean` | - |
| `seatCount` | `number` | How many workspace members currently hold a role for your application, counted when you read it. Compare it against `seatCapacity`. `0` on an `"unlimited"` plan - there is no ceiling to count against, so Connect does not pay for the count. `ConnectSeatUsage` renders nothing in that case for the same reason. |
| `seatCapacity?` | `number` \| `null` | How many members may hold your application's role: what a `"fixed"` plan includes, or the seats a `"per-seat"` subscription has **bought** (never fewer than it includes). `null` on `"unlimited"`. On a per-seat plan a seat is a purchase - assigning someone past this number is refused, and the customer buys more on Connect at `/billing/subscriptions/{id}?seats=N#seats`. Older Connect deployments do not send it; fall back to `plan.seats.included` (`seatCapacityOf` does). |
| `addons` | `string`[] | - |
| `currentBillingCycle` | \| \{ `startDate`: `string`; `endDate`: `string`; \} \| `null` | - |
| `nextBillingDate` | `string` \| `null` | - |
| `subscribedAt` | `string` | - |
| `appliedDiscount` | [`AppliedDiscount`](#interface-applieddiscount) \| `null` | `null` when no coupon or admin discount is in effect. |

***

### Interface: SubscriptionResponse [#interface-subscriptionresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `applicationId` | `string` | - |
| `hasActiveSubscription` | `boolean` | Equivalent to `subscription?.status === "active"`. |
| `subscription` | [`SubscriptionDetail`](#interface-subscriptiondetail) \| `null` | - |
| `plan` | [`SubscribedPlan`](#interface-subscribedplan) \| `null` | `null` exactly when `subscription` is. |

***

### Interface: TelephonyCarrierEvent [#interface-telephonycarrierevent]

A carrier event Connect relayed to your telephony webhook - verify it with
`verifyTelephonyEvent`. For `answer` and `fallback`, the XML you respond
with is executed on the call verbatim.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `type` | `"telephony.carrier_event"` | - |
| `event` | [`TelephonyEdgeEvent`](#type-alias-telephonyedgeevent) | - |
| `params` | `Record`\<`string`, `string`\> | The carrier's form parameters, verbatim, as strings - `CallUUID`, `From`, `To`, `RequestUUID`, ... |
| `channel` | \| \{ `id`: `string`; `workspaceId`: `string`; `address`: `string`; \} \| `null` | The number your application handles that the event concerns, when Connect could tell. `null` for a browser call. |
| `receivedAt` | `string` | ISO-8601. |

***

### Interface: TelephonyDialInput [#interface-telephonydialinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `channelId` | `string` | A channel your application handles in that workspace. |
| `to` | `string` | E.164. |
| `ringTimeoutSeconds` | `number` | 5-120. |
| `clientRef?` | `string` | Your own reference for the call. |

***

### Interface: TelephonyDialResult [#interface-telephonydialresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `providerRequestId` | `string` | The carrier's request id - arrives on the answer event as `params.RequestUUID`. |

***

### Interface: TokenResponse [#interface-tokenresponse]

Who the signed-in account is to you, in three flat fields.

Replaces the `applicationRole` (`{role, granted}`) and `platformAccess`
(`{roles}`) blocks, which were mutually exclusive by construction: a
Connect platform account belongs to no workspace, so one of the two was
always the empty answer and every integration wrote the same null-check
twice.

Read `staff` first - it decides what the other two mean.

**`staff` is not a client-side security boundary.** `<ConnectPlatformGate>`
and the hooks around it decide what to *render*; the browser can ignore all
of it. Re-check it server-side with `client.identity.getUser(sessionToken)`
before serving anything an operator console exposes.

#### Extends

- [`SessionAccess`](#interface-sessionaccess)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `role` | `string` \| `null` | The one role this session holds, or `null`. For a member (`staff: false`): the role matched on *your* application, resolved for your `client_id` rather than their workspace-wide roles - `null` when their permissions match no single role you defined, including when they have no access to your application at all. For staff (`staff: true`): the highest-authority Connect platform role **your application accepts** - never Connect's full role list for that person. An application accepting only `developer` is told `"developer"` about a `platform_admin` who also holds it. | [`SessionAccess`](#interface-sessionaccess).[`role`](#interface-sessionaccess) |
| `permissions` | `string`[] | What to gate on. Permission ids from *your* own `permissionCatalog` for a member; Connect `platform:*` ids for staff. One vocabulary at a time, decided by `staff` - a member session never carries a `platform:*` id and a staff session never carries one of yours. | [`SessionAccess`](#interface-sessionaccess).[`permissions`](#interface-sessionaccess) |
| `staff` | `boolean` | `true` when this is one of Connect's own operators rather than your customer. Only ever `true` when a Connect platform account signs in **and** your application has staff access turned on for that role. It is off by default; a platform admin enables it per application in the Connect Console, so until then this is always `false`. A staff session is shaped differently, and every difference is deliberate: `user.workspaceId`/`workspaceName`/`workspaceType` are `null` (these accounts belong to no workspace, by enforcement rather than by accident) and `subscription` is `null` (nobody is paying for staff to be here). Serve your operator console off this session and nothing else. | [`SessionAccess`](#interface-sessionaccess).[`staff`](#interface-sessionaccess) |
| `idToken` | `string` | - | - |
| `tokenType` | `"Bearer"` | - | - |
| `expiresIn` | `number` | Seconds until `idToken` expires - about 5 minutes. | - |
| `sessionToken` | `string` | Opaque, revocable. Store this; it is the only part of the response meant to outlive the callback. | - |
| `sessionTokenExpiresIn` | `number` | Seconds until `sessionToken` expires - about 15 days. | - |
| `user` | [`ConnectUser`](#interface-connectuser) | - | - |
| `subscription` | [`SessionSubscription`](#interface-sessionsubscription) \| `null` | - | - |
| `audience` | [`SessionAudience`](#interface-sessionaudience) | Whether this account is the kind your application serves - see `SessionAudience`. | - |

***

### Interface: TypefaceEntry [#interface-typefaceentry]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `family` | `string` | The first named family in the stack - the one the company chose, not the fallbacks. |
| `role` | [`TypefaceRole`](#type-alias-typefacerole) \| `null` | - |
| `weights` | `number`[] | - |
| `webfontUrl` | `string` \| `null` | - |
| `occurrences` | `number` | - |

***

### Interface: UploadedFile [#interface-uploadedfile]

#### Properties

| Property | Type |
| ------ | ------ |
| `id` | `string` |
| `fileName` | `string` |
| `mimeType` | `string` |
| `size` | `number` |
| `url` | `string` |

***

### Interface: UpsertContactFromInteractionInput [#interface-upsertcontactfrominteractioninput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `phone` | `string` | The person's number, E.164 - the key the contact is found or created by. |
| `facts` | [`ContactFactsInput`](#interface-contactfactsinput) | - |
| `source` | `"call"` \| `"extraction"` | `call` for what your telephony knows, `extraction` for what a model inferred. |
| `callId?` | `string` \| `null` | The call or conversation the facts came from. Recorded against each fact. |
| `countCall?` | `boolean` | `true` only on the one write that closes the interaction. Defaults to `false`, so a save made mid-call does not count the call again. |

***

### Interface: UserResponse [#interface-userresponse]

Who the signed-in account is to you, in three flat fields.

Replaces the `applicationRole` (`{role, granted}`) and `platformAccess`
(`{roles}`) blocks, which were mutually exclusive by construction: a
Connect platform account belongs to no workspace, so one of the two was
always the empty answer and every integration wrote the same null-check
twice.

Read `staff` first - it decides what the other two mean.

**`staff` is not a client-side security boundary.** `<ConnectPlatformGate>`
and the hooks around it decide what to *render*; the browser can ignore all
of it. Re-check it server-side with `client.identity.getUser(sessionToken)`
before serving anything an operator console exposes.

#### Extends

- [`SessionAccess`](#interface-sessionaccess)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `role` | `string` \| `null` | The one role this session holds, or `null`. For a member (`staff: false`): the role matched on *your* application, resolved for your `client_id` rather than their workspace-wide roles - `null` when their permissions match no single role you defined, including when they have no access to your application at all. For staff (`staff: true`): the highest-authority Connect platform role **your application accepts** - never Connect's full role list for that person. An application accepting only `developer` is told `"developer"` about a `platform_admin` who also holds it. | [`SessionAccess`](#interface-sessionaccess).[`role`](#interface-sessionaccess) |
| `permissions` | `string`[] | What to gate on. Permission ids from *your* own `permissionCatalog` for a member; Connect `platform:*` ids for staff. One vocabulary at a time, decided by `staff` - a member session never carries a `platform:*` id and a staff session never carries one of yours. | [`SessionAccess`](#interface-sessionaccess).[`permissions`](#interface-sessionaccess) |
| `staff` | `boolean` | `true` when this is one of Connect's own operators rather than your customer. Only ever `true` when a Connect platform account signs in **and** your application has staff access turned on for that role. It is off by default; a platform admin enables it per application in the Connect Console, so until then this is always `false`. A staff session is shaped differently, and every difference is deliberate: `user.workspaceId`/`workspaceName`/`workspaceType` are `null` (these accounts belong to no workspace, by enforcement rather than by accident) and `subscription` is `null` (nobody is paying for staff to be here). Serve your operator console off this session and nothing else. | [`SessionAccess`](#interface-sessionaccess).[`staff`](#interface-sessionaccess) |
| `user` | [`ConnectUser`](#interface-connectuser) | - | - |
| `subscription` | [`SessionSubscription`](#interface-sessionsubscription) \| `null` | - | - |
| `audience` | [`SessionAudience`](#interface-sessionaudience) | Live, like `subscription` and `role`/`permissions`/`staff` - a workspace that converted to a business since sign-in matches here without another sign-in. | - |
| `sessionExpiresAt` | `string` | ISO 8601. Refresh proactively off this rather than waiting for a 401. | - |

***

### Interface: UserRolesResponse [#interface-userrolesresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `userId` | `string` |
| `workspaceId` | `string` |
| `role` | `string` \| `null` |
| `granted` | `string`[] |

***

### Interface: UserWorkspace [#interface-userworkspace]

One workspace the signed-in user belongs to - a row in a workspace switcher.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `name` | `string` | - |
| `type` | [`WorkspaceType`](#type-alias-workspacetype) | - |
| `isActive` | `boolean` | The workspace this session is currently pinned to. |
| `inAudience` | `boolean` | Whether your application is sold to this kind of workspace. `false` rows are worth showing disabled with the reason - a workspace that silently disappears from a switcher reads as data loss to the person who owns it. |
| `subscribed` | `boolean` | Whether it holds an entitling subscription to your application. |

***

### Interface: VerifyOtpInput [#interface-verifyotpinput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `verificationId` | `string` | - |
| `code` | `string` | 6 digits. |

***

### Interface: VerifyOtpResponse [#interface-verifyotpresponse]

#### Properties

| Property | Type |
| ------ | ------ |
| `verificationId` | `string` |
| `status` | `"pending"` \| `"approved"` \| `"expired"` \| `"max_attempts_exceeded"` |
| `phoneNumber` | `string` |

***

### Interface: VisitorAppointment [#interface-visitorappointment]

The appointment `visitor.book` placed, and the one a reopened conversation
reports.

`VisitorBooking` plus `label`, and the extra field is the point: the words are
produced by the same formatter the confirmation email uses, so the chat and
the email cannot read differently about the same appointment. Render it rather
than formatting `startAt` yourself - the visitor's browser is in their zone and
the appointment is in the host's, and `Intl`'s month names differ between
engines besides.

#### Extends

- [`VisitorBooking`](#interface-visitorbooking)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `startAt` | `string` | - | [`VisitorBooking`](#interface-visitorbooking).[`startAt`](#interface-visitorbooking) |
| `endAt` | `string` | - | [`VisitorBooking`](#interface-visitorbooking).[`endAt`](#interface-visitorbooking) |
| `timezone` | `string` | The workspace's zone, not the visitor's - the zone the confirmation uses. | [`VisitorBooking`](#interface-visitorbooking).[`timezone`](#interface-visitorbooking) |
| `label` | `string` | - | - |

***

### Interface: VisitorBooking [#interface-visitorbooking]

An appointment this conversation booked, as a visitor is allowed to see it.

Deliberately not the booking: no host ids, no workspace ids, no internal
reference - the same narrowing Connect's own public booking page makes.

#### Extended by

- [`VisitorAppointment`](#interface-visitorappointment)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `startAt` | `string` | - |
| `endAt` | `string` | - |
| `timezone` | `string` | The workspace's zone, not the visitor's - the zone the confirmation uses. |

***

### Interface: VisitorKnowledgeItem [#interface-visitorknowledgeitem]

One thing a workspace's website chat is allowed to tell strangers.

**Not the workspace's Context Index.** That corpus is fed by every Drive
upload; this store holds only what somebody explicitly published, which is
what makes it safe to answer an anonymous visitor from. Nothing arrives here
as a side effect of anything else.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `id` | `string` | - |
| `title` | `string` | - |
| `body` | `string` | - |
| `source` | [`VisitorKnowledgeSource`](#type-alias-visitorknowledgesource) | - |
| `sourceRef` | `string` \| `null` | The crawled URL, or the uploaded file's name. `null` for authored text. |
| `status` | `"draft"` \| `"published"` | - |
| `indexState` | [`VisitorKnowledgeIndexState`](#type-alias-visitorknowledgeindexstate) | - |
| `indexError` | `string` \| `null` | Why the last index attempt failed, for your own screen to show. |
| `publishedAt` | `string` \| `null` | ISO-8601, or `null` for a draft. |
| `createdAt` | `string` | - |
| `updatedAt` | `string` | - |

***

### Interface: VisitorKnowledgeList [#interface-visitorknowledgelist]

The knowledge base, and how its published half reaches the model.

`mode` is a property of the *set*, which is why it is here and not on each
item - and why a screen should read it from here rather than inferring it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `items` | [`VisitorKnowledgeItem`](#interface-visitorknowledgeitem)[] | Drafts included: this is the list a customer audits. |
| `mode` | `"none"` \| `"inline"` \| `"retrieval"` | - |

***

### Interface: VisitorMessage [#interface-visitormessage]

One stored message in a visitor conversation. `at` is ISO-8601.

#### Properties

| Property | Type |
| ------ | ------ |
| `role` | `"visitor"` \| `"assistant"` |
| `text` | `string` |
| `at` | `string` |

***

### Interface: VisitorSession [#interface-visitorsession]

A conversation, as `visitor.startSession` opens or reopens it.

**It carries no greeting and no suggestions.** Those are your own widget
settings, rendered from your own store - so your bubble paints its first frame
with no call to Connect at all, and this exists only to mint or recover the
conversation Connect holds.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `sessionId` | `string` | Hold this and send it with every turn. Treat it as a credential: it is the only thing identifying the conversation, and anyone holding it can continue it. Keep it in `sessionStorage` - it should survive a reload, which is what lets somebody follow a link out of the chat and come back, and it should not travel to another tab or outlive the visit. |
| `messages` | [`VisitorMessage`](#interface-visitormessage)[] | The transcript, oldest first. Empty on a new conversation. |
| `field` | [`VisitorField`](#type-alias-visitorfield) | - |
| `turnsRemaining` | `number` | - |
| `booking` | [`VisitorAppointment`](#interface-visitorappointment) \| `null` | - |

***

### Interface: VisitorSlot [#interface-visitorslot]

One time the assistant may offer, as its slot lookup returned it.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `startAt` | `string` | ISO-8601. Pass this back verbatim to book it. |
| `label` | `string` | Already written out in the workspace's own timezone - render this, don't reformat. |

***

### Interface: VisitorSlotOffer [#interface-visitorslotoffer]

The times a turn looked up, lifted out of the tool result.

**Draw the buttons from this, never from the assistant's sentence.** A time the
visitor taps is then one the server offered, which is what makes
`visitor.book` unable to take a time nobody was given.

#### Properties

| Property | Type |
| ------ | ------ |
| `eventType` | `string` |
| `durationMinutes` | `number` |
| `timezone` | `string` |
| `slots` | [`VisitorSlot`](#interface-visitorslot)[] |

***

### Interface: VisitorToolCall [#interface-visitortoolcall]

What a tool did this turn. `result` is the tool's own payload - switch on `name`.

#### Properties

| Property | Type |
| ------ | ------ |
| `name` | `string` |
| `result` | `unknown` |

***

### Interface: VisitorTurnDone [#interface-visitorturndone]

The `done` event at the end of a streamed turn.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `toolCalls` | [`VisitorToolCall`](#interface-visitortoolcall)[] | - |
| `field` | [`VisitorField`](#type-alias-visitorfield) | - |
| `turnsRemaining` | `number` | - |
| `grounded` | `boolean` | Whether the reply was grounded in published knowledge. `false` means nothing published covered the question, so the assistant said so and offered a call. Worth recording: a run of them is a knowledge base with a gap in it, which is the most useful signal this endpoint produces. |

***

### Interface: WeeklyHours [#interface-weeklyhours]

A week of working hours, one entry per weekday - `0` is Sunday. An empty
array means closed that day; every key is required, so "closed on Sunday"
is an array the caller had to mean rather than a key they forgot.

#### Properties

| Property | Type |
| ------ | ------ |
| `0` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `1` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `2` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `3` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `4` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `5` | [`WeeklyInterval`](#interface-weeklyinterval)[] |
| `6` | [`WeeklyInterval`](#interface-weeklyinterval)[] |

***

### Interface: WeeklyInterval [#interface-weeklyinterval]

One interval within a day, in minutes since midnight - `540` is 9am.

#### Properties

| Property | Type |
| ------ | ------ |
| `startMinute` | `number` |
| `endMinute` | `number` |

***

### Interface: WhatsAppTemplateState [#interface-whatsapptemplatestate]

Whether one of your templates would send right now. Branch on `live`.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `slug` | `string` | - |
| `language` | `string` | - |
| `name` | `string` | Meta's name for it: `app_<applicationKey>_<slug>`. |
| `status` | [`WhatsAppTemplateReviewStatus`](#type-alias-whatsapptemplatereviewstatus) | - |
| `live` | `boolean` | Approved by Meta *and* currently served. |
| `variables` | `string`[] | The values a send must carry, from the approved content. |

***

### Interface: WorkspaceApp [#interface-workspaceapp]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `applicationId` | `string` | - |
| `applicationKey` | `string` \| `null` | Stable, human-readable key - branch on this, not `applicationId`, which is per-environment. |
| `name` | `string` | - |
| `description` | `string` | Plain text, HTML already stripped server-side. |
| `iconUrl` | `string` \| `null` | Uploaded brand icon. `null` falls back to `icon`. |
| `icon` | `string` | A `lucide-react` icon name from Connect's curated set. |
| `category` | [`WorkspaceAppCategory`](#type-alias-workspaceappcategory) | Catalog classification, if you want to group or filter the grid by it. |
| `launchUrl` | `string` \| `null` | Where a tile navigates. `null` when the application registered no site. |
| `subscribed` | `boolean` | Whether this workspace holds a launchable subscription. `/apps` answers with the platform's whole active catalog, not only what the workspace bought, so this is the split a launcher renders as two groups: the apps you open, and the apps you could. `false` means the tile belongs in the second - send it to Connect, not to `launchUrl`. |
| `isCurrent` | `boolean` | True for the application whose API key made the call - render it as the active tile. |
| `needsAttention` | `boolean` | Launchable but not healthy (paused, or cancelling at period end) - badge it, do not hide it. Always `false` when `subscribed` is `false`. |

***

### Interface: WorkspaceBillingAddress [#interface-workspacebillingaddress]

A workspace's registered address, as its billing profile reports it.

`stateCode` is the Indian state code, and it is the field that matters most
of the six: it decides CGST+SGST versus IGST on anything you raise against
this entity.

#### Properties

| Property | Type |
| ------ | ------ |
| `line1` | `string` \| `null` |
| `line2` | `string` \| `null` |
| `city` | `string` \| `null` |
| `stateCode` | `string` \| `null` |
| `postalCode` | `string` \| `null` |
| `countryCode` | `string` |

***

### Interface: WorkspaceBillingProfile [#interface-workspacebillingprofile]

A workspace's **self-declared** billing identity - what the customer gave
CosmoOps so we can invoice them.

#### Read it to prefill, not to share

This is the *Bill To* side: who we invoice for the subscription. Your own
application's letterhead is the *Bill From* side. For a single-entity
customer they are the same three facts, which is the whole reason this is
readable - a GSTIN typed twice is a GSTIN that diverges, and the copy on a
quotation is the one that decides how the tax splits.

They are **not the same field**, though. A group that bills us through a
holding company while quoting under a trading subsidiary has to stay
expressible, so copy this into your own settings on first setup and keep
your copy authoritative. There is deliberately no write form: an
application must not be able to edit the details its own invoices are
raised against.

#### Nothing here is verified

It is what the customer typed, not a KYB record - no PAN check stands
behind it. A convenience, never evidence of identity.

Every field is nullable, because every one is optional to a customer who has
not needed an invoice yet. Fall back to the workspace name you already hold
on the session rather than printing an empty letterhead.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `legalName` | `string` \| `null` | The registered entity name, or `null` - most workspaces never set one. |
| `taxId` | `string` \| `null` | GSTIN, 15 characters. `null` for an unregistered customer. |
| `billingAddress` | [`WorkspaceBillingAddress`](#interface-workspacebillingaddress) \| `null` | `null` as a whole when none was given - one check, not six. |
| `businessType` | `string` \| `null` | The legal entity form, self-declared. `null` for an individual workspace. |

***

### Interface: WorkspaceDeletedEvent [#interface-workspacedeletedevent]

The grace window closed and Connect purged the workspace. Purge your own copy.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `purgedAt` | `string` | ISO-8601. |
| `policy` | [`ConnectDeletionPolicyNotice`](#interface-connectdeletionpolicynotice) \| `null` | The same frozen policy `workspace.deletion_scheduled` carried, repeated here because this is the event you act on: whether a class is destroyed now or moved to a retained store for another `retainDays` is this object's answer, not your code's. |

***

### Interface: WorkspaceDeletionCancelledEvent [#interface-workspacedeletioncancelledevent]

The deletion was withdrawn and the workspace is live again.

**Handle this one.** It is the event integrations forget, and its absence
is the worst failure of the set: an application that froze a workspace on
`workspace.deletion_scheduled` and never unfreezes leaves a paying
customer locked out of something that has no idea anything changed.

#### Properties

| Property | Type |
| ------ | ------ |
| `workspaceId` | `string` |

***

### Interface: WorkspaceDeletionScheduledEvent [#interface-workspacedeletionscheduledevent]

A workspace's deletion has been confirmed and the grace window is running.

Access is *already* denied - readiness reports
`workspace_pending_deletion` from this moment - so treat this as "stop
writing", not "start planning". `purgeAfter` is what makes this event worth
having: the customer's data still exists until then, so an export or a
wind-down can run on the same clock rather than after everything is gone.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | - |
| `purgeAfter` | `string` | ISO-8601. When Connect destroys the workspace. |
| `requestedBy` | `"owner"` \| `"platform_admin"` | - |
| `graceDays` | `number` | - |
| `policy` | [`ConnectDeletionPolicyNotice`](#interface-connectdeletionpolicynotice) \| `null` | Your declared policy for this deletion - including how long each data class may outlive the purge. `null` for a deletion confirmed before policies existed, which is the only case where you fall back to your own records. |

***

### Interface: WorkspaceSwitchResult [#interface-workspaceswitchresult]

The result of a workspace switch.

`sessionToken` is a **new** credential - the one you switched from is
revoked. Persist this before navigating, or the user is signed out.

#### Properties

| Property | Type |
| ------ | ------ |
| `sessionToken` | `string` |
| `sessionTokenExpiresIn` | `number` |
| `workspaceId` | `string` |
| `workspaceName` | `string` |
| `workspaceType` | [`WorkspaceType`](#type-alias-workspacetype) |

***

### Interface: WriteLogInput [#interface-writeloginput]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `level?` | [`LogLevel`](#type-alias-loglevel) | - |
| `message` | `string` | - |
| `metadata?` | `Record`\<`string`, `unknown`\> | - |
| `timestamp?` | `string` \| `Date` | ISO 8601 or a `Date`. Defaults to now. |
| `sessionToken?` | `string` | Attribute this event to an end user, by their opaque Connect `sessionToken` - the same one the token exchange handed you. **There is no `userId` to send, deliberately.** The token is verified and checked to have been minted for *your* application before anything is attributed, exactly as `applicationId` comes from your API key rather than your body. An actor a caller could type is not an actor worth recording. Omit it for genuine server-to-server events with no user behind them. |

***

### Interface: WriteLogResponse [#interface-writelogresponse]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `delivered` | `boolean` | `false` when the write failed - see `LogsResource.write`, which never throws. |
| `attributed` | `boolean` | Whether a supplied `sessionToken` resolved to a user. `false` with `delivered: true` means the line was stored unattributed - an expired or revoked token. The event is deliberately kept rather than rejected: losing telemetry because attribution failed is the worse of the two outcomes. Always `false` when no `sessionToken` was sent. |
## Type Aliases

### Type Alias: AiGenerateMessage [#type-alias-aigeneratemessage]

```ts
type AiGenerateMessage = 
  | {
  role: "user";
  content: string;
}
  | {
  role: "assistant";
  content: string;
  toolCalls?: AiGenerateToolCall[];
}
  | {
  role: "tool";
  toolCallId: string;
  toolName: string;
  output: unknown;
};
```

One message of the conversation you send. To continue after a tool call,
send the model's turn back as an `assistant` message carrying its
`toolCalls`, then one `tool` message per call with what running it produced.

***

### Type Alias: AiTone [#type-alias-aitone]

```ts
type AiTone = "neutral" | "positive" | "warning" | "danger";
```

***

### Type Alias: AiToolName [#type-alias-aitoolname]

```ts
type AiToolName = 
  | "showTable"
  | "showChart"
  | "showMetrics"
  | "showTimeline"
  | "showDetails"
  | "showSteps"
  | "showSources"
  | "showActions";
```

The tools this client knows how to draw. Anything else in the stream is ignored.

***

### Type Alias: AiValue [#type-alias-aivalue]

```ts
type AiValue = string | number | null;
```

One cell. `null` is "no value" and renders as an em dash.

***

### Type Alias: AiValueFormat [#type-alias-aivalueformat]

```ts
type AiValueFormat = 
  | "text"
  | "number"
  | "currency"
  | "percent"
  | "date"
  | "datetime"
  | "duration"
  | "bytes"
  | "status";
```

How a scalar should be presented. See `AiValue` for what each one expects.

***

### Type Alias: ApplicationAudience [#type-alias-applicationaudience]

```ts
type ApplicationAudience = "b2b" | "b2c" | "both";
```

***

### Type Alias: AssessmentAnswers [#type-alias-assessmentanswers]

```ts
type AssessmentAnswers = Record<string, string[]>;
```

Answers, keyed by section key.

***

### Type Alias: AssessmentFindingSeverity [#type-alias-assessmentfindingseverity]

```ts
type AssessmentFindingSeverity = "critical" | "warning" | "note";
```

***

### Type Alias: BillingInterval [#type-alias-billinginterval]

```ts
type BillingInterval = "monthly" | "annual" | "one-time";
```

How often a plan bills.

`"one-time"` is a plan that is paid for once and does not renew, so it has
no cadence to pair with: `planGroup` still groups it, but there is no
monthly/annual toggle to render for it. A pricing grid that assumes two
cadences will show it under whichever tab happens to be selected - check the
interval before offering the switch.

***

### Type Alias: BookingSource [#type-alias-bookingsource]

```ts
type BookingSource = "agent-call" | "portal" | "api";
```

Where a booking came from - usually the first question anyone asks about an
appointment they did not expect.

`api` is your integration; `portal` is the workspace's own screen or its
public booking link; `agent-call` is a voice agent, mid-call.

***

### Type Alias: BookingStatus [#type-alias-bookingstatus]

```ts
type BookingStatus = "hold" | "confirmed" | "cancelled" | "rescheduled" | "no_show";
```

***

### Type Alias: BrandCitationSource [#type-alias-brandcitationsource]

```ts
type BrandCitationSource = "website" | "stylesheet" | "render";
```

Where a brand claim was read. Its own union rather than `EnrichmentSource`,
which has no notion of a stylesheet or a rendered page.

***

### Type Alias: BrandExtractionResult [#type-alias-brandextractionresult]

```ts
type BrandExtractionResult = 
  | {
  status: "extracted";
  profile: BrandProfile;
  cached: boolean;
}
  | {
  status: "skipped";
  reason: BrandSkipReason;
};
```

#### Union Members

##### Type Literal

```ts
{
  status: "extracted";
  profile: BrandProfile;
  cached: boolean;
}
```

###### status

```ts
status: "extracted";
```

###### profile

```ts
profile: BrandProfile;
```

###### cached

```ts
cached: boolean;
```

Whether Connect served this from its platform-wide cache. The profile is the same either way.

***

##### Type Literal

```ts
{
  status: "skipped";
  reason: BrandSkipReason;
}
```

***

### Type Alias: BrandSkipReason [#type-alias-brandskipreason]

```ts
type BrandSkipReason = 
  | "no_domain"
  | "consumer_domain"
  | "blocked_domain"
  | "unreachable"
  | "no_signal"
  | "no_stylesheet";
```

Why there is nothing to report. A normal 200, not an error.

`consumer_domain` and `blocked_domain` will never succeed on retry.
`unreachable` may. `no_stylesheet` means the site answered but shipped no
CSS Connect could measure - a real outcome for a page built entirely in
inline styles.

***

### Type Alias: BusinessModel [#type-alias-businessmodel]

```ts
type BusinessModel = "product" | "service" | "hybrid";
```

Whether the company sells a thing, an engagement, or both.

***

### Type Alias: CallDirection [#type-alias-calldirection]

```ts
type CallDirection = "inbound" | "outbound";
```

Which way the call went.

***

### Type Alias: CallDisconnectReason [#type-alias-calldisconnectreason]

```ts
type CallDisconnectReason = 
  | "caller_hangup"
  | "agent_hangup"
  | "max_duration"
  | "transferred"
  | "error"
  | "abandoned";
```

How a call that actually ran came to an end. Widened at its use site for the same reason as `CallRejectReason`.

***

### Type Alias: CallHandler [#type-alias-callhandler]

```ts
type CallHandler = "agent" | "member";
```

Who was on Connect's end of a call: one of the workspace's agents, or a member calling from a browser.

***

### Type Alias: CallingAvailabilityStatus [#type-alias-callingavailabilitystatus]

```ts
type CallingAvailabilityStatus = 
  | "available"
  | "no_permission"
  | "no_number"
  | "number_pending"
  | "no_balance";
```

Why a member can or cannot call - see `calls.getCallingAvailability`.

- `no_permission` - they lack "Make calls". Don't offer calling.
- `no_number` - the workspace has no business number. `offer` says where to
  buy one (a "Business number" add-on on one of its subscriptions).
- `number_pending` - one was bought but Connect's pool was empty; an admin
  can retry from Connect's Calling page.
- `no_balance` - a number, but not a minute's worth of prepaid minutes.
- `available`.

***

### Type Alias: CallRecordingStatus [#type-alias-callrecordingstatus]

```ts
type CallRecordingStatus = "none" | "pending" | "stored" | "failed";
```

Where the recording has got to. `pending` is normal for a call that has just
ended - the carrier posts the audio afterwards - and `failed` is kept
distinct from `none` so "we never recorded this" is never read as "we
recorded it and lost it".

***

### Type Alias: CallRejectReason [#type-alias-callrejectreason]

```ts
type CallRejectReason = 
  | "unknown_number"
  | "channel_suspended"
  | "channel_released"
  | "channel_unassigned"
  | "no_routing"
  | "workspace_blocked"
  | "answer_unavailable"
  | "runtime_unconfigured";
```

Why a call was refused. Widened at the point of use with `(string & {})`,
the same way `ReadinessBlocker.code` is: Connect adds reasons as it learns
to tell failures apart, and a new one should reach you as a value to handle
rather than as a type error on a published package you cannot patch. Handle
the ones you branch on and treat the rest as "refused".

***

### Type Alias: CallSentiment [#type-alias-callsentiment]

```ts
type CallSentiment = "positive" | "neutral" | "negative";
```

How the person called came across, by the end of the call.

***

### Type Alias: CallStatus [#type-alias-callstatus]

```ts
type CallStatus = "dialing" | "answered" | "rejected" | "unanswered" | "completed";
```

`rejected` means Connect never connected it - see `rejectReason`.
`answered` is a call still in progress; `completed` is one that ran and
ended.

`dialing` and `unanswered` are an outbound call's: ringing, and nobody
picked up - see `unansweredReason`. A call your `<ConnectCallButton>`
placed passes through `dialing` on its way to one of the other two.

***

### Type Alias: CallTurnRole [#type-alias-callturnrole]

```ts
type CallTurnRole = "user" | "agent";
```

Who spoke.

***

### Type Alias: CallUnansweredReason [#type-alias-callunansweredreason]

```ts
type CallUnansweredReason = "no_answer" | "busy" | "failed" | "cancelled";
```

Why an outbound call reached nobody. Widened at its use site like `CallRejectReason`.

***

### Type Alias: ChannelKind [#type-alias-channelkind]

```ts
type ChannelKind = "voice" | "meeting" | "whatsapp";
```

What kind of conversation a channel carries. `voice` is the only kind
Connect stocks inventory for today, so `claim` refuses the other two:
`meeting` has no runtime yet, and customer `whatsapp` is provided by the
CosmoOps Messaging application rather than by Connect.

***

### Type Alias: ChannelStatus [#type-alias-channelstatus]

```ts
type ChannelStatus = "active" | "suspended" | "released";
```

`suspended` and `released` both stop the channel answering. The difference
is whose act it was: a Connect operator suspended it, or the number went
back to inventory.

***

### Type Alias: CompanyEnrichmentResult [#type-alias-companyenrichmentresult]

```ts
type CompanyEnrichmentResult = 
  | {
  status: "enriched";
  profile: EnrichedCompany;
  cached: boolean;
}
  | {
  status: "skipped";
  reason: EnrichmentSkipReason;
};
```

#### Union Members

##### Type Literal

```ts
{
  status: "enriched";
  profile: EnrichedCompany;
  cached: boolean;
}
```

###### status

```ts
status: "enriched";
```

###### profile

```ts
profile: EnrichedCompany;
```

###### cached

```ts
cached: boolean;
```

Whether Connect served this from its platform-wide cache. The profile is the same either way.

***

##### Type Literal

```ts
{
  status: "skipped";
  reason: EnrichmentSkipReason;
}
```

***

### Type Alias: CompanySizeBand [#type-alias-companysizeband]

```ts
type CompanySizeBand = "1-10" | "11-50" | "51-200" | "201-500" | "500+";
```

Coarse on purpose - a headcount read off a homepage is a claim, not a measurement.

***

### Type Alias: ConnectCallDirection [#type-alias-connectcalldirection]

```ts
type ConnectCallDirection = "inbound" | "outbound";
```

Which way a call went.

***

### Type Alias: ConnectCallPurpose [#type-alias-connectcallpurpose]

```ts
type ConnectCallPurpose = "service" | "transactional" | "promotional";
```

What a call is for, declared by the caller - Connect places it from a
workspace number allowed to carry it. In India the series decides: regular
numbers carry `service` and `transactional`, 140-series numbers carry
`promotional` only. Omit it and the call is placed as before and recorded
as undeclared.

***

### Type Alias: ConnectContactSource [#type-alias-connectcontactsource]

```ts
type ConnectContactSource = "call" | "manual" | "import" | "api" | "extraction";
```

How Connect came to know a contact.

`extraction` is the interesting one: an agent's skill captured them from
what was said, with nobody typing anything. `call` means the number was seen
on a call. `manual` and `import` are the two a human stands behind, and
Connect lets only those overwrite a field that already has a value.

***

### Type Alias: ConnectDataDisposition [#type-alias-connectdatadisposition]

```ts
type ConnectDataDisposition = "erase" | "anonymize" | "retain";
```

What becomes of one class of data when the workspace is purged.

***

### Type Alias: ConnectDoNotCallReason [#type-alias-connectdonotcallreason]

```ts
type ConnectDoNotCallReason = 
  | "asked_on_call"
  | "asked_in_writing"
  | "complaint"
  | "imported"
  | "other";
```

Why a person asked not to be called - the CRM's own vocabulary.

***

### Type Alias: ConnectFiduciaryRole [#type-alias-connectfiduciaryrole]

```ts
type ConnectFiduciaryRole = "connect_owned" | "processor" | "independent";
```

Who answers for your copy of the customer's data. See the deletion-policy guide.

***

### Type Alias: ConnectMetric [#type-alias-connectmetric]

```ts
type ConnectMetric = 
  | "usage.events"
  | "usage.active_users"
  | "api.requests"
  | "api.errors"
  | "billing.mrr_paise"
  | "billing.active_subscriptions"
  | "metering.addon_consumed";
```

The metrics Connect can report for your application.

Hand-written here rather than imported from Connect's own registry, like
every other wire type in this file, so the published package carries none of
Connect's internals. `conformance.test.ts` type-checks these against the
server DTOs, so drift fails the build in Connect's repo rather than in a
partner's integration.

***

### Type Alias: ConnectWebhookEventType [#type-alias-connectwebhookeventtype]

```ts
type ConnectWebhookEventType = 
  | "workspace.deletion_scheduled"
  | "workspace.deletion_cancelled"
  | "workspace.deleted"
  | "subscription.cancelled"
  | "subscription.activated"
  | "call.completed"
  | "call.missed"
  | "call.analyzed"
  | "booking.confirmed"
  | "booking.cancelled"
  | "booking.rescheduled"
  | "contact.created"
  | "contact.updated";
```

Every event Connect will POST to your webhook endpoint.

Additive only - Connect adds event types, never renames one - so a
`switch` that does not handle a future type is safe, and
`createConnectWebhookHandler` simply ignores anything you have not
registered a handler for.

***

### Type Alias: ContactSource [#type-alias-contactsource]

```ts
type ContactSource = "call" | "manual" | "import" | "api" | "extraction";
```

How a contact came to exist. `call` means a voice agent created it from an
inbound call; `manual` and `import` are the two a human stands behind.

***

### Type Alias: CreditEntryType [#type-alias-creditentrytype]

```ts
type CreditEntryType = "topup" | "consume" | "refund" | "adjustment" | "migration";
```

***

### Type Alias: CssFramework [#type-alias-cssframework]

```ts
type CssFramework = "tailwind" | "bootstrap" | "material" | "chakra" | "bulma";
```

A CSS framework detected by signature.

Worth checking first, because it tells you how to read everything else: a
Tailwind site's spacing scale is Tailwind's defaults rather than a
considered choice, and a palette full of `--bs-*` is Bootstrap's.

***

### Type Alias: EnrichmentSkipReason [#type-alias-enrichmentskipreason]

```ts
type EnrichmentSkipReason = 
  | "no_domain"
  | "consumer_domain"
  | "blocked_domain"
  | "unreachable"
  | "no_signal";
```

Why there is nothing to report. A normal outcome, not an error - most
records legitimately have nothing to enrich.

`consumer_domain` and `blocked_domain` will never succeed on retry.
`unreachable` and `no_signal` may, later.

***

### Type Alias: EnrichmentSource [#type-alias-enrichmentsource]

```ts
type EnrichmentSource = "domain" | "registry" | "website";
```

Where a value came from.

- `domain` - the input itself. `url` is `null`, because there is no page.
- `website` - a page on the company's own site, at `url`.
- `registry` - a statutory register. Reserved; nothing returns it yet.

***

### Type Alias: IntegrationConnectionStatus [#type-alias-integrationconnectionstatus]

```ts
type IntegrationConnectionStatus = 
  | "not_connected"
  | "pending"
  | "active"
  | "failed"
  | "disconnected"
  | "expired";
```

`not_connected` never travels over the wire as a persisted state - it's
what `ListWorkspaceIntegrationsUseCase` reports for a toolkit this
workspace has no `IntegrationConnection` record for at all.

***

### Type Alias: IntegrationProxyMethod [#type-alias-integrationproxymethod]

```ts
type IntegrationProxyMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
```

***

### Type Alias: LogLevel [#type-alias-loglevel]

```ts
type LogLevel = "debug" | "info" | "warn" | "error";
```

***

### Type Alias: LogoKind [#type-alias-logokind]

```ts
type LogoKind = "primary" | "mark" | "wordmark" | "favicon" | "social";
```

***

### Type Alias: LogoutScope [#type-alias-logoutscope]

```ts
type LogoutScope = "device" | "all";
```

***

### Type Alias: OfficialNameSource [#type-alias-officialnamesource]

```ts
type OfficialNameSource = "registry" | "website-legal";
```

Where an official name came from.

`website-legal` is a legal disclosure the company published itself - a
footer copyright line, or the entity named on its Terms page - matched by
code against a corporate suffix and cited. `registry` is a statutory
register and outranks it. Neither is a model's recall.

***

### Type Alias: OtpChannel [#type-alias-otpchannel]

```ts
type OtpChannel = "whatsapp" | "sms";
```

***

### Type Alias: PaletteRole [#type-alias-paletterole]

```ts
type PaletteRole = 
  | "primary"
  | "secondary"
  | "accent"
  | "background"
  | "surface"
  | "text"
  | "muted";
```

***

### Type Alias: PaletteSource [#type-alias-palettesource]

```ts
type PaletteSource = "css-var" | "computed" | "frequency";
```

How a colour was obtained, which is how much it is worth.

`css-var` is the company naming its own colour in its own tokens - the
strongest signal short of a brand manual. `computed` comes from a rendered
page. `frequency` is a ranked count of declarations and is the weakest.

***

### Type Alias: PresenceBand [#type-alias-presenceband]

```ts
type PresenceBand = "absent" | "emerging" | "established" | "strong";
```

Where a score sits, coarsely. Advisory - use it to pick wording, not to make a decision.

***

### Type Alias: PresenceCategory [#type-alias-presencecategory]

```ts
type PresenceCategory = "findability" | "identity" | "social" | "contactability" | "trust";
```

The five things a presence is scored on.

***

### Type Alias: PresenceCriterionStatus [#type-alias-presencecriterionstatus]

```ts
type PresenceCriterionStatus = "pass" | "partial" | "fail";
```

How a check came out.

`partial` is a real third state rather than a rounded failure: a site with
`og:title` but no `og:image` has done most of the work, and rendering that as
a failure is both wrong and discouraging.

***

### Type Alias: PresenceNetwork [#type-alias-presencenetwork]

```ts
type PresenceNetwork = 
  | "linkedin"
  | "instagram"
  | "facebook"
  | "youtube"
  | "google_business"
  | "crunchbase"
  | "x"
  | "tiktok"
  | "github"
  | "other";
```

The networks a scan recognises. `other` is a profile that matched no known pattern, kept rather than dropped.

***

### Type Alias: PresenceScanResult [#type-alias-presencescanresult]

```ts
type PresenceScanResult = 
  | {
  status: "scanned";
  scan: PresenceScan;
  cached: boolean;
}
  | {
  status: "skipped";
  reason: PresenceSkipReason;
};
```

#### Union Members

##### Type Literal

```ts
{
  status: "scanned";
  scan: PresenceScan;
  cached: boolean;
}
```

###### status

```ts
status: "scanned";
```

###### scan

```ts
scan: PresenceScan;
```

###### cached

```ts
cached: boolean;
```

Whether Connect served this from its platform-wide cache. The scan is the same either way.

***

##### Type Literal

```ts
{
  status: "skipped";
  reason: PresenceSkipReason;
}
```

***

### Type Alias: PresenceSkipReason [#type-alias-presenceskipreason]

```ts
type PresenceSkipReason = 
  | "no_domain"
  | "consumer_domain"
  | "blocked_domain"
  | "unreachable"
  | "provider_unavailable";
```

Why there is nothing to report. A normal 200, not an error.

The distinction that matters for what you show and whether you retry:
`unreachable` is about the **site** - the home page did not answer - and may
succeed later. `provider_unavailable` is about **Connect** - our scanner is
down or unconfigured - and is worth retrying without telling the user
anything about their own site. `no_domain`, `consumer_domain` and
`blocked_domain` never succeed on retry.

Note what is **not** here: a low score. Every check failing is a successful
scan and the most useful report this produces.

***

### Type Alias: ReadinessAssessmentResult [#type-alias-readinessassessmentresult]

```ts
type ReadinessAssessmentResult = 
  | {
  status: "assessed";
  report: ReadinessReport;
  cached: boolean;
}
  | {
  status: "skipped";
  reason: ReadinessSkipReason;
};
```

#### Union Members

##### Type Literal

```ts
{
  status: "assessed";
  report: ReadinessReport;
  cached: boolean;
}
```

###### status

```ts
status: "assessed";
```

###### report

```ts
report: ReadinessReport;
```

###### cached

```ts
cached: boolean;
```

Whether Connect served this from its platform-wide cache. The report is the same either way.

***

##### Type Literal

```ts
{
  status: "skipped";
  reason: ReadinessSkipReason;
}
```

***

### Type Alias: ReadinessBlockerCode [#type-alias-readinessblockercode]

```ts
type ReadinessBlockerCode = 
  | "subscription_missing"
  | "subscription_inactive"
  | "audience_mismatch"
  | "verification_required"
  | "verification_pending"
  | "verification_rejected"
  | "verification_expired"
  | "payment_method_missing"
  | "seat_limit_reached"
  | "workspace_not_found"
  | "onboarding_approval_pending"
  | "onboarding_approval_rejected"
  | "workspace_suspended"
  | "payment_retry_pending";
```

***

### Type Alias: ReadinessCheckKind [#type-alias-readinesscheckkind]

```ts
type ReadinessCheckKind = "core" | "bonus";
```

Whether a check can cost points or only earn them.

A `bonus` check raises the score when it passes and leaves it alone when it
does not - so a site is never marked down for missing a convention almost
nobody has adopted yet. Worth surfacing differently in a UI: a failed `core`
check is a gap, an absent `bonus` one is an opportunity.

***

### Type Alias: ReadinessCheckStatus [#type-alias-readinesscheckstatus]

```ts
type ReadinessCheckStatus = "pass" | "partial" | "fail" | "absent" | "error";
```

How a check came out.

`error` is the one to handle deliberately: it means Connect **could not
measure** this - a timeout, a refused connection - and it is excluded from
the score rather than counted as a failure. Render it as unknown, never as a
problem. `absent` ("you do not have this") and `fail` ("you have it and it is
broken") are likewise distinct and want different words.

***

### Type Alias: ReadinessDimension [#type-alias-readinessdimension]

```ts
type ReadinessDimension = 
  | "agent_interfaces"
  | "retrievability"
  | "machine_readable"
  | "crawl_consent"
  | "trust";
```

The five questions a readiness report answers.

***

### Type Alias: ReadinessGrade [#type-alias-readinessgrade]

```ts
type ReadinessGrade = "A" | "B" | "C" | "D" | "F";
```

***

### Type Alias: ReadinessSkipReason [#type-alias-readinessskipreason]

```ts
type ReadinessSkipReason = 
  | "no_domain"
  | "blocked_domain"
  | "consumer_domain"
  | "unreachable"
  | "provider_unavailable";
```

Why there is nothing to report. A normal 200, not an error.

The distinction that matters for what you show and whether you retry:
`unreachable` is about the **site** - it did not answer - and may succeed
later. `provider_unavailable` is about **Connect** - our assessment service
is down or unconfigured - and is worth retrying without telling the user
anything about their own site. `no_domain` and `blocked_domain` never succeed
on retry.

***

### Type Alias: SessionDeviceType [#type-alias-sessiondevicetype]

```ts
type SessionDeviceType = "desktop" | "mobile" | "tablet" | "unknown";
```

***

### Type Alias: StoreAggregationOperation [#type-alias-storeaggregationoperation]

```ts
type StoreAggregationOperation = "count" | "sum" | "avg";
```

***

### Type Alias: StoreCommitMode [#type-alias-storecommitmode]

```ts
type StoreCommitMode = "create" | "replace" | "merge" | "delete";
```

***

### Type Alias: StoreFieldTransform [#type-alias-storefieldtransform]

```ts
type StoreFieldTransform = 
  | {
  __transform: "increment";
  by: number;
}
  | {
  __transform: "serverTimestamp";
}
  | {
  __transform: "arrayUnion";
  values: unknown[];
}
  | {
  __transform: "arrayRemove";
  values: unknown[];
}
  | {
  __transform: "delete";
};
```

A value Connect computes at write time instead of storing verbatim.

Build them with the `increment`/`serverTimestamp`/`arrayUnion`/
`arrayRemove`/`deleteField` helpers rather than by hand - the sentinel shape
is an encoding, not an API.

`increment` is the one that cannot be expressed any other way: it is
commutative, so concurrent increments all land with no precondition and no
retry, which read-modify-write from your own process can never guarantee.

***

### Type Alias: StoreIndexDirection [#type-alias-storeindexdirection]

```ts
type StoreIndexDirection = "asc" | "desc" | "array-contains";
```

How one field sits in a composite index. `array-contains` is a position, not an order.

***

### Type Alias: StorePrecondition [#type-alias-storeprecondition]

```ts
type StorePrecondition = 
  | {
  exists: boolean;
}
  | {
  updateTime: string;
};
```

What must still be true of a document for a commit to land.

`{ updateTime }` is the `_updateTime` a read returned - the optimistic half
of read, decide, commit. A precondition that no longer holds fails the whole
commit with a 409 and writes nothing.

***

### Type Alias: StoreQueryOperator [#type-alias-storequeryoperator]

```ts
type StoreQueryOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "array-contains";
```

The comparison operators a store query may use - narrower than Firestore's own set.

***

### Type Alias: SubscriptionStatus [#type-alias-subscriptionstatus]

```ts
type SubscriptionStatus = "created" | "authenticated" | "active" | "paused" | "cancelled";
```

***

### Type Alias: TelephonyEdgeEvent [#type-alias-telephonyedgeevent]

```ts
type TelephonyEdgeEvent = "answer" | "hangup" | "recording" | "fallback";
```

Which carrier callback a relayed event is.

***

### Type Alias: ThemeMode [#type-alias-thememode]

```ts
type ThemeMode = "light" | "dark" | "system";
```

Host-controlled - the SDK has no theme provider of its own. See `ConnectUserMenuProps.theme`.

***

### Type Alias: ThumbnailSize [#type-alias-thumbnailsize]

```ts
type ThumbnailSize = "small" | "medium" | "large";
```

***

### Type Alias: TypefaceRole [#type-alias-typefacerole]

```ts
type TypefaceRole = "display" | "heading" | "body" | "mono";
```

***

### Type Alias: VisitorField [#type-alias-visitorfield]

```ts
type VisitorField = "message" | "email" | "code" | "name";
```

What the widget should ask for next, decided by Connect.

`"message"` is the ordinary composer. The others mean render that field's own
input instead - and the reason this is a server decision rather than something
the widget infers from the assistant's sentence is the phone: an email asked
for in prose gets typed into a general text box with autocorrect on, where
`type="email" inputmode="email" autocomplete="email"` brings up a keyboard
with an `@` on it. A six-digit code wants `autocomplete="one-time-code"`,
which is what lets iOS offer the code straight from the notification.

***

### Type Alias: VisitorKnowledgeIndexState [#type-alias-visitorknowledgeindexstate]

```ts
type VisitorKnowledgeIndexState = "inline" | "indexing" | "indexed" | "failed";
```

How a published item reaches the model.

`inline` is the **normal and better** state for a small knowledge base, not a
degraded one: the model is shown every published item rather than a top-k
slice, with no chunk boundaries and no retrieval round trip. The other three
only occur once a knowledge base has outgrown the prompt.

***

### Type Alias: VisitorKnowledgeSource [#type-alias-visitorknowledgesource]

```ts
type VisitorKnowledgeSource = "website" | "authored" | "upload";
```

Where a knowledge item's text came from.

***

### Type Alias: WhatsAppTemplateReviewStatus [#type-alias-whatsapptemplatereviewstatus]

```ts
type WhatsAppTemplateReviewStatus = "DRAFT" | "PENDING" | "APPROVED" | "REJECTED" | "PAUSED" | "DISABLED";
```

Meta's review state for a template. Only `APPROVED` sends.

***

### Type Alias: WirePlanFeature [#type-alias-wireplanfeature]

```ts
type WirePlanFeature = string | PlanFeature;
```

What `features` can look like coming off the wire.

Connect used to send bare keys and now sends `{ key, name, description }`.
A published SDK talks to whatever version the partner's Connect instance is
running and has no say in when that moves - so both shapes are real, and a
client that assumes the newer one renders an empty feature list against
every instance that has not upgraded yet.

***

### Type Alias: WorkspaceAppCategory [#type-alias-workspaceappcategory]

```ts
type WorkspaceAppCategory = 
  | "communication"
  | "storage"
  | "analytics"
  | "security"
  | "billing"
  | "productivity"
  | "sales"
  | "operations"
  | "infrastructure"
  | "other";
```

Connect's closed set of catalog categories - `WorkspaceApp.category`.

***

### Type Alias: WorkspaceType [#type-alias-workspacetype]

```ts
type WorkspaceType = "individual" | "business";
```
## Variables

### Variable: NO\_PLAN\_LIMIT [#variable-no_plan_limit]

```ts
const NO_PLAN_LIMIT: PlanLimit;
```

What a key the plan carries no answer for reads as - see
`SubscribedPlan.limits`, which is sparse.

`"none"` rather than `"unlimited"`: a plan that was never asked about a
limit has not sold it, and defaulting the other way would hand an unbounded
allowance to every tier that predates the question.
## Functions

### Function: arrayRemove() [#function-arrayremove]

```ts
function arrayRemove(...values: unknown[]): StoreFieldTransform;
```

Removes every occurrence of each value.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| ...`values` | `unknown`[] |

#### Returns

[`StoreFieldTransform`](#type-alias-storefieldtransform)

***

### Function: arrayUnion() [#function-arrayunion]

```ts
function arrayUnion(...values: unknown[]): StoreFieldTransform;
```

Adds the values not already in the array. Order is not preserved.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| ...`values` | `unknown`[] |

#### Returns

[`StoreFieldTransform`](#type-alias-storefieldtransform)

***

### Function: blockingBlockers() [#function-blockingblockers]

```ts
function blockingBlockers(readiness: ReadinessResponse): ReadinessBlocker[];
```

The blockers that actually deny access, in the order to show them.

Advisory ones (no saved payment method mid-subscription, say) are worth
surfacing but are not a reason to lock anyone out today - filtering them here
keeps that judgement in one place rather than in every call site.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `readiness` | [`ReadinessResponse`](#interface-readinessresponse) |

#### Returns

[`ReadinessBlocker`](#interface-readinessblocker)[]

***

### Function: deleteField() [#function-deletefield]

```ts
function deleteField(): StoreFieldTransform;
```

Removes the field entirely.

`merge` only - a `replace` already drops every field it does not list, so
asking it to delete one is a contradiction rather than a narrowing.

#### Returns

[`StoreFieldTransform`](#type-alias-storefieldtransform)

***

### Function: describeQueryRejection() [#function-describequeryrejection]

```ts
function describeQueryRejection(collection: StoreCollectionDeclaration, query: {
  filters?: readonly StoreQueryFilter[];
  orderBy?: StoreOrderBy | null;
}): string | null;
```

Why a collection cannot serve a query, in words a caller can act on - or
`null` when it can.

Runs the same two rules Connect runs, in the same order: every field must be
declared queryable, and the shape must be inside an automatic single-field
index or covered by a declared composite one. Checking here rather than
letting the call fail is not about saving a round trip - it is so the refusal
can name the index to declare while the caller still has the query in hand.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `collection` | [`StoreCollectionDeclaration`](#interface-storecollectiondeclaration) |
| `query` | \{ `filters?`: readonly [`StoreQueryFilter`](#interface-storequeryfilter)[]; `orderBy?`: [`StoreOrderBy`](#interface-storeorderby) \| `null`; \} |
| `query.filters?` | readonly [`StoreQueryFilter`](#interface-storequeryfilter)[] |
| `query.orderBy?` | [`StoreOrderBy`](#interface-storeorderby) \| `null` |

#### Returns

`string` \| `null`

***

### Function: documentExists() [#function-documentexists]

```ts
function documentExists(exists?: boolean): StorePrecondition;
```

"Only if this document exists" / "only if it does not."

#### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `exists` | `boolean` | `true` |

#### Returns

[`StorePrecondition`](#type-alias-storeprecondition)

***

### Function: increment() [#function-increment]

```ts
function increment(by: number): StoreFieldTransform;
```

Add to a number, atomically.

**The one operation you genuinely cannot do yourself.** Increments are
commutative, so two concurrent `increment(500)` calls both land - where
read-add-write from your own process loses one of them however carefully it
retries, because the read and the write are two round trips with a gap in
between. Use this for balances, counters and tallies, and reach for a
precondition only when the new value depends on the old one in a way
addition cannot express.

Negative to subtract; there is no separate `decrement`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `by` | `number` |

#### Returns

[`StoreFieldTransform`](#type-alias-storefieldtransform)

***

### Function: isConnectError() [#function-isconnecterror]

```ts
function isConnectError(error: unknown): error is ConnectError;
```

`@cosmoops/connect-sdk` - the official client for the CosmoOps Connect
**application-partner API** (`/api/v1/application/*`).

This root entry is the isomorphic surface: wire types, the error class, and
pure helpers. It pulls in neither Node-only code nor React, so it is safe to
import from anywhere.

- `@cosmoops/connect-sdk/server` - `ConnectClient` and `createConnectHandler`.
  Holds the API key. Never import this from browser code.
- `@cosmoops/connect-sdk/client` - React components and hooks. Talks to your
  own origin only.
- `@cosmoops/connect-sdk/mcp` - your store's declaration, derived into MCP
  tools. Holds the API key; never import this from browser code.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `error` | `unknown` |

#### Returns

`error is ConnectError`

***

### Function: normalizePlanFeatures() [#function-normalizeplanfeatures]

```ts
function normalizePlanFeatures(features: 
  | readonly WirePlanFeature[]
  | undefined): PlanFeature[];
```

Coerces either wire shape into `PlanFeature[]`.

A bare key becomes `{ key, name: key, description: "" }` - the same
fallback the server applies for a plan older than its application's feature
catalog, so an un-upgraded Connect renders exactly what it always did
rather than a column of blanks.

Applied inside `BillingResource.listPlans`, so anything reading a plan
through the SDK or its bridge already has the normalised shape and no
component needs to know this ambiguity exists. Exported for a partner who
hand-rolled a bridge instead of using `createConnectHandler`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `features` | \| readonly [`WirePlanFeature`](#type-alias-wireplanfeature)[] \| `undefined` |

#### Returns

[`PlanFeature`](#interface-planfeature)[]

***

### Function: planLimitAllows() [#function-planlimitallows]

```ts
function planLimitAllows(
   limit: PlanLimit, 
   currentCount: number, 
   additional?: number): boolean;
```

Whether `additional` more of the resource fit under this limit, given how
many the subscriber already has. The comparison, written once.

Every way of writing it at a call site is wrong in a different direction:
`count < limit.value` is off by one as soon as you add more than one row,
`if (!limit.value)` refuses the unlimited tier, and
`limit.value ?? Infinity` grants unlimited to a plan that allows none. This
is the same implementation Connect's own server uses to validate a plan.

```ts
const limit = await connect.entitlements.getLimit(workspaceId, "sites");
if (!planLimitAllows(limit, await countSites(workspaceId))) {
  throw new Error("Your plan's site limit is reached.");
}
```

#### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `limit` | [`PlanLimit`](#interface-planlimit) | `undefined` |
| `currentCount` | `number` | `undefined` |
| `additional` | `number` | `1` |

#### Returns

`boolean`

***

### Function: planLimitFor() [#function-planlimitfor]

```ts
function planLimitFor(limits: 
  | Readonly<Record<string, PlanLimit>>
  | undefined, key: string): PlanLimit;
```

One plan's answer for one limit key, with the sparse-map default applied.

Indexing `plan.limits` directly is what makes a missing key `undefined`,
which then reads as unlimited through the first `?.` somebody writes. This
returns a `PlanLimit` either way.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `limits` | \| `Readonly`\<`Record`\<`string`, [`PlanLimit`](#interface-planlimit)\>\> \| `undefined` |
| `key` | `string` |

#### Returns

[`PlanLimit`](#interface-planlimit)

***

### Function: serverTimestamp() [#function-servertimestamp]

```ts
function serverTimestamp(): StoreFieldTransform;
```

Connect's own write time, rather than a clock reading from your process.

#### Returns

[`StoreFieldTransform`](#type-alias-storefieldtransform)

***

### Function: storeIndexCovers() [#function-storeindexcovers]

```ts
function storeIndexCovers(declared: readonly {
  fields: StoreIndexField[];
}[], required: readonly StoreIndexField[]): boolean;
```

Whether one of `declared` can serve a query needing exactly `required`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `declared` | readonly \{ `fields`: [`StoreIndexField`](#interface-storeindexfield)[]; \}[] |
| `required` | readonly [`StoreIndexField`](#interface-storeindexfield)[] |

#### Returns

`boolean`

***

### Function: storeIndexFor() [#function-storeindexfor]

```ts
function storeIndexFor(query: {
  filters?: readonly StoreQueryFilter[];
  orderBy?: StoreOrderBy | null;
}): StoreIndexField[] | null;
```

The composite index a query shape needs, or `null` when Firestore's
automatic single-field indexes already serve it.

Use it to generate your Store tab declarations from the queries your code
actually makes, rather than transcribing them by hand and discovering the
gap in production:

```ts
const needed = MY_QUERY_SHAPES.map(storeIndexFor).filter(Boolean);
```

**Field order is Firestore's, not yours.** Equality fields first (sorted, so
the order you passed them in cannot produce two different declarations for
one query), then the `array-contains` field, then the field the query orders
by. Declaring the same shape twice in two orders builds two indexes, one of
which is dead weight on every document write.

Connect runs the same derivation server-side and is the authority: a query
with no matching declared index is refused with the index it needs spelled
out, so a disagreement here surfaces on the first call rather than silently.

Throws for a query Firestore cannot serve at all - two `array-contains`
filters, ranges over two fields, or a range ordered by a different field.

A **range bounded on both sides** over one field needs no declaration: two
filters, one field, and the automatic single-field index serves it. Ask this
function with the filters you actually send rather than with one of the two
bounds - the answer is the same, and modelling the range as a single `>=`
is how a schema comes to declare a shape its own runtime does not make.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `query` | \{ `filters?`: readonly [`StoreQueryFilter`](#interface-storequeryfilter)[]; `orderBy?`: [`StoreOrderBy`](#interface-storeorderby) \| `null`; \} |
| `query.filters?` | readonly [`StoreQueryFilter`](#interface-storequeryfilter)[] |
| `query.orderBy?` | [`StoreOrderBy`](#interface-storeorderby) \| `null` |

#### Returns

[`StoreIndexField`](#interface-storeindexfield)[] \| `null`

***

### Function: storeIndexSignature() [#function-storeindexsignature]

```ts
function storeIndexSignature(fields: readonly StoreIndexField[]): string;
```

An index as one comparable string - `"status:asc,createdAt:desc"`.

Deduplicate a generated list on this before declaring it: a dozen query
shapes across a screen usually collapse to two or three distinct indexes.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `fields` | readonly [`StoreIndexField`](#interface-storeindexfield)[] |

#### Returns

`string`

***

### Function: unchangedSince() [#function-unchangedsince]

```ts
function unchangedSince(document: StoreDocument): StorePrecondition;
```

"Only if this document has not changed since I read it."

Pass the document a read returned, and the precondition is built from its
`_updateTime`. Taking the document rather than the timestamp is deliberate:
a caller who has to reach for `doc.data._updateTime` themselves is one typo
away from a precondition that silently does nothing.

Throws rather than degrading when the document carries no `_updateTime` -
that only happens for a document you built yourself rather than read, and a
precondition that quietly became "no precondition" is worse than a crash at
the call site.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `document` | [`StoreDocument`](#interface-storedocument) |

#### Returns

[`StorePrecondition`](#type-alias-storeprecondition)