# API Designer

> This skill should be used when "design an API", "create REST endpoints", "design API structure", "API best practices", "how should I design this endpoint", "REST API conventions", "API error handling", "API versioning", "design a webhook".

- Skill: `kwokyc/api-designer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kwokyc/api-designer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kwokyc/api-designer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: KwokYC (https://skillmd.com/u/kwokyc)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/kwokyc/api-designer

---


# API Designer

Design clean, consistent REST APIs. Follow established conventions. Provide predictable interfaces.

## URL Structure

### Resource Naming

```
✅ Use nouns, not verbs:
GET    /users          (not /getUsers)
GET    /users/123      (not /getUserById/123)
POST   /users          (not /createUser)
PUT    /users/123      (not /updateUser)
DELETE /users/123      (not /deleteUser)

✅ Use plural nouns:
/users, /orders, /products

❌ Not singular:
/user, /order, /product

✅ Nest for relationships:
/users/123/orders       (orders belonging to user 123)
/users/123/orders/456   (specific order)

❌ Don't nest more than 2 levels:
/users/123/orders/456/items/789/comments
✅ Use: /order-items/789/comments
```

### Versioning

```
✅ URL path versioning (simplest, most common):
/api/v1/users
/api/v2/users

✅ Header versioning (cleaner URLs):
Accept: application/vnd.myapi.v1+json

❌ Query parameter versioning:
/api/users?version=1
```

## HTTP Methods & Status Codes

### Method Semantics

| Method | Idempotent | Purpose | Returns |
|--------|-----------|---------|---------|
| GET | Yes | Read resource | 200 |
| POST | No | Create resource | 201 |
| PUT | Yes | Replace resource entirely | 200 |
| PATCH | No | Partial update | 200 |
| DELETE | Yes | Remove resource | 204 |

### Status Codes

```
200 OK                  - Successful GET, PUT, PATCH
201 Created             - Successful POST (resource created)
204 No Content          - Successful DELETE, or PUT with no body
400 Bad Request         - Invalid input / validation error
401 Unauthorized        - Not authenticated
403 Forbidden           - Authenticated but not authorized
404 Not Found           - Resource doesn't exist
409 Conflict            - Duplicate resource, version conflict
422 Unprocessable Entity - Valid JSON but semantic error
429 Too Many Requests   - Rate limited
500 Internal Server Error - Server bug
```

```
❌ Wrong status codes:
200 for errors with {"error": "not found"}
200 for creation without returning the created resource
404 for "user has no orders" (should be 200 with empty array)
500 for validation errors

✅ Correct:
POST /users with invalid data → 400
GET /users/999 (doesn't exist) → 404
POST /users (duplicate email) → 409
GET /users/123/orders (no orders) → 200 {"data": []}
```

## Request & Response Format

### Success Response

```json
{
  "data": {
    "id": "usr_abc123",
    "name": "Alice",
    "email": "alice@example.com",
    "created_at": "2025-01-15T10:30:00Z"
  }
}
```

### Collection Response with Pagination

```json
{
  "data": [
    { "id": "usr_1", "name": "Alice" },
    { "id": "usr_2", "name": "Bob" }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 45,
    "total_pages": 3
  }
}
```

### Error Response

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "value": "not-an-email"
      },
      {
        "field": "age",
        "message": "Must be between 0 and 150",
        "value": -5
      }
    ]
  }
}
```

```
❌ Bad error responses:
{"error": "bad request"}                    // No details
{"message": "Something went wrong"}         // No error code
{"errors": ["field1 is bad", "field2 bad"]} // No structure
<html>500 Internal Server Error</html>      // HTML for API

✅ Good error response:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [{ "field": "email", "message": "Invalid format" }]
  }
}
```

## Filtering, Sorting, Pagination

### Filtering

```
GET /users?status=active&role=admin
GET /orders?created_after=2025-01-01&total_gte=100
GET /products?category=electronics&in_stock=true
```

### Sorting

```
GET /users?sort=created_at        (ascending, default)
GET /users?sort=-created_at       (descending, prefix with -)
GET /users?sort=role,-created_at  (multiple fields)
```

### Pagination

```
✅ Page-based (simple):
GET /users?page=2&per_page=20

✅ Cursor-based (for real-time data):
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
Response includes: "next_cursor": "eyJpZCI6MTIwfQ"
```

## Authentication Patterns

```
✅ Bearer token (most common):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

✅ API key (for server-to-server):
X-API-Key: abc123def456

❌ Don't put tokens in URLs:
/api/users?token=abc123
```

## Rate Limiting Headers

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705312800
Retry-After: 60  (when 429)
```

## Idempotency

For non-idempotent operations that need safety:

```
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

// Server stores key → response mapping
// Same key returns same response without re-executing
```

## Webhook Design

```
POST to registered URL with:

{
  "id": "evt_abc123",
  "type": "order.completed",
  "created_at": "2025-01-15T10:30:00Z",
  "data": {
    "order_id": "ord_xyz789",
    "total": 99.99
  }
}

Include:
- Signature header: X-Webhook-Signature: sha256=...
- Retry with exponential backoff (3 attempts)
- Idempotent event processing (include event ID)
```

## Naming Conventions

```
JSON fields: snake_case (most common in APIs)
  {"user_id": 123, "created_at": "..."}

Dates: ISO 8601 with timezone
  "2025-01-15T10:30:00Z"
  NOT: "01/15/2025", "Jan 15", 1705312800

IDs: Prefixed strings for type safety
  "usr_abc123", "ord_xyz789", "pay_def456"
  NOT: 123, 456 (ambiguous type)

Booleans: is_ / has_ prefix
  {"is_active": true, "has_permission": false}

Money: Integer cents, not float
  {"amount_cents": 9999, "currency": "USD"}
  NOT: {"amount": 99.99} (floating point error)
```

## Decision Checklist

Before finalizing an API design:

- [ ] URLs use plural nouns, no verbs
- [ ] Correct HTTP method for each operation
- [ ] Appropriate status codes (not 200 for everything)
- [ ] Consistent error response format
- [ ] Pagination on list endpoints
- [ ] Filtering and sorting support
- [ ] Authentication required on sensitive endpoints
- [ ] Rate limiting configured
- [ ] Input validation with clear error messages
- [ ] No sensitive data in URLs (tokens, passwords)
- [ ] CORS configured for browser clients
- [ ] API versioning strategy defined

