Stop writing TypeScript interfaces by hand: auto-generate them from JSON
Every time you integrate a new API, you end up staring at a JSON response and typing out an interface by hand.
Field by field. Guessing which values are optional. Forgetting that created_at is a string, not a Date.
Copy-pasting from docs that may or may not match the real response.
This is tedious, slow, and a reliable source of bugs. The JSON already contains every type you need. A single API call can extract them.
One POST request, one TypeScript interface
Send any JSON object to the Botoi json-to-typescript endpoint with a name for the root interface.
The API returns the full TypeScript interface as a string.
curl -X POST https://api.botoi.com/v1/schema/json-to-typescript \
-H "Content-Type: application/json" \
-d '{
"json": {
"id": 1,
"name": "Acme Corp",
"active": true,
"tags": ["saas", "b2b"]
},
"name": "Company"
}' Response:
{
"success": true,
"data": {
"typescript": "interface Company {\n id: number;\n name: string;\n active: boolean;\n tags: string[];\n}",
"name": "Company"
}
} The generated interface, formatted:
interface Company {
id: number;
name: string;
active: boolean;
tags: string[];
}
The API infers number, string, boolean, and string[] from the values.
No manual annotation. No guessing.
Build a types-from-api script for your project
Typing interfaces by hand is a one-time fix. The better approach: a script that fetches live API responses and generates type files you can commit to your repo.
Here is a bash script that generates TypeScript types from any URL:
#!/bin/bash
set -euo pipefail
API="https://api.botoi.com/v1/schema/json-to-typescript"
OUT_DIR="./src/types/generated"
mkdir -p "$OUT_DIR"
generate_type() {
local url="$1"
local name="$2"
local file="$3"
echo "Fetching $url ..."
local json
json=$(curl -s "$url")
echo "Generating $name interface..."
local result
result=$(curl -s -X POST "$API" \
-H "Content-Type: application/json" \
-d "{"json": $json, "name": "$name"}")
echo "$result" | jq -r '.data.typescript' > "$OUT_DIR/$file"
echo "Wrote $OUT_DIR/$file"
}
generate_type "https://api.github.com/users/octocat" "GitHubUser" "github-user.ts"
generate_type "https://jsonplaceholder.typicode.com/posts/1" "BlogPost" "blog-post.ts"
echo "Done. All types written to $OUT_DIR/" Run this script during development or as a pre-commit hook. When the upstream API changes, re-run the script and your types stay accurate.
If you prefer Node.js over bash:
import { writeFileSync, mkdirSync } from "fs";
const API = "https://api.botoi.com/v1/schema/json-to-typescript";
async function generateType(json: unknown, name: string): Promise<string> {
const res = await fetch(API, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ json, name }),
});
const data = await res.json();
return data.data.typescript;
}
async function main() {
const userRes = await fetch("https://api.github.com/users/octocat");
const userJson = await userRes.json();
const typescript = await generateType(userJson, "GitHubUser");
mkdirSync("./src/types/generated", { recursive: true });
writeFileSync("./src/types/generated/github-user.ts", typescript);
console.log("Wrote ./src/types/generated/github-user.ts");
}
main(); Real example: generate types for the GitHub API user response
The GitHub /users/:username endpoint returns 30+ fields. Writing that interface by hand
takes minutes and invites typos. Here is the two-step approach:
# Fetch the GitHub user JSON
GITHUB_JSON=$(curl -s https://api.github.com/users/octocat)
# Send it to the Botoi API
curl -s -X POST https://api.botoi.com/v1/schema/json-to-typescript \
-H "Content-Type: application/json" \
-d "{"json": $GITHUB_JSON, "name": "GitHubUser"}" \
| jq -r '.data.typescript' Output:
interface GitHubUser {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
name: string;
company: string;
blog: string;
location: string;
bio: string;
twitter_username: string;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
updated_at: string;
} That is 32 fields, typed correctly, in under a second. Pipe the output into a file and you have a production-ready type definition.
Generate Zod schemas instead
TypeScript interfaces give you compile-time safety, but they vanish at runtime. If you need runtime validation,
the json-to-zod endpoint generates a Zod schema from the same JSON input.
curl -s -X POST https://api.botoi.com/v1/schema/json-to-zod \
-H "Content-Type: application/json" \
-d '{
"json": {
"id": 1,
"name": "Acme Corp",
"active": true,
"tags": ["saas", "b2b"]
},
"name": "Company"
}' | jq -r '.data.zod' Output:
import { z } from "zod";
const Company = z.object({
id: z.number(),
name: z.string(),
active: z.boolean(),
tags: z.array(z.string()),
}); Drop the generated schema into your codebase and you get both runtime validation and type inference:
import { z } from "zod";
const Company = z.object({
id: z.number(),
name: z.string(),
active: z.boolean(),
tags: z.array(z.string()),
});
type Company = z.infer<typeof Company>;
const parsed = Company.parse(apiResponse);
// parsed is fully typed and validated at runtime With Zod, invalid data throws at the boundary instead of causing silent bugs deep in your application logic.
When to use each endpoint
| Scenario | Endpoint | Why |
|---|---|---|
| Internal API responses you trust | json-to-typescript | Lightweight; no runtime cost |
| External API responses | json-to-zod | Validates data at the boundary |
| Form inputs or user-submitted data | json-to-zod | Parse, don't validate |
| Quick prototyping | json-to-typescript | Fastest path to typed code |
| CI pipeline type generation | Either | Both produce deterministic output |
Key points
- One request replaces manual typing. Send JSON, get a TypeScript interface or Zod schema back.
- Automate it. Add a script to your project that regenerates types whenever upstream APIs change.
- No account required. The free tier allows 5 requests per minute with no signup.
- Works with any JSON source. REST APIs, config files, database exports, webhook payloads.
The full API docs cover
additional schema endpoints, including json-to-jsonschema for JSON Schema output.
Frequently asked questions
- What JSON structures does the API support?
- The API handles nested objects, arrays, mixed-type arrays, nulls, and deeply nested structures. It infers the correct TypeScript type for each value, including optional fields when null is present.
- Do I need an API key?
- No. Anonymous access is allowed at 5 requests per minute with IP-based rate limiting. For higher volume, sign up for an API key at botoi.com/api.
- Can I generate Zod schemas instead of TypeScript interfaces?
- Yes. Send the same JSON body to POST https://api.botoi.com/v1/schema/json-to-zod and you get a Zod schema string you can drop into your project.
- How does the API handle arrays with mixed types?
- If an array contains values of different types, the API produces a union type. For example, an array with strings and numbers becomes (string | number)[].
- Can I use this in a CI pipeline?
- Yes. The API is a standard HTTP POST endpoint. Call it from any shell script, GitHub Action, or build step to keep your type definitions in sync with upstream API responses.
Try this API
JSON Validator API — interactive playground and code examples
More tutorial posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.