Blog
Wild & Free Tools

Typing API Responses With TypeScript Interfaces Generated From JSON

Last updated: April 2026 8 min read
Quick Answer

Table of Contents

  1. The Fast Loop: Postman to Interface
  2. Handling Null and Missing Fields
  3. Paginated Endpoints and Wrapper Types
  4. Arrays with Mixed Item Shapes
  5. Keeping Types in Sync with the API
  6. Frequently Asked Questions

Third-party API docs lie. Not on purpose — they just drift. A field gets added in production before it hits the docs, a field becomes nullable in v2 but the docs still show it as a string, an array is documented as always non-empty but sometimes ships empty.

The shortest path to types that actually match production is: hit the endpoint, copy a real response, paste it into a JSON-to-TypeScript generator, and use that output. This guide walks through the practical workflow and the gotchas that come up when you do this on real APIs.

The Fast Loop — Postman to TypeScript Interface

This is the loop. Two tabs, three paste operations, done.

Step 1. Hit the endpoint in Postman, Insomnia, Bruno, or curl. Use a request that's representative of production — real auth token, real user ID, not a test account that's missing half the data.

Step 2. Copy the response body. In Postman: right-click the response area, Copy to Clipboard. In curl: pipe to pbcopy (Mac), clip.exe (Windows WSL), or xclip (Linux).

Step 3. Paste into our JSON to TypeScript generator. You'll see the interface appear on the right in well under a second.

Step 4. Copy the interface, paste into your code. Rename Root to something meaningful — UserResponse, OrderListItem, whatever fits your domain.

The whole loop is under 30 seconds once you've done it a few times. You're not writing types by hand, and you're not relying on docs that might be wrong.

Nullable Fields, Missing Fields, and Why They Are Different

A single sample might not show you every shape the API can return. Two cases bite people.

The field was present in your sample but can be null. Your generated interface shows avatar: string, but sometimes the API returns avatar: null. When null ships, your code crashes on user.avatar.startsWith.

Fix: paste a second sample that shows null, or edit the type to avatar: string | null manually. Once you know a field can be null, code defensively — TypeScript's strict null checks will force you to.

The field was present in your sample but can be absent. Your interface shows nickname: string, but some users don't have one and the API just omits the key.

Fix: nickname?: string — the question mark. Optional and nullable are different. user.nickname could be undefined (key missing) or null (key present, value null). nickname?: string | null covers both.

A pragmatic rule of thumb: for any API you don't control, assume every field can be null or missing unless you've explicitly verified otherwise across enough samples.

Sell Custom Apparel — We Handle Printing & Free Shipping

Paginated Endpoints and Wrapper Types

Most REST APIs wrap lists in a pagination envelope. Something like:

{
  "data": [ /* array of items */ ],
  "page": 1,
  "pageSize": 20,
  "total": 340,
  "hasMore": true
}

Paste this into the generator and you get a Root interface with a data array typed from the first element. That's fine, but in your code you usually want a generic wrapper so you can reuse it across every paginated endpoint:

interface Paginated<T> {
  data: T[];
  page: number;
  pageSize: number;
  total: number;
  hasMore: boolean;
}

type UsersList = Paginated<User>;
type OrdersList = Paginated<Order>;

The generator won't produce the generic automatically — it doesn't know which field is "the generic one." But it gets you 90% of the way there, and the last step is a one-time refactor you do once per codebase.

Arrays Where Different Items Have Different Shapes

Some APIs return arrays where different items have different shapes. Feed data, notification lists, activity logs:

[
  { "type": "comment", "text": "nice" },
  { "type": "like", "targetId": 42 },
  { "type": "follow", "userId": 7 }
]

The generator sees the first element and types the array as that shape. But real items have more variants. What you want is a discriminated union:

type ActivityItem =
  | { type: "comment"; text: string }
  | { type: "like"; targetId: number }
  | { type: "follow"; userId: number };

type Activity = ActivityItem[];

You can't infer this from a single sample — you need to know all the variants. Use the generator to scaffold the first variant, then add the others by hand. Or, if your API ships an OpenAPI schema, generate from that instead — schemas describe all variants, samples describe one.

Keeping Your Types in Sync as the API Evolves

APIs change. Your types need to keep up. Three strategies:

1. Regenerate on demand. When your code breaks at runtime because a field is null that wasn't before, re-run the generator with a fresh sample. Low effort, reactive — you're fixing after the fact.

2. Runtime validation with Zod. Generate a Zod schema instead of a bare interface. At runtime, Zod parses the response and throws clearly if the shape has drifted. Your code fails at the boundary with a specific error, not deep in business logic.

3. Contract tests. For APIs you control, write a test that hits a real endpoint and validates the response against the schema. Runs in CI, catches breakage before it ships.

For most third-party APIs, strategy 2 is the sweet spot. You get the type safety of generated interfaces plus a loud runtime failure when the API changes under you. Read how runtime validation fits in for the full pattern.

Generate Types From a Real Response

Paste a real API response, get a TypeScript interface that matches production.

Open Free JSON to TypeScript Generator

Frequently Asked Questions

Should I generate types from every endpoint response?

Only endpoints you actually use. Generating for every possible endpoint up front creates types that go stale before you need them.

What if the API response shape depends on input parameters?

Generate a type for each distinct shape and union them, or use generics if the parameter affects a single field. A filter query that returns different fields based on the requested view is a common example.

Should generated types live in the same file as the code that uses them?

Most teams put API types in a dedicated types/ or api/ folder so they can be imported from anywhere. If a type is only used in one file, co-locate it.

Do I need runtime validation if I have TypeScript types?

Types are compile-time only — they do not check real responses. If your API can change or return unexpected shapes, runtime validation (Zod, Yup, Valibot) catches drift that types cannot.

Andrew Walsh
Andrew Walsh Developer Tools & API Writer

Andrew worked as a developer advocate at two SaaS startups writing API documentation used by thousands of engineers.

More articles by Andrew →
Launch Your Own Clothing Brand — No Inventory, No Risk