AgentStack
SKILL verified MIT Self-run

Api Design

skill-marvinrichter-clarc-api-design · by marvinrichter

REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs.

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-marvinrichter-clarc-api-design

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Api Design? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

API Design Patterns

Conventions and best practices for designing consistent, developer-friendly REST APIs.

When to Activate

  • Before writing any implementation — design the contract first, code second
  • Designing new API endpoints or changing existing ones
  • Reviewing existing API contracts for consistency
  • Adding pagination, filtering, or sorting — see skill api-pagination-filtering
  • Implementing error handling for APIs
  • Planning API versioning strategy
  • Building public or partner-facing APIs

> For the full Contract-First workflow (spec writing, code generation, CI breaking-change detection, Pact): see skill api-contract. > For API documentation production — platform choice (Mintlify, Docusaurus, Redoc, Scalar), OpenAPI descriptions/examples, interactive playground, changelog automation, Vale prose linting, and Divio structure: see skill api-docs-patterns.

Contract-First Principle

Write the OpenAPI spec before writing any implementation code.

@startuml
:Write spec\napi/v1/openapi.yaml;
note right
  REST → OpenAPI 3.1
  Events → AsyncAPI 3.0
end note
:Lint spec\nspectral lint;
:Generate types / stubs\nfrom spec;
note right
  TS: openapi-typescript
  Go: oapi-codegen
  Java: openapi-generator
  Python: datamodel-codegen
end note
:Implement business logic only;
:Validate in CI\nschemathesis + oasdiff;
@enduml

The spec is the public contract. Consumers depend on it. Code is a private implementation detail.

  • Never generate the spec from code (annotations, reflection) — it will drift
  • Never write request/response types by hand — generate them from the spec
  • Any breaking change requires a new API version (/api/v2/)

See skill api-contract for the complete toolchain and CI setup.

Write descriptions and examples from the start

OpenAPI documentation is easiest to write while you are designing the spec — not after the implementation is shipped.

Minimum documentation requirements per operation (add these when you write each path, not later):

paths:
  /orders:
    post:
      summary: Create an order        # ← one-line summary
      description: |                  # ← full description with scope, side effects, notes
        Places a new order for the authenticated customer.
        The order is created in `pending` status and transitions to
        `processing` once payment is confirmed (async, webhook fired).

        **Scopes required:** `orders:write`
      operationId: createOrder        # ← stable, unique identifier
      tags: [Orders]                  # ← logical grouping
      parameters: []                  # ← every param needs description + example
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            example:                  # ← concrete, realistic example
              customer_id: "cust_abc123"
              items:
                - product_id: "prod_xyz"
                  quantity: 2
      responses:
        '201':
          description: Order created successfully.
          headers:
            Location:
              description: URL of the newly created order.
              schema: { type: string }
        '400':
          description: Request validation failed.
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }

> See skill api-docs-patterns for the full documentation workflow: platform setup, interactive playground, code examples in all languages, changelog automation, and CI.


Resource Design

URL Structure

# Resources are nouns, plural, lowercase, kebab-case
GET    /api/v1/users
GET    /api/v1/users/:id
POST   /api/v1/users
PUT    /api/v1/users/:id
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

# Sub-resources for relationships
GET    /api/v1/users/:id/orders
POST   /api/v1/users/:id/orders

# Actions that don't map to CRUD (use verbs sparingly)
POST   /api/v1/orders/:id/cancel
POST   /api/v1/auth/login
POST   /api/v1/auth/refresh

Naming Rules

# GOOD
/api/v1/team-members          # kebab-case for multi-word resources
/api/v1/orders?status=active  # query params for filtering
/api/v1/users/123/orders      # nested resources for ownership

# BAD
/api/v1/getUsers              # verb in URL
/api/v1/user                  # singular (use plural)
/api/v1/team_members          # snake_case in URLs
/api/v1/users/123/getOrders   # verb in nested resource

HTTP Methods and Status Codes

Method Semantics

| Method | Idempotent | Safe | Use For | |--------|-----------|------|---------| | GET | Yes | Yes | Retrieve resources | | POST | No | No | Create resources, trigger actions | | PUT | Yes | No | Full replacement of a resource | | PATCH | No* | No | Partial update of a resource | | DELETE | Yes | No | Remove a resource |

*PATCH can be made idempotent with proper implementation

Status Code Reference

# Success
200 OK                    — GET, PUT, PATCH (with response body)
201 Created               — POST (include Location header)
204 No Content            — DELETE, PUT (no response body)

# Client Errors
400 Bad Request           — Validation failure, malformed JSON
401 Unauthorized          — Missing or invalid authentication
403 Forbidden             — Authenticated but not authorized
404 Not Found             — Resource doesn't exist
409 Conflict              — Duplicate entry, state conflict
422 Unprocessable Entity  — Semantically invalid (valid JSON, bad data)
429 Too Many Requests     — Rate limit exceeded

