ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-04-08Advanced

Mastering API-First Development with Antigravity — Auto-Generate Code, Tests & Docs from OpenAPI Specs

Learn how to leverage Antigravity's AI agents to build a complete API-first development workflow — from OpenAPI specification design to automated code generation, testing, and documentation.

antigravity436openapiapi-firstcode-generation3swaggertypescript27testing17documentation8

Setup and context — Why API-First Development Matters

In modern software development, parallel frontend and backend development is the norm. Yet when API contracts are shared only through verbal agreements or informal documentation, integration testing inevitably reveals a wave of mismatches and bugs.

API-first development means defining the API contract before writing any implementation code, then using that contract as the single source of truth throughout the development lifecycle. By centering your workflow around an OpenAPI specification, you can derive backend stubs, frontend type definitions, test cases, and published documentation — all from one specification file.

Antigravity's AI agents are exceptionally well-suited to this workflow. This article walks you through the complete API-first development pipeline: from designing OpenAPI specs, to automated code generation, contract testing, documentation deployment, and CI/CD integration.

If you're interested in API fundamentals and serverless architectures, check out Building Serverless APIs at Lightning Speed with Antigravity — Hono + Cloudflare Workers Practical Guide as well.

OpenAPI Specification Fundamentals and Design Principles

The Structure of OpenAPI 3.1

OpenAPI 3.1 achieved full JSON Schema compatibility and is the de facto standard as of 2026. Here's the basic structure:

# openapi.yaml — Your project's API specification
openapi: "3.1.0"
info:
  title: "My SaaS API"
  version: "1.0.0"
  description: "Core API handling user management and billing"
servers:
  - url: "https://api.example.com/v1"
    description: "Production"
  - url: "http://localhost:3000/v1"
    description: "Local development"
paths:
  /users:
    get:
      operationId: listUsers
      summary: "List all users"
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: "Success"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserListResponse"
components:
  schemas:
    User:
      type: object
      required: [id, email, createdAt]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        createdAt:
          type: string
          format: date-time
    UserListResponse:
      type: object
      properties:
        users:
          type: array
          items:
            $ref: "#/components/schemas/User"
        total:
          type: integer
        page:
          type: integer

Design Principle: Think from the Consumer's Perspective

The key to great API design is adopting the consumer's viewpoint rather than the implementor's. When asking Antigravity's agent to draft a specification, including these principles in your prompt significantly improves quality:

  • Resource-oriented: URL paths use nouns (/users, /orders) while actions are expressed through HTTP methods
  • Consistent naming: operationId follows a verb + resource convention like listUsers, getUser, createUser
  • Standardized errors: Define a common error structure using RFC 7807 (Problem Details) across all endpoints
  • Pagination: Unify on either cursor-based or offset-based pagination and reuse definitions from components/parameters

Crafting OpenAPI Specs Efficiently with Antigravity

AI Agent-Driven Spec Generation

The most efficient approach is to use Antigravity's Manager Surface to delegate spec creation to an agent:

# Antigravity prompt example
Create an OpenAPI 3.1 specification in YAML based on these requirements:
 
## Service Overview
- E-commerce product management API
- Authentication via Bearer token (JWT)
- Cursor-based pagination
 
## Endpoints
1. List products (with filtering and sorting)
2. Get product details
3. Create product (admin only)
4. Update product (admin only)
5. Delete product (admin only)
 
## Constraints
- Error responses follow RFC 7807
- All responses include rate-limit headers
- Use components/schemas for schema reuse

Antigravity's agent references existing code in your project context, so if you already have model definitions (Prisma schemas, TypeORM entities, etc.), it automatically generates specifications consistent with them.

Inline Editing in Editor View

For fine-tuning spec details, Editor View's inline AI commands are invaluable. Open the spec file, place your cursor on a specific path or schema, and trigger Ctrl+I (inline command):

# Inline command example
"Add rate limiting response headers (X-RateLimit-Limit,
X-RateLimit-Remaining, X-RateLimit-Reset) to this endpoint"

The AI understands the schema context and automatically adds header definitions to components/headers, creating $ref references from each response.

Building the Code Generation Pipeline

