AI API Generation from Text Descriptions (Text-to-API)
Text-to-API is the generation of a full REST/GraphQL API layer from natural language description or OpenAPI specification. The task is broader than just writing endpoints: you need data models, validation, middleware, tests, documentation. AI acts as a junior developer with strong FastAPI/Express knowledge who writes at thousands of lines per minute.
API Generator Architecture
from anthropic import Anthropic
from pathlib import Path
import json
from pydantic import BaseModel
from typing import Literal, Optional
client = Anthropic()
class APIEndpoint(BaseModel):
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
path: str
summary: str
request_body: Optional[dict] = None
response_schema: dict
auth_required: bool = True
query_params: list[dict] = []
class APISpec(BaseModel):
title: str
description: str
version: str
base_path: str
endpoints: list[APIEndpoint]
entities: list[dict] # Business entities
class TextToAPIGenerator:
def parse_description(self, description: str) -> APISpec:
"""Parses text description into structured specification"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
system="""You are an API architect. Parse system description into REST API structure.
REST Rules:
- Nouns in URLs (not verbs): /users, /orders
- Correct HTTP methods: GET=read, POST=create, PUT=full replace, PATCH=partial update, DELETE=delete
- Max nesting 2 levels: /users/{id}/orders
- Plural for collections: /products, /categories
- Pagination: ?page=1&limit=20
- Filtering: ?status=active&created_after=2024-01-01""",
messages=[{
"role": "user",
"content": f"""Parse description and return JSON API specification:
{{
"title": "...",
"description": "...",
"version": "1.0.0",
"base_path": "/api/v1",
"entities": [
{{"name": "...", "fields": [{{"name": "...", "type": "...", "required": true}}]}}
],
"endpoints": [
{{
"method": "GET|POST|PUT|PATCH|DELETE",
"path": "/resource/{{id}}",
"summary": "...",
"auth_required": true,
"request_body": {{"field": "type"}},
"response_schema": {{"id": "int", "name": "str"}},
"query_params": [{{"name": "...", "type": "...", "required": false}}]
}}
]
}}
System description:
{description}"""
}]
)
text = response.content[0].text
start = text.find("{")
end = text.rfind("}") + 1
data = json.loads(text[start:end])
return APISpec(**data)
def generate_fastapi_code(self, spec: APISpec) -> dict[str, str]:
"""Generates complete FastAPI project"""
files = {}
# models.py
files["models.py"] = self._generate_models(spec)
# schemas.py
files["schemas.py"] = self._generate_schemas(spec)
# routers/{resource}.py for each entity
for entity in spec.entities:
router_code = self._generate_router(entity, spec)
files[f"routers/{entity['name'].lower()}.py"] = router_code
# main.py
files["main.py"] = self._generate_main(spec)
# tests/
for entity in spec.entities:
test_code = self._generate_tests(entity, spec)
files[f"tests/test_{entity['name'].lower()}.py"] = test_code
return files
def _generate_models(self, spec: APISpec) -> str:
"""Generates SQLAlchemy models"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Create SQLAlchemy 2.0 models for entities:
Entities:
{json.dumps(spec.entities, ensure_ascii=False, indent=2)}
Requirements:
- Use DeclarativeBase
- Add id (Integer PK autoincrement), created_at, updated_at for all models
- Use correct types: String(256), Text, Integer, Float, Boolean, DateTime
- Add __tablename__
- Add relationship() for model relationships
- Add __repr__ for debugging
Return only Python code."""
}]
)
return response.content[0].text.strip()
def _generate_schemas(self, spec: APISpec) -> str:
"""Generates Pydantic v2 schemas"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Create Pydantic v2 schemas for validation:
Entities: {json.dumps(spec.entities, ensure_ascii=False)}
Endpoints: {json.dumps([e.dict() for e in spec.endpoints], ensure_ascii=False)}
For each entity create:
- <Entity>Create — for POST (all required fields)
- <Entity>Update — for PATCH (all fields Optional)
- <Entity>Response — for responses (including id, created_at)
- <Entity>ListResponse — with pagination
Add field validators where needed (email format, positive numbers, string lengths).
Return only Python code."""
}]
)
return response.content[0].text.strip()
def _generate_router(self, entity: dict, spec: APISpec) -> str:
"""Generates router with CRUD endpoints"""
entity_endpoints = [
e for e in spec.endpoints
if entity["name"].lower() in e.path.lower()
]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Create FastAPI router for entity {entity['name']}.
Endpoints to implement:
{json.dumps([e.dict() for e in entity_endpoints], ensure_ascii=False, indent=2)}
Requirements:
- Use APIRouter with prefix and tags
- Dependency injection for DB session (AsyncSession)
- Dependency injection for authorization (get_current_user)
- Async/await for all operations
- Correct HTTP statuses: 201 for POST, 204 for DELETE, 404 if not found
- Pagination via query params page/limit
- Logging via structlog
Return only Python code."""
}]
)
return response.content[0].text.strip()
def _generate_main(self, spec: APISpec) -> str:
return f"""from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
{chr(10).join(f"from routers.{e['name'].lower()} import router as {e['name'].lower()}_router" for e in spec.entities)}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
yield
# Shutdown
app = FastAPI(
title="{spec.title}",
description="{spec.description}",
version="{spec.version}",
lifespan=lifespan,
)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
{chr(10).join(f'app.include_router({e["name"].lower()}_router, prefix="{spec.base_path}")' for e in spec.entities)}
"""
def _generate_tests(self, entity: dict, spec: APISpec) -> str:
"""Generates pytest tests for CRUD endpoints"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Create pytest tests for CRUD endpoints of entity {entity['name']}.
Use:
- pytest-asyncio for async tests
- httpx.AsyncClient for HTTP requests
- pytest fixtures for setup/teardown
- TestDatabase (SQLite in-memory) for isolation
Cover: creation, list reading, single reading, update, delete, 404 cases, input validation.
Return only Python code."""
}]
)
return response.content[0].text.strip()
CLI Interface
import click
import yaml
@click.command()
@click.argument("description_file", type=click.Path(exists=True))
@click.option("--output-dir", "-o", default="./generated_api")
@click.option("--framework", default="fastapi", type=click.Choice(["fastapi", "express"]))
def generate_api(description_file: str, output_dir: str, framework: str):
"""Generates API from text description"""
description = Path(description_file).read_text()
generator = TextToAPIGenerator()
click.echo("Parsing description...")
spec = generator.parse_description(description)
click.echo(f"Found {len(spec.endpoints)} endpoints, {len(spec.entities)} entities")
click.echo("Generating code...")
files = generator.generate_fastapi_code(spec)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
for filename, content in files.items():
file_path = output_path / filename
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content)
click.echo(f" Created: {filename}")
# Generate docker-compose and requirements.txt
_generate_project_files(output_path, spec)
click.echo(f"\nAPI generated in {output_dir}")
click.echo("Run: cd generated_api && docker-compose up")
if __name__ == "__main__":
generate_api()
Description Example for Generation
# Task Management System
Multi-user system for teams.
Entities:
- User: email (unique), name, role (admin/member), avatar_url
- Team: name, description, owner_id
- Project: name, description, team_id, status (active/archived)
- Task: title, description, project_id, assignee_id, status (todo/in_progress/done), priority (low/medium/high), due_date
Functionality:
- Registration and authorization (JWT)
- CRUD for teams, projects, tasks
- Task assignment to team members
- Filter tasks by status, assignee, priority
- Pagination for all lists
- Soft delete for tasks
Practical Case: Internal B2B Product
Task: startup wanted MVP backend for service marketplace in 2 weeks. 8 main entities, 45+ endpoints.
Generation:
- 2-page product description → APISpec (30 seconds)
- FastAPI code generation (7 files + tests) → 8 minutes
- Manual customization of authorization business logic → 3 days
- Payment gateway integration → 2 days
Result: working MVP in 5 days instead of planned 14. Test coverage 67% (tests auto-generated).
What AI generates well: CRUD, pagination, validation, project structure, happy path tests.
What needs hands-on: complex authorization logic, specific pricing algorithms, non-trivial SQL queries with window functions.
Timeline
- Generator prototype (description → one file): 2–3 days
- Full project generation with tests: 1–2 weeks
- Support additional frameworks (Express, Django REST): +1 week each
- CI/CD integration for regeneration on spec changes: 1 week







