Client package

@clay/client is the workspace-scoped, Fern-generated TypeScript SDK that the mobile, landing, and backend-internal apps use to call the Clay API. It is committed to the repository under packages/client/src/generated and must be regenerated whenever the OpenAPI contract changes.

Purpose

The package exists to keep every client in lock-step with the backend HTTP surface. A single source of truth — apps/backend/openapi/openapi.yaml — feeds the Fern generator, which emits a typed SDK with endpoints, environments, errors, and a passthrough fetch escape hatch.
ConcernOwned by @clay/client
Endpoint shapesTyped resource clients (currently health)
EnvironmentsClayApiEnvironment.Default = "https://api.clay.local"
Error modelClayApiError, ClayApiTimeoutError, handleNonStatusCodeError
Transport plumbingNative fetch, retries, timeouts, request logging
Escape hatchClayApiClient.fetch(...) for endpoints the SDK does not yet expose

Package layout

packages/client/
├── fern/
│   ├── fern.config.json     # organization "clay", CLI version 5.65.3
│   └── generators.yml       # reads OpenAPI, emits TS SDK to ../src/generated
├── src/
│   └── generated/           # DO NOT hand-edit — Fern output
│       ├── Client.ts        # ClayApiClient
│       ├── environments.ts  # ClayApiEnvironment
│       ├── errors/          # ClayApiError, ClayApiTimeoutError, ...
│       └── index.ts         # package entry
├── package.json             # @clay/client, scripts: generate, check, typecheck, clean
└── tsconfig.json
Treat packages/client/src/generated as build output. Edit the OpenAPI source instead; the next pnpm generate rewrites this folder.

Generation pipeline

The pipeline runs from packages/client. Fern reads the OpenAPI document emitted by the backend, runs the TypeScript generator, and writes the result into src/generated.
1

Author the OpenAPI contract

Hono routes in @clay/backend describe their shape with @hono/zod-openapi. The backend exports the live OpenAPI document to apps/backend/openapi/openapi.yaml.
2

Check the contract

From packages/client, run pnpm check to invoke fern check against the OpenAPI document. Catch typos, missing paths, or invalid schemas before generation.
3

Regenerate the SDK

From packages/client, run pnpm generate. The script deletes src/generated, then runs fern generate --group ts-sdk --force from inside fern/. The ts-sdk group uses fernapi/fern-typescript-sdk@3.72.5 with fetchSupport: native and writes to ../src/generated.
4

Verify the build

From packages/client, run pnpm typecheck to confirm tsc --noEmit passes against the freshly generated types before downstream apps pick them up.
{
  "organization": "clay",
  "version": "5.65.3"
}
The generator config sets fetchSupport: native, so the SDK uses the platform’s built-in fetch — it does not pull in node-fetch, undici, or any other HTTP shim.

Exports

@clay/client resolves through ./src/generated/index.{ts,js} and re-exports the surface below.

ClayApiClient

The main client class. Construct with new ClayApiClient({environment}). Exposes resource clients as lazy getters and a fetch(input, init, requestOptions) passthrough for endpoints the SDK has not yet generated.

ClayApi

Namespace export from ./api/index.js. Holds the per-resource sub-clients and request/response types generated from the OpenAPI document.

ClayApiEnvironment

Object literal of base URLs. Today only Default = "https://api.clay.local". The union type ClayApiEnvironment is the same as Default until a second environment is added.

Errors

ClayApiError (status code, body, raw response, cause) and ClayApiTimeoutError (wraps the underlying cause). Plus handleNonStatusCodeError for advanced error-mapping use.

Environments

packages/client/src/generated/environments.ts
export const ClayApiEnvironment = {
  Default: "https://api.clay.local",
} as const;

export type ClayApiEnvironment = typeof ClayApiEnvironment.Default;
The default points at https://api.clay.local, which is a placeholder hostname. Production, staging, and localhost base URLs must be wired through deployment configuration before pointing an app at a real Worker.

Errors

The SDK throws two top-level error types.
ErrorWhen it is thrown
ClayApiErrorThe server returned a non-2xx response, or the response body could not be decoded.
ClayApiTimeoutErrorThe request exceeded the configured timeoutInSeconds. Carries the underlying cause.
ClayApiError exposes:
  • statusCode?: number — the HTTP status if one was returned
  • body?: unknown — the parsed response body (or raw string for non-JSON responses)
  • rawResponse — the core.RawResponse from the fetcher layer
  • cause?: unknown — the original throwable, when applicable
handleNonStatusCodeError is the SDK’s internal mapper for fetcher failures (non-json, body-is-null, timeout, unknown) into either ClayApiError or ClayApiTimeoutError. Apps that build custom error handlers can reuse this to stay aligned with the SDK’s error model.

Auth surface (planned)

Reference material for the broader auth surface lives in the canonical Clay repository at docs/vendor/better-auth/llms.txt; see Reference materials for the refresh workflow. The SDK does not yet expose auth resources — they land with the B2C auth model documented in Auth model.

Consumption pattern

Import the client from @clay/client, construct it once with an environment, and call resource methods. The current SDK exposes only health, but the pattern scales as new resources ship.
apps/mobile/app/_lib/clay-api.ts
import { ClayApiClient, ClayApiEnvironment } from "@clay/client";

export const clayApi = new ClayApiClient({
  environment: ClayApiEnvironment.Default,
});
apps/mobile/app/_lib/use-health.ts
import { clayApi } from "./clay-api";

export async function fetchHealth() {
  // Resource clients are lazy — instantiated on first access.
  return clayApi.health.check();
}
Construct ClayApiClient once per process and share the instance. The class is cheap to create, but each instance allocates its own fetcher, retry policy, and request logger.

Regeneration commands

All commands below run from packages/client unless noted.
TaskCommand
Validate the OpenAPI contractpnpm check
Regenerate the SDK from OpenAPIpnpm generate
Build (typecheck the generated types)pnpm build
Typecheck generated typespnpm typecheck
Remove the generated folderpnpm clean
Format Fern + config filespnpm format
CI-friendly format checkpnpm format:check
pnpm generate first runs rm -rf src/generated so stale endpoints cannot linger. Always run pnpm typecheck after generation and before opening a PR.

Acceptance criteria

  • packages/client/src/generated is committed and matches the latest apps/backend/openapi/openapi.yaml.
  • pnpm generate runs cleanly with no generator warnings against the current OpenAPI.
  • pnpm typecheck (tsc --noEmit) passes against src/generated/index.d.ts.
  • ClayApiEnvironment.Default is the only base URL exposed until a second environment is added.
  • Consumers (apps/mobile, apps/landing) import from @clay/client, never from a hand-written wrapper that re-exports the same types.

Architecture

How the generated client fits the four-app Turborepo surface.

Workspace

The pnpm + Turborepo layout and per-package scripts that ship @clay/client.

API reference

The implemented HTTP routes that the SDK’s resource clients wrap.

OpenAPI pipeline

The backend OpenAPI export pipeline that feeds Fern generation.