Generate Production-Ready OpenAPI Specs in 20 Minutes

Fix incomplete AI-generated API docs with validation techniques, structured prompts, and code-first generation using Zod and TypeScript.

Problem: AI Writes Incomplete API Docs

You asked an LLM to generate OpenAPI specs and got generic schemas that miss edge cases, lack proper error responses, and use inconsistent naming conventions.

You'll learn:

  • How to prompt LLMs for complete OpenAPI 3.1 specs
  • Validation techniques to catch AI hallucinations
  • Patterns that make AI-generated docs maintainable

Time: 20 min | Level: Intermediate


Why AI Fails at API Docs

LLMs don't understand your business logic. They generate syntactically valid YAML that's semantically wrong—missing required fields, inventing status codes, or using inconsistent data types.

Common symptoms:

  • Schemas don't match actual API responses
  • Missing authentication requirements
  • No examples for complex request bodies
  • Inconsistent naming (camelCase vs snake_case)

Solution

Step 1: Provide Complete Context

Don't just ask "generate OpenAPI for my user endpoint." Give the AI your actual code.

// Include actual route handler
app.post('/api/users', async (req, res) => {
  const { email, name, role } = req.body;
  
  // AI needs to see validation
  if (!email || !email.includes('@')) {
    return res.status(400).json({ error: 'Invalid email' });
  }
  
  // AI needs to see possible responses
  try {
    const user = await createUser({ email, name, role });
    res.status(201).json(user);
  } catch (err) {
    if (err.code === 'DUPLICATE_EMAIL') {
      return res.status(409).json({ error: 'Email exists' });
    }
    res.status(500).json({ error: 'Server error' });
  }
});

Prompt template:

Generate OpenAPI 3.1 spec for this endpoint:

[paste actual code]

Requirements:
- Include all possible status codes (I see 201, 400, 409, 500)
- Use real field names from code
- Add request/response examples
- Document authentication if needed

Step 2: Validate Against Real Responses

AI will hallucinate fields. Catch it with automated validation.

# Install validator
npm install -D @apidevtools/swagger-cli

# Generate spec with AI, save to api-spec.yaml

# Validate syntax
npx swagger-cli validate api-spec.yaml

Then test against real API:

// validate-spec.js
const SwaggerParser = require('@apidevtools/swagger-parser');
const axios = require('axios');

async function validateEndpoint(spec, path, method) {
  const endpoint = spec.paths[path][method];
  
  // Make real API call
  const response = await axios({
    method,
    url: `http://localhost:3000${path}`,
    // Use example from spec
    data: endpoint.requestBody?.content['application/json']?.example
  });
  
  // Compare response shape to schema
  const schema = endpoint.responses[response.status]?.content['application/json']?.schema;
  const Ajv = require('ajv');
  const ajv = new Ajv();
  const valid = ajv.validate(schema, response.data);
  
  if (!valid) {
    console.error('Schema mismatch:', ajv.errors);
  }
}

If it fails:

  • "Schema validation failed": AI invented fields. Update spec to match reality.
  • "No schema for status 422": Add missing error response to spec.

Step 3: Use Structured Output Format

Don't let AI freestyle. Enforce structure with schema constraints.

# template.yaml - Give this to AI
openapi: 3.1.0
info:
  title: [AI fills this]
  version: 1.0.0
  
paths:
  /api/users:
    post:
      summary: [AI fills: short description]
      description: [AI fills: detailed behavior]
      
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [AI lists required fields]
              properties:
                # AI fills each field with:
                # - type
                # - description
                # - format (if applicable)
                # - example
            example: [AI provides realistic example]
      
      responses:
        '201':
          description: [AI explains when this happens]
          content:
            application/json:
              schema:
                # AI defines response shape
              example: [AI shows actual response]
        
        '400':
          description: [AI lists validation errors]
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: [AI shows real error message]
        
        # AI adds other status codes seen in code

Why this works: AI fills blanks instead of inventing structure. You get consistent formatting across all endpoints.


Step 4: Automate Consistency Checks

AI will use user_id in one endpoint and userId in another. Catch it.

// check-consistency.js
const spec = require('./api-spec.json');

const issues = [];

// Check naming convention
Object.values(spec.components?.schemas || {}).forEach(schema => {
  Object.keys(schema.properties || {}).forEach(field => {
    if (field.includes('_')) {
      issues.push(`Snake_case field: ${field}`);
    }
  });
});

// Check all 4xx responses have error field
Object.entries(spec.paths).forEach(([path, methods]) => {
  Object.entries(methods).forEach(([method, endpoint]) => {
    Object.entries(endpoint.responses || {}).forEach(([code, response]) => {
      if (code.startsWith('4')) {
        const schema = response.content?.['application/json']?.schema;
        if (!schema?.properties?.error) {
          issues.push(`${method.toUpperCase()} ${path} - ${code} missing error field`);
        }
      }
    });
  });
});

if (issues.length > 0) {
  console.error('Consistency issues:', issues);
  process.exit(1);
}

Run in CI:

# .github/workflows/api-docs.yml
- name: Check API spec consistency
  run: node check-consistency.js

Step 5: Generate from Code (Advanced)

Instead of AI guessing, extract OpenAPI from TypeScript types.

// user.types.ts
import { z } from 'zod';

// Define schema with validation
export const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  role: z.enum(['admin', 'user', 'guest']).default('user')
});

export type CreateUserRequest = z.infer<typeof CreateUserSchema>;
// Use zod-to-openapi
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

const schema = CreateUserSchema.openapi({
  example: {
    email: 'alice@example.com',
    name: 'Alice Smith',
    role: 'user'
  }
});

// Auto-generate OpenAPI
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';

const registry = new OpenAPIRegistry();
registry.registerPath({
  method: 'post',
  path: '/api/users',
  request: {
    body: {
      content: {
        'application/json': { schema: CreateUserSchema }
      }
    }
  },
  responses: {
    201: {
      description: 'User created',
      content: {
        'application/json': {
          schema: z.object({
            id: z.string().uuid(),
            email: z.string(),
            createdAt: z.string().datetime()
          })
        }
      }
    }
  }
});

Then ask AI to fill descriptions:

Add human-readable descriptions to this OpenAPI spec:

[paste generated spec]

For each field, explain:
- What it represents
- Validation rules
- When it's required

Verification

# Validate spec
npx swagger-cli validate api-spec.yaml

# Generate interactive docs
npx @redocly/cli build-docs api-spec.yaml

# Test against running API
node validate-spec.js

You should see: Valid OpenAPI 3.1 spec, no schema mismatches, consistent naming conventions.


What You Learned

  • AI needs actual code context, not just endpoint descriptions
  • Always validate generated specs against real API responses
  • Enforce structure with templates to prevent AI freestyle
  • Code-first generation (Zod, TypeScript) is more reliable than pure AI

Limitations:

  • AI can't infer complex business rules without examples
  • Authentication flows need manual review
  • Deprecated endpoints confuse AI if they exist in code

Tested with Claude Sonnet 4, GPT-4, OpenAPI 3.1.0, Zod 3.x