Awesome-omni-skill api-design

Design and review REST/GraphQL APIs with best practices. Use when creating endpoints, reviewing API architecture, designing data contracts, route handlers, Server Actions, webhooks, or when user mentions "API", "endpoint", "REST", "GraphQL", "route handler", "request/response", or "HTTP methods".

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/api-design-kensaurus" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-design-19afe8 && rm -rf "$T"
manifest: skills/development/api-design-kensaurus/SKILL.md
source content

API Design Skill

Systematic methodology for designing, reviewing, and improving APIs following industry best practices.

When to Use

  • Designing new API endpoints
  • Reviewing existing API architecture
  • Creating data contracts between services
  • Standardizing API patterns across a codebase
  • Migrating or versioning APIs

CRITICAL: Check Existing First

Before creating ANY new endpoint, verify:

  1. Search for existing endpoints:
rg "export.*GET|POST|PATCH|DELETE" app/api/ --type ts
ls -la app/api/
  1. Check for existing Server Actions:
rg "'use server'" --type ts -l
rg "export async function" src/features/*/server/
  1. Check existing RPC functions (Supabase):
SELECT proname FROM pg_proc WHERE pronamespace = 'public'::regnamespace;
  1. Review existing patterns:
  • Check
    app/api/
    folder structure
  • Look at existing error handling patterns
  • Verify authentication patterns used

Why: Duplicate endpoints cause routing conflicts, inconsistent behavior, and maintenance nightmares. Always search before creating.

API Design Principles

1. RESTful Resource Design

URL Structure:

GET    /resources          → List resources
GET    /resources/:id      → Get single resource
POST   /resources          → Create resource
PATCH  /resources/:id      → Partial update
PUT    /resources/:id      → Full replace
DELETE /resources/:id      → Delete resource

# Nested resources
GET    /users/:id/posts    → User's posts
POST   /users/:id/posts    → Create post for user

# Actions (when CRUD doesn't fit)
POST   /orders/:id/cancel  → Cancel order
POST   /users/:id/verify   → Verify user

Naming Conventions:

  • Use plural nouns for resources (
    /users
    , not
    /user
    )
  • Use kebab-case for multi-word (
    /user-profiles
    )
  • Avoid verbs in URLs (let HTTP methods convey action)
  • Keep URLs shallow (max 2-3 levels deep)

2. Request/Response Design

Request Structure:

// Query params for filtering/sorting/pagination
GET /products?category=electronics&sort=-price&page=2&limit=20

// Body for mutations
POST /products
{
  "name": "Widget",
  "price": 29.99,
  "categoryId": "cat_123"
}

// Partial updates with PATCH
PATCH /products/123
{
  "price": 24.99  // Only changed fields
}

Response Structure (Consistent Envelope):

// Success response
{
  "data": { /* resource or array */ },
  "meta": {
    "pagination": { "page": 1, "limit": 20, "total": 100 }
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "field": "email", "message": "Invalid email format" }
    ]
  }
}

3. HTTP Status Codes

CodeMeaningWhen to Use
200OKSuccessful GET, PATCH, PUT
201CreatedSuccessful POST
204No ContentSuccessful DELETE
400Bad RequestInvalid input, validation error
401UnauthorizedMissing/invalid auth
403ForbiddenAuth valid but no permission
404Not FoundResource doesn't exist
409ConflictDuplicate, state conflict
422UnprocessableSemantic validation error
429Too Many RequestsRate limited
500Internal ErrorServer error (never expose details)

4. Pagination Patterns

Offset-based (simple but slow at scale):

GET /posts?page=5&limit=20

{
  "data": [...],
  "meta": {
    "pagination": {
      "page": 5,
      "limit": 20,
      "total": 543,
      "totalPages": 28
    }
  }
}

Cursor-based (better for large datasets):

GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20

