Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.deep.space/llms.txt

Use this file to discover all available pages before exploring further.

DeepSpace fronts 215+ third-party API endpoints - LLMs (Claude, GPT, Cerebras), search (Exa, Tavily), media (LiveKit, Resend), finance (Finnhub, Alpha Vantage), social (Discord, Slack), Google Workspace, weather, and more - through a single signed proxy. You don’t store API keys, configure webhooks, or build per-vendor SDKs. You call integration.post(...), the platform handles billing, rate-limiting, and provider routing.

Calling an integration

import { integration } from 'deepspace'

const result = await integration.post('openweathermap/geocoding', { q: 'Brooklyn' })

if (result.success) {
  console.log(result.data)
} else {
  console.error(result.error)
}
Endpoint names are always two segments: <integration>/<endpoint>. The response is a discriminated envelope:
type IntegrationResponse<T> =
  | { success: true; data: T }
  | { success: false; error: string; issues?: ValidationIssue[] }
When the api-worker’s Zod validator rejects a body, issues carries field-level errors:
if (!result.success && result.issues) {
  for (const issue of result.issues) {
    console.log(issue.path, issue.message)
  }
}

Discover endpoints - deepspace integrations

The CLI exposes the full catalog and lets you invoke endpoints interactively. Discovery is free; calls are billed - list and info work without authentication, so you can scope integration work before deciding whether to log in.
No login required.
# Human-readable
npx deepspace integrations list

# Machine-readable (JSON)
npx deepspace integrations list --json
Returns all 215+ endpoints grouped by integration with one-line descriptions.
See the CLI reference for the full flag list.

Billing - developer vs user

Every integration has a billing setting in src/integrations.ts:
// src/integrations.ts
export const integrations: Record<string, { billing: 'developer' | 'user' }> = {
  google: { billing: 'user' },        // already in scaffold (OAuth requires user-pays)
  openai: { billing: 'developer' },   // owner pays
  exa: { billing: 'developer' },
}
SettingWho paysAnonymous callers
'developer' (default)The app owner via APP_OWNER_JWTAllowed
'user'The signed-in callerBlocked with 401
Auth-gate any UI that calls integration.post(...) for 'developer'-billed endpoints. The api-worker accepts anonymous callers for developer-billed integrations, so a public endpoint silently bills the owner for every visitor (or bot) hit. Wrap calling components in useAuth().isSignedIn.

Response shapes

data shape varies by endpoint. Common patterns:
// List endpoint
const r = await integration.post('exa/search', { query: 'climate change papers' })
if (r.success) {
  for (const result of r.data.results) {
    console.log(result.title, result.url)
  }
}

// Detail endpoint
const r = await integration.post('finnhub/stock-quote', { symbol: 'AAPL' })
if (r.success) {
  console.log(r.data.c, r.data.h, r.data.l)  // current, high, low
}
Empty results are not errors. Some endpoints return success: true with empty data when the upstream has no matches. Check for empty state explicitly. For example, finnhub/stock-price returns an all-zero quote for an invalid symbol - the call “succeeds” but the data is meaningless.

Calling from your worker

Inside server actions and cron tasks, use tools.integration or ctx.integrations.call:
// Server action
export const summarizeDay: ActionHandler<Env> = async ({ tools }) => {
  const r = await tools.integration('openai/chat-completion', {
    model: 'gpt-5.4-mini',
    messages: [{ role: 'user', content: 'Summarize today\'s activity' }],
  })
  // ...
}

// Cron task
export async function runTask(name: string, env: Env) {
  const ctx = buildCronContext(env, env.OWNER_USER_ID, `app:${env.APP_NAME}`)
  const r = await ctx.integrations.call('resend/send-email', { to, subject, text })
}
Both routes go through the api-worker proxy. Billing follows src/integrations.ts - when called from a server action, 'user' integrations bill the caller and 'developer' ones bill the owner.

Request options

The post, get, put, and delete methods accept an options object:
const r = await integration.post('exa/search', body, {
  timeoutMs: 30_000,                          // default 120s
  headers: { 'X-Custom': 'value' },
})

OAuth integrations

A handful of integrations require per-user OAuth (Google Workspace, Gmail, Calendar, Drive). These are always billed as 'user'. The scaffold ships Google with billing: 'user'. When a user calls an OAuth endpoint without a connected account, the response includes a requiresOAuth flag:
const r = await integration.post('google/gmail-send', { to, subject, body })

if (!r.success && r.error === 'requiresOAuth') {
  // Redirect to the OAuth connect URL
  window.location.href = `/api/integrations/oauth/google/connect?return=${encodeURIComponent(location.pathname)}`
}
After connecting, retry the original call. Disconnect via:
await fetch('/api/integrations/oauth/google/disconnect', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${await getAuthToken()}` },
})
Check connection status:
const r = await fetch('/api/integrations/status', {
  headers: { Authorization: `Bearer ${await getAuthToken()}` },
})
const { google } = await r.json()
// { connected: boolean, scopes: string[] }

Testing integrations

Integration calls hit real third-party services and cost real money. Keep integration assertions minimal:
  • One integration.post(...) per endpoint per test run, not a matrix
  • Never put integration calls inside for loops or retry-until-success polls
  • Skip 'user'-billed endpoint calls in api.spec.ts - test accounts have no credits and will 402
For the integration call itself, assert the envelope shape - not the upstream provider’s exact response:
test('weather lookup returns coords', async ({ request }) => {
  const token = await signInAndGetToken(request)
  const r = await request.post('/api/integrations/openweathermap/geocoding', {
    headers: { Authorization: `Bearer ${token}` },
    data: { q: 'Brooklyn' },
  })
  const body = await r.json()
  expect(body.success).toBe(true)
  expect(body.data[0]).toMatchObject({ lat: expect.any(Number), lon: expect.any(Number) })
})

Tips

  • Run info before guessing a body shape. The Zod schemas the api-worker validates against are the source of truth - npx deepspace integrations info <endpoint> prints them with an example body.
  • Use tools.integration from server actions for owner-pays endpoints. Keeps the JWT scope correct and centralizes routing.
  • For LLM streaming, use the AI chat pipeline (see AI chat) rather than integration.post. The proxy returns the full response; the AI helpers stream it.

Next steps