rchitecture prioritizes data fidelity over documentation assumptions, ensuring generated types reflect what the API actually returns.
Step-by-Step Implementation
1. Fetch Live Payloads and Generate Types
Point the CLI at any JSON endpoint. The tool performs a single request, analyzes the response structure, and outputs both TypeScript interfaces and Zod schemas.
npx snaptype from-url https://api.logistics.example.com/v2/shipments \
--zod \
-o src/contracts/shipment.ts
2. Generated Output Structure
The CLI performs structural inference, expanding nested objects, detecting repeated value patterns for enums, identifying nullable fields, and recognizing semantic formats.
// src/contracts/shipment.ts
export interface Shipment {
trackingId: string;
origin: Location;
destination: Location;
status: "pending" | "in_transit" | "delivered" | "exception";
weightKg: number | null;
dimensions: Dimensions;
estimatedDelivery: string;
customsDeclaration?: CustomsInfo;
}
export interface Location {
facilityCode: string;
coordinates: {
latitude: number;
longitude: number;
};
}
export interface Dimensions {
lengthCm: number;
widthCm: number;
heightCm: number;
}
export interface CustomsInfo {
hsCode: string;
declaredValue: number;
currency: "USD" | "EUR" | "GBP" | "JPY";
}
import { z } from "zod";
export const LocationSchema = z.object({
facilityCode: z.string(),
coordinates: z.object({
latitude: z.number(),
longitude: z.number(),
}),
});
export const DimensionsSchema = z.object({
lengthCm: z.number().positive(),
widthCm: z.number().positive(),
heightCm: z.number().positive(),
});
export const CustomsInfoSchema = z.object({
hsCode: z.string().min(6).max(12),
declaredValue: z.number().nonnegative(),
currency: z.enum(["USD", "EUR", "GBP", "JPY"]),
});
export const ShipmentSchema = z.object({
trackingId: z.string().uuid(),
origin: LocationSchema,
destination: LocationSchema,
status: z.enum(["pending", "in_transit", "delivered", "exception"]),
weightKg: z.number().nullable(),
dimensions: DimensionsSchema,
estimatedDelivery: z.string().datetime(),
customsDeclaration: CustomsInfoSchema.optional(),
});
3. OpenAPI Specification Parsing
When working with documented services, the CLI can parse OpenAPI 3.0 or Swagger 2.0 specifications and generate one file per schema definition.
npx snaptype from-openapi ./specs/inventory-service.yaml \
-o src/contracts/
This approach is ideal for services with comprehensive specs, as it generates types for all defined schemas without requiring live endpoint access.
4. Local Data Ingestion
For internal tools, database exports, or legacy systems without live endpoints, the CLI accepts JSON and CSV files.
npx snaptype from-json ./fixtures/user-profile.json --zod -o src/contracts/user.ts
npx snaptype from-csv ./exports/transaction-log.csv --zod -o src/contracts/transaction.ts
Architecture Decisions and Rationale
Why generate from actual payloads instead of relying solely on OpenAPI specs?
API documentation frequently lags behind implementation. Field names change, optional fields become required, and enum values expand without spec updates. Data-driven generation ensures types match runtime behavior. When specs are available, they serve as a fallback or supplementary source, but live payloads remain the source of truth.
Why pair TypeScript interfaces with Zod schemas automatically?
TypeScript provides compile-time safety but offers zero runtime guarantees. Zod bridges this gap by enabling runtime validation, type narrowing, and safe parsing. Generating both simultaneously eliminates the manual mapping step between static types and validation logic, reducing boilerplate by 60β80%.
Why use a configuration file for authentication and defaults?
Enterprise APIs require consistent headers, tenant identifiers, and bearer tokens. Repeating -H flags across multiple commands introduces friction and increases the risk of token leakage in shell history. A project-level configuration file centralizes defaults while allowing CLI overrides for endpoint-specific requirements.
Why infer enums and semantic types automatically?
Manual type authoring forces developers to guess whether a string field represents a date, UUID, currency code, or status enum. The CLI analyzes value distributions across responses, identifies low-cardinality string sets, and converts them to union types. Semantic detection (ISO datetime, UUID format, numeric ranges) further reduces manual refinement.
Pitfall Guide
1. Sample Bias from Single-Page Responses
Explanation: Fetching a single endpoint response often returns a paginated subset. Optional fields, rare status values, or deeply nested objects may not appear in the sample, leading to incomplete type definitions.
Fix: Aggregate multiple responses across different query parameters or pagination offsets. Use the --aggregate flag if available, or manually merge samples before generation. When possible, cross-reference with OpenAPI specs to fill gaps.
2. Authentication Token Leakage
Explanation: Storing bearer tokens or API keys directly in .snaptyperc or shell commands risks accidental commits to version control or exposure in CI logs.
Fix: Use environment variable interpolation in configuration files. Reference tokens via $TOKEN or ${process.env.API_KEY} syntax. Never commit raw credentials. Implement pre-commit hooks to scan for secret patterns.
3. Over-Generation of Validation Schemas
Explanation: Generating Zod schemas for every endpoint creates unnecessary bundle size in client-side applications, especially when only critical paths require runtime validation.
Fix: Scope generation to high-risk endpoints (user input, financial data, external integrations). Use tree-shaking or conditional imports to exclude unused schemas from production builds. Consider generating types only for internal services where validation is handled server-side.
4. Ignoring Contract Drift in CI/CD
Explanation: Running type generation once and never updating it creates stale contracts. API changes silently break type safety until runtime errors occur.
Fix: Integrate generation into CI pipelines with checksum verification. Schedule periodic regeneration against staging environments. Fail builds when generated types diverge from committed versions, forcing developers to review and accept contract changes.
5. Circular Reference and Deep Nesting Crashes
Explanation: Some APIs return deeply nested or self-referential structures (e.g., comment threads, organizational hierarchies). Naive inference can trigger stack overflows or generate infinitely recursive types.
Fix: Limit inference depth via configuration flags. Use OpenAPI spec generation for complex graphs, as specs explicitly define reference boundaries. Manually prune non-essential nested objects before generation.
6. Semantic Type Misclassification
Explanation: The CLI may incorrectly infer a string field as a date or UUID based on format matching, when the API actually returns arbitrary strings.
Fix: Review generated semantic types before committing. Override incorrect inferences using post-generation type assertions or configuration rules. Maintain a type refinement layer for edge cases where format detection is ambiguous.
7. Mixing Build-Time and Runtime Concerns
Explanation: Importing generated Zod schemas directly into browser bundles increases payload size and introduces unnecessary validation overhead for trusted internal data.
Fix: Separate type generation from runtime validation. Use generated interfaces for compile-time safety across the application. Reserve Zod schemas for boundary layers (API clients, form handlers, external data ingestion). Implement code splitting to load validation schemas only when needed.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Third-party API with live docs | from-url with aggregation | Payloads reflect actual behavior, specs often lag | Low (seconds per endpoint) |
| Internal microservice with OpenAPI spec | from-openapi | Spec is authoritative, covers all schemas | Low (one-time setup) |
| Legacy database export / CSV migration | from-csv | No live endpoint available, schema must be inferred | Medium (requires data sampling) |
| Authenticated enterprise API | from-url + .snaptyperc | Centralized auth management, DRY header configuration | Low (config overhead) |
| High-traffic client application | Types only, skip Zod | Minimize bundle size, validation handled server-side | Low (reduced payload) |
| Rapid prototyping / MVP | from-url + --zod | Speed over precision, validation catches early bugs | Low (fast iteration) |
Configuration Template
Create a .snaptyperc file at the project root to centralize defaults and authentication:
{
"outputDirectory": "src/contracts",
"generateZod": true,
"inferenceDepth": 4,
"semanticDetection": true,
"headers": {
"Authorization": "Bearer ${API_TOKEN}",
"X-Client-Version": "2.1.0",
"Accept": "application/json"
},
"overrides": {
"dateFields": ["createdAt", "updatedAt", "shippedAt"],
"uuidFields": ["id", "trackingId", "referenceId"]
}
}
CLI commands automatically inherit these settings. Endpoint-specific flags override configuration values when provided.
Quick Start Guide
- Initialize the project: Run
npx snaptype from-url https://api.example.com/v1/status --zod -o src/contracts/status.ts to verify connectivity and output structure.
- Configure defaults: Create
.snaptyperc with your base URL, authentication headers, and output preferences.
- Generate core contracts: Run generation commands for your primary endpoints. Review inferred types and Zod schemas for accuracy.
- Integrate into workflow: Add generation scripts to
package.json and configure CI to run them on API dependency updates.
- Validate at boundaries: Import generated Zod schemas in API client layers to enforce runtime validation before data enters application state.
Data-driven type generation transforms API integration from a manual, error-prone process into a deterministic, maintainable workflow. By treating payloads as the source of truth and automating contract synchronization, teams eliminate guesswork, reduce maintenance overhead, and enforce runtime safety without sacrificing development velocity.