{
  "data": [...],
  "meta": {
    "pagination": {
      "nextCursor": "eyJpZCI6MTQzfQ",
      "prevCursor": "eyJpZCI6MTAzfQ",
      "hasMore": true
    }
  }
}

5. Filtering & Sorting

// Filtering
GET /products?status=active&minPrice=10&maxPrice=100
GET /products?category[in]=electronics,books
GET /products?createdAt[gte]=2024-01-01

// Sorting
GET /products?sort=price         // Ascending
GET /products?sort=-price        // Descending
GET /products?sort=-createdAt,name  // Multiple

// Field selection (reduce payload)
GET /users?fields=id,name,email

6. Versioning Strategies

URL versioning (recommended for breaking changes):

/api/v1/users
/api/v2/users

Header versioning (cleaner URLs):

Accept: application/vnd.api+json; version=2

When to version:

  • Breaking changes to response structure
  • Removing fields or endpoints
  • Changing field types
  • NOT for additive changes (new optional fields)

7. Authentication Patterns

Bearer Token (JWT):

headers: {
  'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...'
}

API Key (service-to-service):

headers: {
  'X-API-Key': 'sk_live_abc123...'
}

Security Checklist:

  • Auth on every endpoint (except public)
  • Rate limiting per user/IP
  • Request validation before processing
  • No sensitive data in URLs (use headers/body)
  • HTTPS only

8. Error Handling

Consistent Error Schema:

interface ApiError {
  error: {
    code: string           // Machine-readable (VALIDATION_ERROR)
    message: string        // Human-readable
    details?: ErrorDetail[] // Field-level errors
    requestId?: string     // For debugging
  }
}

interface ErrorDetail {
  field: string
  message: string
  code?: string
}

Implementation:

// Zod validation errors → API errors
function formatZodError(error: ZodError): ApiError {
  return {
    error: {
      code: 'VALIDATION_ERROR',
      message: 'Invalid request data',
      details: error.errors.map(e => ({
        field: e.path.join('.'),
        message: e.message,
      }))
    }
  }
}

9. Next.js Route Handler Patterns

// app/api/products/route.ts
import { z } from 'zod'
import { NextRequest, NextResponse } from 'next/server'

const CreateProductSchema = z.object({
  name: z.string().min(1).max(200),
  price: z.number().positive(),
})

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const validated = CreateProductSchema.parse(body)
    
    const product = await db.product.create({ data: validated })
    
    return NextResponse.json(
      { data: product },
      { status: 201 }
    )
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        formatZodError(error),
        { status: 400 }
      )
    }
    return NextResponse.json(
      { error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } },
      { status: 500 }
    )
  }
}

10. Server Actions vs Route Handlers (Next.js 15+)

Use CaseRecommendation
Internal mutationsServer Actions
Webhooks from external servicesRoute Handlers
Third-party API proxyRoute Handlers
File uploadsRoute Handlers (or Server Actions with
unstable_after
)
Streaming responsesRoute Handlers
Form submissionsServer Actions
Background tasksServer Actions +
after()
from next/server

Server Action Example (Next.js 15):

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'
import { after } from 'next/server'

export async function createProduct(formData: FormData) {
  const product = await db.product.create({
    data: { name: formData.get('name') }
  })
  
  // Run after response (Next.js 15)
  after(async () => {
    await sendNotification(product.id)
  })
  
  revalidatePath('/products')
  return { success: true, data: product }
}

API Review Checklist

  • URLs follow RESTful conventions
  • HTTP methods used correctly
  • Status codes are appropriate
  • Request/response schemas defined (Zod)
  • Error responses consistent
  • Pagination implemented for lists
  • Authentication required where needed
  • Rate limiting in place
  • Input validation on all endpoints
  • No sensitive data in URLs
  • Versioning strategy defined
  • OpenAPI/Swagger docs generated

API Documentation Template

# openapi.yaml
openapi: 3.0.0
info:
  title: Product API
  version: 1.0.0

paths:
  /products:
    get:
      summary: List products
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        200:
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'