TypeScript Backend (Hono + Zod) Auto-Generation

Here's how to build a pipeline that generates TypeScript server code from your OpenAPI spec, combining openapi-typescript and openapi-zod-client:

# Install required packages
npm install openapi-typescript openapi-zod-client -D
 
# Generate type definitions
npx openapi-typescript openapi.yaml -o src/generated/api-types.ts
 
# Generate Zod schemas
npx openapi-zod-client openapi.yaml -o src/generated/api-schemas.ts

Here's how to use the generated types in a Hono router:

// src/routes/users.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { listUsersQuerySchema, createUserBodySchema } from "../generated/api-schemas";
import type { paths } from "../generated/api-types";
 
// Type-safe response type
type ListUsersResponse =
  paths["/users"]["get"]["responses"]["200"]["content"]["application/json"];
 
const users = new Hono();
 
users.get(
  "/",
  zValidator("query", listUsersQuerySchema),
  async (c) => {
    const { page, limit } = c.req.valid("query");
    // Fetch users from DB (Prisma example)
    const [items, total] = await Promise.all([
      prisma.user.findMany({
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { createdAt: "desc" },
      }),
      prisma.user.count(),
    ]);
    // Response is compatible with ListUsersResponse type
    const response: ListUsersResponse = {
      users: items,
      total,
      page,
    };
    return c.json(response);
  }
);
 
export default users;

Frontend API Client Auto-Generation

On the frontend side, openapi-fetch provides a type-safe API client generated from your spec:

// src/lib/api-client.ts
import createClient from "openapi-fetch";
import type { paths } from "../generated/api-types";
 
// Type-safe API client
const client = createClient<paths>({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,
  headers: {
    Authorization: `Bearer ${getAccessToken()}`,
  },
});
 
// Usage — paths, parameters, and responses are all type-safe
async function fetchUsers(page: number = 1) {
  const { data, error } = await client.GET("/users", {
    params: {
      query: { page, limit: 20 },
    },
  });
  if (error) {
    // error type is also inferred from the OpenAPI spec
    throw new ApiError(error);
  }
  // data.users — inferred as User[]
  return data;
}

The biggest advantage: updating your OpenAPI spec and regenerating types automatically synchronizes backend and frontend types.

Automated Test Generation with Antigravity Agents

Auto-Generating API Tests

Feed your OpenAPI spec to Antigravity's agent and have it generate comprehensive test suites:

# Antigravity prompt example
Generate Vitest test cases for every endpoint in openapi.yaml
covering these scenarios:
 
1. Happy path (200/201 responses)
2. Validation errors (400)
3. Authentication errors (401)
4. Authorization errors (403)
5. Not found (404)
6. Rate limiting (429)
 
Include clear descriptions and thorough assertions for each test.

Here's an example of the generated test code:

// tests/api/users.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { createTestClient } from "../helpers/test-client";
 
describe("GET /users", () => {
  const client = createTestClient();
 
  it("returns paginated user list", async () => {
    const res = await client.get("/users?page=1&limit=10");
    expect(res.status).toBe(200);
    expect(res.body.users).toBeInstanceOf(Array);
    expect(res.body.users.length).toBeLessThanOrEqual(10);
    expect(res.body.total).toBeTypeOf("number");
    expect(res.body.page).toBe(1);
  });
 
  it("returns 400 when limit exceeds maximum", async () => {
    const res = await client.get("/users?limit=999");
    expect(res.status).toBe(400);
    expect(res.body.type).toContain("validation-error");
    // Verify RFC 7807 error response format
    expect(res.body).toHaveProperty("title");
    expect(res.body).toHaveProperty("detail");
  });
 
  it("returns 401 without authentication", async () => {
    const unauthClient = createTestClient({ auth: false });
    const res = await unauthClient.get("/users");
    expect(res.status).toBe(401);
  });
});
 
