# Adopting Generated API Types

> Use when migrating frontend code from manual API client calls (`api.get`, `api.create`, `api.surveys.get`, `api.dashboards.list`, `new ApiRequest()`) and handwritten TypeScript interfaces to generated API functions and types. Triggers on files importing from `lib/api`, files with `api.get<`, `api.create<`, `api.<entity>.<method>`, manual interface definitions that duplicate backend serializers, or any frontend file that constructs API URLs by hand. Covers the full replacement workflow — finding the generated equivalent, swapping imports, adapting call sites, and removing dead manual types.

- Skill: `gabrielmoreira/adopting-generated-api-types` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add gabrielmoreira/adopting-generated-api-types`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gabrielmoreira/adopting-generated-api-types/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: gabrielmoreira (https://skillmd.com/u/gabrielmoreira)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/gabrielmoreira/adopting-generated-api-types

---


# Adopting generated API types

## Overview

PostHog generates TypeScript API client functions and types from Django serializers via the OpenAPI pipeline:

```text
Django serializer → drf-spectacular → OpenAPI JSON → Orval → TypeScript (api.ts + api.schemas.ts + api.zod.ts)
```

Generated files live in:

- **Core:** `frontend/src/generated/core/api.ts`, `api.schemas.ts`, and `api.zod.ts`
- **Products:** `products/<product>/frontend/generated/api.ts`, `api.schemas.ts`, and `api.zod.ts`

Generated types use the `Api` suffix (`DashboardApi`, `SurveyApi`). Handwritten types never do.

This skill guides replacing manual API calls and handwritten types with generated equivalents.

## The three manual patterns to migrate

The legacy `frontend/src/lib/api.ts` (~6000 lines) has three layers, all migration targets:

### 1. High-level object API (most common)

Domain-specific convenience methods on the `api` object:

```typescript
api.surveys.get(id)
api.surveys.create(data)
api.dashboards.list()
api.cohorts.update(id, data)
api.actions.create(data)
```

These are the most widely used pattern — every entity has its own namespace with CRUD plus custom methods (e.g., `api.surveys.getResponsesCount()`, `api.dashboards.streamTiles()`).

### 2. Raw HTTP methods with manual URLs

```typescript
api.get<SomeType>(`api/projects/${id}/surveys/`)
api.create<SomeType>(`api/projects/${id}/surveys/`, data)
api.update<SomeType>(url, data)
api.put<SomeType>(url, data)
api.delete(url)
```

### 3. ApiRequest builder (fluent URL construction)

```typescript
const url = new ApiRequest().surveys().assembleFullUrl()
const response = await api.get(url)

// or directly:
await new ApiRequest().survey(surveyId).withAction('summarize_responses').create({ data })
```

All three patterns should be replaced with generated functions where available.

## When to use

- Touching a file that calls `api.<entity>.<method>()` (e.g., `api.surveys.get()`)
- Touching a file that calls `api.get<T>(...)`, `api.create<T>(...)`, etc.
- Touching a file that uses `new ApiRequest()` to build URLs
- Touching a file that imports handwritten interfaces from `~/types` for API response shapes
- Cleaning up frontend code after backend serializer improvements

## Step-by-step workflow

### 1. Identify what the manual call does

Look at the existing call and extract:

- **HTTP method** — GET, POST, PUT, PATCH, DELETE
- **Entity and action** — what resource, what operation
- **Type parameter** — the handwritten type used for the response

### 2. Find the generated equivalent

Generated function names follow the `{resource}{Action}` convention:

```text
surveysList          — GET    /api/projects/{id}/surveys/
surveysCreate        — POST   /api/projects/{id}/surveys/
surveysRetrieve      — GET    /api/projects/{id}/surveys/{id}/
surveysPartialUpdate — PATCH  /api/projects/{id}/surveys/{id}/
surveysDestroy       — DELETE /api/projects/{id}/surveys/{id}/
```

**Where to search:**

- Core endpoints: `frontend/src/generated/core/api.ts`
- Product endpoints: `products/<product>/frontend/generated/api.ts`

**Search strategies:**

1. Grep for the entity name in the generated `api.ts` files
2. Search by the `get*Url` helper functions — every generated function has a URL builder above it
3. Search `api.schemas.ts` for the type name with `Api` suffix

If no generated function exists, the backend endpoint may lack `@extend_schema` or `@validated_request`. Fix the backend first using the `improving-drf-endpoints` skill, then run `hogli build:openapi`.

**Custom actions** (like `api.surveys.summarize_responses()`) may not have generated equivalents if the backend `@action` lacks `@extend_schema`. Check generated files first; if missing, fix the backend.

### 3. Check type compatibility

Compare the handwritten type with the generated `Api` type. Key differences:

- **`readonly` modifiers** — generated types mark read-only fields
- **Optional vs required** — generated types reflect `required=` precisely
- **Nullability** — `null` types are explicit
- **Extra fields** — generated types may include fields the handwritten type omits

See [type-compatibility.md](references/type-compatibility.md) for details.

### 4. Replace the call

See [migration-patterns.md](references/migration-patterns.md) for detailed before/after examples covering:

- High-level object API (`api.surveys.get()` → `surveysRetrieve()`)
- Raw HTTP methods (`api.get<T>(url)` → generated function)
- ApiRequest builder → generated function
- Paginated list calls
- Create/update with request bodies
- Delete calls
- Kea logic loaders and listeners
- Calls with abort signals

### 5. Replace the type at usage sites

Update downstream references from the handwritten type to the generated one:

```typescript
// Before
function renderSurvey(survey: Survey): JSX.Element { ... }