# Server Errors
500 Internal Server Error — Unexpected failure (never expose details)
502 Bad Gateway           — Upstream service failed
503 Service Unavailable   — Temporary overload, include Retry-After

Common Mistakes

# BAD: 200 for everything
{ "status": 200, "success": false, "error": "Not found" }

# GOOD: Use HTTP status codes semantically + RFC 7807 body
HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "User abc-123 not found.",
  "instance": "/api/v1/users/abc-123"
}

# BAD: 500 for validation errors
# GOOD: 400 or 422 with field-level details (RFC 7807 errors extension)

# BAD: 200 for created resources
# GOOD: 201 with Location header
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123

Response Format

Success Response

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

Collection Response (with Pagination)

{
  "data": [
    { "id": "abc-123", "name": "Alice" },
    { "id": "def-456", "name": "Bob" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/api/v1/users?page=1&per_page=20",
    "next": "/api/v1/users?page=2&per_page=20",
    "last": "/api/v1/users?page=8&per_page=20"
  }
}

Error Response — RFC 7807 / RFC 9457 Problem Details

All error responses MUST use Content-Type: application/problem+json and the standard fields:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-failed",
  "title": "Validation Failed",
  "status": 400,
  "detail": "One or more fields failed validation.",
  "instance": "/api/v1/users",
  "errors": [
    { "field": "email", "detail": "must be a valid email address" },
    { "field": "age",   "detail": "must be between 0 and 150" }
  ]
}

| Field | Required | Description | |---|---|---| | type | Recommended | URI identifying the problem type. about:blank if no docs exist yet. | | title | Recommended | Stable, human-readable summary (don't interpolate dynamic data). | | status | Yes | HTTP status code mirrored in the body. | | detail | Optional | Occurrence-specific explanation for the client. | | instance | Optional | URI of this specific occurrence (e.g., request path or ID). | | errors | Extension | RFC 9457 array for multiple sub-problems (validation errors). |

See skill: problem-details for full specification and per-language implementation.

Response Envelope Variants

// Success responses: return the resource (or data wrapper for public APIs)
interface ApiResponse {
  data: T;
  meta?: PaginationMeta;
  links?: PaginationLinks;
}

// Error responses: always RFC 7807 ProblemDetails — never { success: false, error: "..." }
interface ProblemDetails {
  type: string;         // URI — link to docs
  title: string;        // Short, stable summary
  status: number;       // HTTP status mirrored
  detail?: string;      // Occurrence-specific detail
  instance?: string;    // URI of this occurrence
  [key: string]: unknown; // Extension fields
}
// Content-Type for errors: application/problem+json (NOT application/json)

> For pagination (offset/cursor), filtering, sorting, and sparse fieldsets — see skill api-pagination-filtering.

Authentication and Authorization

Token-Based Auth

# Bearer token in Authorization header
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API key (for server-to-server)
GET /api/v1/data
X-API-Key: sk_live_abc123

Authorization Patterns

// Resource-level: check ownership
app.get("/api/v1/orders/:id", async (req, res) => {
  const order = await Order.findById(req.params.id);
  if (!order) return res.status(404).json({ error: { code: "not_found" } });
  if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
  return res.json({ data: order });
});

// Role-based: check permissions
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
  await User.delete(req.params.id);
  return res.status(204).send();
});

Rate Limiting

Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000

# When exceeded — RFC 7807 body
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60

{
  "type": "https://api.example.com/problems/too-many-requests",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Try again in 60 seconds.",
  "retryAfter": 60
}

Rate Limit Tiers

| Tier | Limit | Window | Use Case | |------|-------|--------|----------| | Anonymous | 30/min | Per IP | Public endpoints | | Authenticated | 100/min | Per user | Standard API access | | Premium | 1000/min | Per API key | Paid API plans | | Internal | 10000/min | Per service | Service-to-service |

Versioning

URL Path Versioning (Recommended)

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

Pros: Explicit, easy to route, cacheable Cons: URL changes between versions

Header Versioning

GET /api/users
Accept: application/vnd.myapp.v2+json

Pros: Clean URLs Cons: Harder to test, easy to forget

Versioning Strategy

1. Start with /api/v1/ — don't version until you need to
2. Maintain at most 2 active versions (current + previous)
3. Deprecation timeline:
   - Announce deprecation (6 months notice for public APIs)
   - Add Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
   - Return 410 Gone after sunset date
4. Non-breaking changes don't need a new version:
   - Adding new fields to responses
   - Adding new optional query parameters
   - Adding new endpoints
5. Breaking changes require a new version:
   - Removing or renaming fields
   - Changing field types
   - Changing URL structure
   - Changing authentication method

For per-language implementation patterns (TypeScript, Go), anti-patterns, and the full API Design Checklist, see skill api-design-advanced.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.