describe("POST /users", () => {
  const adminClient = createTestClient({ role: "admin" });
 
  it("allows admin to create a user", async () => {
    const res = await adminClient.post("/users", {
      body: {
        email: "new@example.com",
        name: "New User",
      },
    });
    expect(res.status).toBe(201);
    expect(res.body.id).toBeDefined();
    expect(res.body.email).toBe("new@example.com");
  });
 
  it("returns 403 for non-admin users", async () => {
    const userClient = createTestClient({ role: "user" });
    const res = await userClient.post("/users", {
      body: { email: "test@example.com", name: "Test" },
    });
    expect(res.status).toBe(403);
  });
});

Contract Testing — Detecting Spec-Implementation Drift

The most critical aspect of API-first development is contract testing that guarantees your spec and implementation always match:

// tests/contract/openapi-contract.test.ts
import { describe, it, expect } from "vitest";
import SwaggerParser from "@apidevtools/swagger-parser";
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { createTestClient } from "../helpers/test-client";
 
describe("OpenAPI Contract Tests", () => {
  let api: any;
  let ajv: Ajv;
 
  beforeAll(async () => {
    // Parse the OpenAPI spec
    api = await SwaggerParser.dereference("openapi.yaml");
    ajv = new Ajv({ allErrors: true, strict: false });
    addFormats(ajv);
  });
 
  it("GET /users response matches the specification", async () => {
    const client = createTestClient();
    const res = await client.get("/users");
 
    // Extract schema from spec
    const schema =
      api.paths["/users"].get.responses["200"]
        .content["application/json"].schema;
 
    // Validate response against spec
    const validate = ajv.compile(schema);
    const valid = validate(res.body);
 
    if (!valid) {
      console.error("Schema violations:", validate.errors);
    }
    expect(valid).toBe(true);
  });
});

Integrating these contract tests into your CI pipeline automatically catches "updated the spec but forgot the implementation" and "changed the implementation but forgot the spec" scenarios.

Auto-Generating and Publishing API Documentation

Integrating Redoc / Swagger UI

Here's how to auto-generate beautiful API documentation from your spec and deploy it:

// src/app/api/docs/route.ts — Serve docs via Next.js API Route
import { readFileSync } from "fs";
import path from "path";
 
const specYaml = readFileSync(
  path.join(process.cwd(), "openapi.yaml"),
  "utf-8"
);
 
export async function GET() {
  const html = `<!DOCTYPE html>
<html>
<head>
  <title>API Documentation</title>
  <meta charset="utf-8" />
  <link href="https://fonts.googleapis.com/css?family=Inter:300,400,600" rel="stylesheet">
  <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</head>
<body>
  <div id="redoc-container"></div>
  <script>
    Redoc.init('/api/docs/spec', {
      theme: {
        colors: { primary: { main: '#1a73e8' } },
        typography: { fontFamily: 'Inter, sans-serif' },
      },
      hideDownloadButton: false,
      expandResponses: '200,201',
    }, document.getElementById('redoc-container'));
  </script>
</body>
</html>`;
  return new Response(html, {
    headers: { "Content-Type": "text/html" },
  });
}

Improving Documentation Quality with Antigravity Agents

A prompt pattern for enhancing your spec's documentation quality:

# Antigravity prompt example
Improve the following aspects of openapi.yaml:
 
1. Add descriptions to all endpoints (including use cases)
2. Add example values to all parameters
3. Add realistic examples to all responses
4. Group endpoints using tags
5. Enrich security scheme descriptions

CI/CD Pipeline Integration

Automated Validation Pipeline with GitHub Actions

A pipeline that automatically triggers type generation, testing, and documentation builds whenever your OpenAPI spec changes:

# .github/workflows/api-first.yaml
name: API First Pipeline
on:
  push:
    paths:
      - "openapi.yaml"
      - "src/**"
  pull_request:
    paths:
      - "openapi.yaml"
 
