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

# Proxy helpers reference

> Helpers for fetching the auth, API, and platform workers.

The scaffolded `worker.ts` already uses these for every cross-worker call. They prefer service bindings (set up automatically on deploy) and fall back to HTTPS URLs in local dev.

```ts theme={null}
import {
  apiWorkerFetch, platformWorkerFetch, authWorkerFetch,
  resolveApiTransport,
  createScopedR2Handler,
} from 'deepspace/worker'

import type {
  ApiWorkerEnv, PlatformWorkerEnv, AuthWorkerEnv,
  ScopedR2Config, ScopedR2Handler, ScopedR2Auth, ScopeContext, PrefixResult,
} from 'deepspace/worker'
```

<Warning>
  **Do not replace these with raw `c.env.X.fetch(...)`.** `wrangler dev` doesn't surface service bindings cross-process for SDK apps - the binding is `undefined` locally and the raw fetch silently fails. The helpers paper over the dev/prod mismatch.
</Warning>

## `apiWorkerFetch(env, path, init?)`

Fetch the api-worker (Stripe, integrations, profiles, OAuth, usage tracking).

```ts theme={null}
function apiWorkerFetch(
  env: ApiWorkerEnv,
  path: string,
  init?: RequestInit,
): Promise<Response>

interface ApiWorkerEnv {
  API_WORKER?: Fetcher       // service binding (preferred in prod)
  API_WORKER_URL?: string    // HTTPS fallback (used in dev)
}
```

Prefers the `API_WORKER` service binding; falls back to `API_WORKER_URL`. Throws an actionable Error if neither is configured.

## `platformWorkerFetch(env, pathOrRequest, init?)`

Fetch the platform-worker (cross-app DOs, R2 file gateway, WebSocket multiplexing).

```ts theme={null}
function platformWorkerFetch(
  env: PlatformWorkerEnv,
  pathOrRequest: string | Request,
  init?: RequestInit,
): Promise<Response>

interface PlatformWorkerEnv {
  PLATFORM_WORKER?: Fetcher
  PLATFORM_WORKER_URL?: string
}
```

Accepts either a path string or a full `Request`, so you can hand off `c.req.raw` derivatives with method/headers/body intact:

```ts theme={null}
app.get('/ws/:roomId', async (c) => {
  const roomId = c.req.param('roomId')
  if (/^(workspace|dir|conv):/.test(roomId)) {
    return platformWorkerFetch(c.env, c.req.raw)
  }
  return wsRoute((env) => env.RECORD_ROOMS)(c)
})
```

`wsRoute` in the example above is **not** an SDK export - it's a small helper defined locally in the scaffolded `worker.ts` (around line 345 of `templates/starter/worker.ts`). It owns the JWT verification + URL rewriting boilerplate for WebSocket routes. Apps are free to edit or replace it.

For PNG capture against an `*.app.space` / `*.deep.space` URL, prefer the higher-level [`captureScreenshot`](/sdk-reference/worker/bindings#shared-browser-rendering-capturescreenshot) wrapper instead of calling `platformWorkerFetch` against `/internal/screenshot` yourself - it owns the HMAC headers and the `null`-on-failure contract.

## `authWorkerFetch(env, path, init?)`

Fetch the auth-worker (Better Auth, JWT issuance, OAuth flows).

```ts theme={null}
function authWorkerFetch(
  env: AuthWorkerEnv,
  path: string,
  init?: RequestInit,
): Promise<Response>

interface AuthWorkerEnv {
  /**
   * HTTPS URL for the auth-worker. Optional on the type so apps can declare
   * a partial env shape during local bring-up, but the function throws if it's
   * unset at call time.
   */
  AUTH_WORKER_URL?: string
}
```

URL-only by design - the auth-worker has no service binding, which keeps `Set-Cookie` headers verbatim over plain HTTPS. (Service bindings strip some headers in transit.) Throws if `AUTH_WORKER_URL` is missing.

## `resolveApiTransport(env)`

Exported for the AI helper, which needs the URL form to rewrite an internal `https://api-worker.internal` placeholder before calling provider SDKs. Most apps don't need this.

```ts theme={null}
function resolveApiTransport(env: ApiWorkerEnv):
  | { kind: 'binding'; fetcher: Fetcher }
  | { kind: 'url'; baseUrl: string }
```

## Production note

Cross-worker calls over plain `*.workers.dev` URLs return Cloudflare error 1042 in production. The service binding is the only working transport for deployed apps; the URL fallback is a dev-only convenience the CLI writes into `.dev.vars`.

If a deployed app needs `apiWorkerFetch` or `platformWorkerFetch`, the corresponding `[[services]]` binding must be in `wrangler.toml`:

```toml theme={null}
[[services]]
binding = "API_WORKER"
service = "deepspace-api"

[[services]]
binding = "PLATFORM_WORKER"
service = "deepspace-platform"
```

## R2 helpers

### `createScopedR2Handler(config)`

Factory that builds a route handler for prefix-scoped R2 reads/writes. The scaffold uses this implicitly via the `useR2Files` client hook - most apps don't call it directly.

```ts theme={null}
function createScopedR2Handler(config: ScopedR2Config): ScopedR2Handler

interface ScopedR2Config {
  /**
   * Resolve the R2 key prefix for the given scope.
   * Called with the `?scope=` query param value (default: 'self').
   */
  resolvePrefix(scope: string, ctx: ScopeContext): PrefixResult
  /** Require a non-null userId for upload and delete. Default: true. */
  requireAuthForMutations?: boolean
}

interface ScopeContext {
  userId: string | null
  url: URL
}

type PrefixResult =
  | { prefix: string; error?: undefined }
  | { prefix?: undefined; error: string }

type ScopedR2Handler = (
  request: Request,
  url: URL,
  bucket: R2Bucket,
  auth: { userId: string | null },
) => Promise<Response>
```

Security guarantees: download/delete keys are validated against the resolved prefix, path traversal (`..`) is rejected at the entry point, and mutations require a non-null userId by default.

## See also

* [Architecture concepts](/concepts/architecture#cross-app-shared-scopes) - when to add the platform binding
* [File uploads guide](/guides/file-uploads)