// After
function renderSurvey(survey: SurveyApi): JSX.Element { ... }
```

### 6. Clean up dead types

After migrating all usages of a handwritten type:

1. Remove the type definition from `~/types` or the local file
2. Remove unused imports
3. Run `pnpm --filter=@posthog/frontend typescript:check` to verify no breakage

## Decision guide

| Scenario                                            | Action                                                                                                       |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Generated function exists                           | Replace manual call with generated function                                                                  |
| Generated type exists but function doesn't          | Use the generated type as the generic parameter on the manual call, file a follow-up to add `@extend_schema` |
| Neither exists                                      | Keep the manual pattern, fix the backend serializer/viewset first                                            |
| Custom action without generated equivalent          | Keep the `api.<entity>.<method>()` call, fix the backend `@action` annotation first                          |
| Generated type has different shape than handwritten | Adapt call sites to the generated shape — the serializer is the source of truth                              |
| Code mutates the response object                    | Use a local mutable copy: `const mutable = { ...response }` and mutate that                                  |
| Need both read and write types                      | Use `FooApi` for reads, derive write types via `Parameters<typeof fooCreate>[1]` or use `PatchedFooApi`      |

## Import conventions

```typescript
// Core generated functions — import from api.ts
import { domainsList, domainsCreate, domainsRetrieve } from '~/generated/core/api'

// Core generated types — import type from api.schemas.ts
import type { OrganizationDomainApi } from '~/generated/core/api.schemas'

// Core generated Zod schemas — import from api.zod.ts
import { DomainsCreateBody } from '~/generated/core/api.zod'

// Product generated functions — NO tilde prefix, use 'products/' path
import { surveysList, surveysRetrieve } from 'products/surveys/frontend/generated/api'
import type { SurveyApi } from 'products/surveys/frontend/generated/api.schemas'
import { SurveysCreateBody } from 'products/surveys/frontend/generated/api.zod'

// Within a product, relative imports also work
import { logsAlertsCreate } from '../generated/api'
import type { LogsAlertConfigurationApi } from '../generated/api.schemas'
import { LogsAlertsCreateBody } from '../generated/api.zod'
```

**Path rules:**

- Core: `~/generated/core/...` (tilde prefix)
- Products from outside: `products/<product>/frontend/generated/...` (no tilde)
- Products from inside: relative `../generated/...` or `./generated/...`

Use `import type` for types to enable proper tree-shaking.

## How generated functions work under the hood

Generated functions wrap the same `api` module via `api-orval-mutator.ts`:

```text
surveysList(projectId, params)
  → apiMutator(url, { method: 'GET' })
    → api.get(url)
```

Switching to generated functions does not change HTTP behavior — same cookies, same CSRF, same error handling. The only difference is type safety and URL construction.

## Verifying the migration

1. **TypeScript check:** `pnpm --filter=@posthog/frontend typescript:check`
2. **Grep for leftover manual types:** search for the old type name across the codebase
3. **Run relevant tests:** `hogli test <test_file>`

## Related

- **Backend side:** use `improving-drf-endpoints` to fix serializers that produce poor types
- **Type system docs:** `docs/published/handbook/engineering/type-system.md`
- **API mutator:** `frontend/src/lib/api-orval-mutator.ts`
- **Regenerate types:** `hogli build:openapi`

