Back to blog

TypeScript API contract for Bitrix REST: generate types, stop guessing shapes

We spent two weeks asking "what does this method actually return?" when we moved a 28,000-product catalog to a headless architecture. Open the Bitrix REST docs — they're three months behind the actual version. Open the debugger, call the method, look at the real response, infer the shape, write an interface. Next day, a teammate does the same with another method. Slightly different assumptions.

A month in: 40+ any across the TypeScript codebase and two production incidents — one with pricing, one with inventory.

The problem: Bitrix REST without a schema

Bitrix REST API has no official OpenAPI specification or JSON Schema — response shapes are defined in PHP module source code and documented informally, often lagging behind the actual behavior by several months. Bitrix REST API is the de facto integration layer for headless Bitrix setups. But there's no OpenAPI spec, no JSON Schema, no official typed client. It's a PHP-era API where response shape is defined in module source code or documentation that doesn't always keep up.

We've written about other surprises in Bitrix REST API when building headless — rate limits, pagination instability. Types are a separate problem. For a Next.js developer, the choice is either any everywhere or manual type definitions. The first is fragile. The second rots with every Bitrix update.

We found a third option.

Three approaches to TypeScript types for Bitrix REST

When a REST API has no schema, there are three practical ways to add types: write them manually, infer from runtime validation schemas, or generate from real API responses.

Manual interfaces. Write TypeScript types from docs and debugger output. Fast at first. Types go stale silently — Bitrix updates, the response shape changes, the type is wrong, you find out when the next frontend deploy hits production.

Zod inference. Define the schema with Zod, TypeScript types are inferred automatically. Works, but writing Zod schemas for each method by hand is the same amount of work as manual interfaces. Plus runtime validation you didn't ask for.

Generate from real responses. Call each method with real data, capture the response, generate a TypeScript type. The type reflects reality, not documentation.

We picked the third.

What we built: ts-morph and 600 lines of contract

The generator is a ts-morph script. It does three things.

Calls each needed Bitrix REST method over HTTP with real parameters. For the 28k SKU catalog: catalog.product.list, catalog.product.offer.list, sale.order.add, crm.lead.add, and eight others.

Saves each response as a JSON artifact in the repository. This is the source of truth — committed to version control.

Generates a TypeScript interface from the JSON shape. Output: bitrix-api.types.ts, 600 lines on first run.

The script runs in CI before every frontend deploy. If the response shape changed — the type check catches it before production.

The first real catch

Three months after launch, Bitrix updated. The catalog.product.list response got a new field: PREVIEW_PICTURE_VARIANTS — an array of multiple image sizes, replacing a single PREVIEW_PICTURE field.

Without typed contracts: broken product card images in production, discovered on the next frontend deploy. With typed contracts: CI failure, 4 minutes before the deploy ran.

15 minutes to fix in CI instead of a rollback incident.

What this does to code review

When a developer opens a PR that changes a Bitrix REST integration, the diff includes bitrix-api.types.ts. You can see which field was added, what changed type, what disappeared.

Before: "I think this is string | null." After: an explicit line in code that a reviewer can see and question.

Types stopped being guesses. Became part of the contract.

Why OpenAPI generators didn't work

We tried community tools that parse Bitrix REST documentation and generate OpenAPI schemas. Result: inaccurate schema, half the methods covered partially, any in the same places.

The core problem: Bitrix documentation isn't a specification. Real responses often differ — extra fields, different null patterns, types that vary based on request flags. Only a real response with real data produces a correct type.

Numbers

Six months after introducing the generator: zero runtime errors from Bitrix REST response shape changes. Before: roughly two incidents per month reaching either the client or staging at deploy time.

Side effect: onboarding a new developer onto the project got faster. bitrix-api.types.ts is documentation for every API method we use. No need to dig through Bitrix docs to know what a method returns.

What we didn't do

We don't cover all of Bitrix REST — that's hundreds of methods. Only the ones actually used in the project. Adding a new method means adding one more call to the generator, running it, getting the type.

No automatic scheduled type updates either. Updates happen only when an integration explicitly changes. Otherwise it's noise nobody reads.

The takeaway

Bitrix REST API works for headless, but it needs discipline on the frontend side. Generating types from real responses isn't a nice-to-have. The contract between your PHP backend and Next.js frontend needs to be explicit somewhere — might as well be in the type file that runs in CI.

3-4 hours to build the generator. Pays off on the first Bitrix update.

For building the full headless pipeline before launch: three checks before any headless project.