jobs:
  validate-and-generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
 
      - run: npm ci
 
      # 1. Validate OpenAPI spec
      - name: Validate OpenAPI spec
        run: npx @redocly/cli lint openapi.yaml
 
      # 2. Regenerate TypeScript types
      - name: Generate TypeScript types
        run: npx openapi-typescript openapi.yaml -o src/generated/api-types.ts
 
      # 3. Regenerate Zod schemas
      - name: Generate Zod schemas
        run: npx openapi-zod-client openapi.yaml -o src/generated/api-schemas.ts
 
      # 4. Verify generated files are committed
      - name: Check for uncommitted generated files
        run: |
          git diff --exit-code src/generated/ || {
            echo "❌ Generated files are out of date!"
            echo "Run: npm run generate:api and commit the changes"
            exit 1
          }
 
      # 5. Type checking
      - name: TypeScript type check
        run: npx tsc --noEmit
 
      # 6. Contract tests
      - name: Run contract tests
        run: npx vitest run tests/contract/
 
      # 7. API tests
      - name: Run API tests
        run: npx vitest run tests/api/

Breaking Change Detection

Catch backward-incompatible API changes at the PR stage:

# Additional GitHub Actions step
      - name: Detect breaking changes
        if: github.event_name == 'pull_request'
        run: |
          # Fetch the base branch spec
          git fetch origin main
          git show origin/main:openapi.yaml > /tmp/openapi-base.yaml
          # Detect incompatible changes
          npx @redocly/cli diff /tmp/openapi-base.yaml openapi.yaml \
            --fail-on-incompatible

This configuration automatically blocks PRs that introduce breaking changes like removing required response fields or changing endpoint URLs.

Practical Design Patterns

Pattern 1: Versioning Strategy

# URL path-based versioning
servers:
  - url: "https://api.example.com/v1"
  - url: "https://api.example.com/v2"
 
# Header-based versioning
paths:
  /users:
    get:
      parameters:
        - name: API-Version
          in: header
          schema:
            type: string
            enum: ["2026-01-01", "2026-04-01"]
            default: "2026-04-01"

Pattern 2: Standardized Error Responses (RFC 7807)

components:
  schemas:
    ProblemDetail:
      type: object
      required: [type, title, status]
      properties:
        type:
          type: string
          format: uri
          description: "URI identifying the error type"
        title:
          type: string
          description: "Human-readable error title"
        status:
          type: integer
          description: "HTTP status code"
        detail:
          type: string
          description: "Detailed error description"
        instance:
          type: string
          format: uri
          description: "URI for this specific error occurrence"
        errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string

Pattern 3: Cursor-Based Pagination

# Reusable definitions in components/parameters
components:
  parameters:
    CursorParam:
      name: cursor
      in: query
      description: "Cursor value marking the start of the next page"
      schema:
        type: string
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        default: 20
        minimum: 1
        maximum: 100
  schemas:
    PaginationMeta:
      type: object
      properties:
        hasNext:
          type: boolean
        nextCursor:
          type: string
          nullable: true
        total:
          type: integer

Summary

API-first development may seem like extra upfront work, but as development progresses, that investment yields exponential returns. By leveraging Antigravity's AI agents, you can rapidly iterate through spec creation, type generation, automated testing, and documentation publishing — fundamentally solving the eternal challenge of spec-implementation drift.

For deeper dives into security and authentication patterns, see Building Secure API Backends with Antigravity Agents — Authentication, Validation & Rate Limiting Patterns. If you're interested in comparing REST with GraphQL, check out Antigravity × GraphQL + Apollo Server Practical Tutorial as well.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-06-30
When Every Antigravity-Written Test Is Green but the Same Bug Comes Back — Field Notes on Measuring Hollow Assertions
Your AI-written tests all pass, coverage is high, yet the same defect returns to production. The cause is over-mocking and tautological assertions. These are field notes on using mutation testing as ground truth to measure what your tests actually protect, and to fix it operationally.
App Dev2026-05-04
Swift Testing × Antigravity — Beyond XCTest to AI-Driven Test Design
A comprehensive guide to transforming iOS test quality using Swift Testing and Antigravity together. Covers @Test and #expect macros in practice, parameterized testing, async patterns, XCTest migration strategy, and AI-powered test generation.
App Dev2026-05-03
Building Idempotency Keys and Dedupe Stores in TypeScript with Antigravity
A production guide to designing idempotency keys and dedupe stores in TypeScript with Antigravity — covering Stripe webhook retries, Temporal replays, and the Cloudflare KV / Redis / Postgres trade-offs you actually need to choose between.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →