Skip to main content
Docs
SDK reference

Integrations reference

The `integration` client and OAuth helpers.

The integration object fronts 215+ third-party API endpoints through the platform's signed proxy. For discovery, billing, and the full workflow see the external APIs guide; for the CLI catalog, see deepspace integrations.

import { integration } from 'deepspace'
import type { IntegrationResponse, RequestOptions } from 'deepspace'

integration#

The integration client exposes four HTTP verbs. All return a typed envelope.

const integration: {
  get<T>:    (endpoint: string, params?: Record<string, string | number | boolean | null | undefined>, options?: RequestOptions) => Promise<IntegrationResponse<T>>
  post<T>:   (endpoint: string, data?: unknown, options?: RequestOptions) => Promise<IntegrationResponse<T>>
  put<T>:    (endpoint: string, data?: unknown, options?: RequestOptions) => Promise<IntegrationResponse<T>>
  delete<T>: (endpoint: string, data?: unknown, options?: RequestOptions) => Promise<IntegrationResponse<T>>
}
get takes a query-parameter object as its middle argument; the others take an optional JSON body. delete also accepts a body - the api-worker dispatches DELETE with a JSON payload when one is provided.

Endpoint names are two segments: <integration>/<endpoint> (e.g. openai/chat-completion).

The most common verb. Use for actions, completions, lookups, and anything with a request body.

const result = await integration.post('openai/chat-completion', {
  model: 'claude-sonnet-4-6',
  messages: [{ role: 'user', content: 'Hello' }],
})

if (result.success) {
  console.log(result.data)
} else {
  console.error(result.error, result.issues)
}

For idempotent reads. Pass query parameters as the second argument - they're serialised onto the URL automatically (null/undefined values are skipped).

const result = await integration.get('finnhub/market-news', {
  category: 'crypto',
  minId: 100,
})

For idempotent updates against REST-style endpoints.

const result = await integration.put('notion/update-page', {
  pageId: 'abc123',
  properties: { Status: 'Done' },
})

For destructive operations. The platform proxy is signed and rate-limited per integration; OAuth scopes apply if the integration is OAuth-backed.

const result = await integration.delete('notion/delete-block', {
  blockId: 'xyz789',
})

IntegrationResponse<T>#

type IntegrationResponse<T> =
  | { success: true; data: T }
  | { success: false; error: string; issues?: ValidationIssue[] }

type ValidationIssue = {
  path?: string[]
  message: string
  code?: string
}
issues appears when the api-worker's Zod validator rejects the body shape. Read it instead of guessing field names - or run deepspace integrations info <endpoint> to print the schema before you call.

RequestOptions#

type RequestOptions = {
  headers?: Record<string, string>
  timeoutMs?: number   // default 120000 (120s)
}
const r = await integration.post('exa/search', body, {
  timeoutMs: 30_000,
  headers: { 'X-Custom': 'value' },
})

Cross-app platform context#

These exports support a small set of cross-app surfaces (inbox, platform fetch). The scaffold does not include <PlatformProvider> - wrap manually if you need this surface.

import { PlatformProvider, usePlatform, useInbox, usePlatformWS } from 'deepspace'

<PlatformProvider>{/* tree */}</PlatformProvider>

usePlatform()#

function usePlatform(): {
  platformFetch: (path: string, init?: RequestInit) => Promise<Response>
  inbox: InboxEntry[]
  /** Activate the inbox WebSocket. Returns an unsubscribe function. */
  subscribeInbox: () => () => void
}
platformFetch prepends /platform and adds the auth header automatically. Throws if no <PlatformProvider> is mounted. subscribeInbox is the primitive useInbox() calls under the hood - apps rarely call it directly.

useInbox()#

function useInbox(): InboxEntry[]

type InboxEntry = {
  conversationId: string
  scope: { type: string; participants?: string[]; appId?: string; contentRef?: string; ticketNumber?: string }
  displayName: string
  muted: boolean
  joinedAt: string
  lastMessageAt: string | null
  lastMessagePreview: string | null
  lastMessageAuthor: string | null
  unreadCount: number
}

The inbox WebSocket activates only when at least one component subscribes via useInbox().

usePlatformWS<S>(options)#

Generic platform WebSocket subscription for custom platform-side streams. S is the state shape; the constraint requires a status: ConnectionStatus field so the hook can drive reconnect UI.

function usePlatformWS<S extends { status: ConnectionStatus }>(
  options: PlatformWSOptions<S>,
): PlatformWSResult<S>

type PlatformWSOptions<S> = {
  /** DO path segment - e.g. 'orders' for /platform/ws/orders/{scopeId} */
  path: string
  /** Scope ID - e.g. 'app:demo-corp'. Pass undefined to skip the connection. */
  scopeId: string | undefined
  initialState: S
  onMessage: (msg: Record<string, unknown>, prev: S) => S
}

type PlatformWSResult<S> = {
  state: S
  send: (msg: Record<string, unknown>) => void
}

OAuth endpoints#

For OAuth-backed integrations (currently Google), these REST endpoints manage per-user connections. Call via fetch with the session token.

Endpoint Method Purpose
/api/integrations/status GET Returns connection flags for all OAuth providers
/api/integrations/oauth/:provider/connect GET Initiates OAuth flow (browser navigation)
/api/integrations/oauth/:provider/disconnect DELETE Revokes the current user's stored tokens
const r = await fetch('/api/integrations/status', {
  headers: { Authorization: `Bearer ${await getAuthToken()}` },
})
const { google } = await r.json()
// { connected: boolean, scopes: string[] }

requiresOAuth response#

When a user calls an OAuth endpoint without a connected account:

{ success: false, error: 'requiresOAuth', provider: 'google', connectUrl: '...' }

Redirect the user to connectUrl (or call /api/integrations/oauth/google/connect directly), then retry the original call.

See also#