agentleFS
Sign inSign up

api-design

travisjneuman/.claude/skills/api-design/SKILL.md

REST and GraphQL API design best practices including OpenAPI specs. Use when designing APIs, documenting endpoints, or reviewing API architecture.

Skill99 starsChanged 7 months ago
  • Reads credentials

What's in it

  1. API Design Guide
  2. REST API Principles
  3. Resource Naming
  4. HTTP Methods
  5. Status Codes
  6. Response Format
  7. Pagination
  8. Filtering, Sorting, Fields
  9. Versioning
  10. OpenAPI Specification
  11. Authentication
  12. Rate Limiting
  13. Security Checklist
  14. gRPC and Protocol Buffers
  15. Proto Definition
  16. When to Use gRPC vs REST
  17. tRPC for TypeScript
  18. Webhook Design Patterns
  19. Webhook Payload
  20. Signature Verification
  21. Retry Strategy
  22. Idempotency
  23. API Versioning Strategies
---
name: api-design
description: REST and GraphQL API design best practices including OpenAPI specs. Use when designing APIs, documenting endpoints, or reviewing API architecture.
---

# API Design Guide

Best practices for designing developer-friendly, maintainable APIs.

## REST API Principles

### Resource Naming

**Use nouns, not verbs**:

```
✓ GET /users
✓ GET /users/123
✓ GET /users/123/orders

✗ GET /getUsers
✗ GET /fetchUserById
✗ POST /createNewOrder
```

**Use plural nouns**:

```
✓ /users
✓ /orders
✓ /products

✗ /user
✗ /order
```

**Hierarchical relationships**:

```
/users/{userId}/orders           # User's orders
/users/{userId}/orders/{orderId} # Specific order
```

---

### HTTP Methods

| Method | Purpose                   | Idempotent | Request Body |
| ------ | ------------------------- | ---------- | ------------ |
| GET    | Retrieve resource(s)      | Yes        | No           |
| POST   | Create resource           | No         | Yes          |
| PUT    | Replace resource entirely | Yes        | Yes          |
| PATCH  | Update resource partially | No         | Yes          |
| DELETE | Remove resource           | Yes        | No           |

---

### Status Codes

**Success (2xx)**:

- `200 OK` - Request succeeded
- `201 Created` - Resource created (return Location header)
- `204 No Content` - Success with no response body

**Client Error (4xx)**:

- `400 Bad Request` - Malformed request
- `401 Unauthorized` - Authentication required
- `403 Forbidden` - No permission
- `404 Not Found` - Resource doesn't exist
- `409 Conflict` - State conflict
- `422 Unprocessable Entity` - Validation failed
- `429 Too Many Requests` - Rate limited

**Server Error (5xx)**:

- `500 Internal Server Error` - Unexpected error
- `503 Service Unavailable` - Temporary outage

---

### Response Format

**Successful response**:

```json
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  }
}
```

**Collection response**:

```json
{
  "data": [
    { "id": "1", "name": "Item 1" },
    { "id": "2", "name": "Item 2" }
  ],
  "meta": {
    "page": 1,
    "perPage": 20,
    "total": 100,
    "totalPages": 5
  },
  "links": {
    "self": "/items?page=1",
    "next": "/items?page=2",
    "prev": null
  }
}
```

**Error response**:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address"
      }
    ]
  }
}
```

---

### Pagination

**Offset-based** (simple, but slow on large datasets):

```
GET /users?page=2&perPage=20
GET /users?offset=40&limit=20
```

**Cursor-based** (efficient, recommended):

```
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
```

Response includes next cursor:

```json
{
  "data": [...],
  "meta": {
    "nextCursor": "eyJpZCI6MTQzfQ",
    "hasMore": true
  }
}
```

---

### Filtering, Sorting, Fields

**Filtering**:

```
GET /users?status=active
GET /users?created_after=2024-01-01
GET /users?role=admin,moderator
```

**Sorting**:

```
GET /users?sort=name
GET /users?sort=-created_at         # Descending
GET /users?sort=status,-created_at  # Multiple fields
```

**Field selection**:

```
GET /users?fields=id,name,email
GET /users?include=orders,profile
```

---

### Versioning

**URL path** (recommended):

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

**Header**:

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

---

## OpenAPI Specification

```yaml
openapi: 3.0.3
info:
  title: My API
  version: 1.0.0
  description: API for managing users

servers:
  - url: https://api.example.com/v1

paths:
  /users:
    get:
      summary: List users
      tags: [Users]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        "200":
          description: List of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/User"

components:
  schemas:
    User:
      type: object
      required: [id, email]
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
```

---

## Authentication

**API Keys** (simple, for server-to-server):

```
Authorization: Api-Key YOUR_API_KEY
```

**Bearer Tokens** (JWT, OAuth):

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

**Include in OpenAPI**:

```yaml
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []
```

---

## Rate Limiting

Include headers in responses:

```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
```

Return `429 Too Many Requests` when exceeded.

---

## Security Checklist

- [ ] HTTPS only
- [ ] Authentication on protected routes
- [ ] Input validation
- [ ] Output encoding
- [ ] Rate limiting
- [ ] CORS configuration
- [ ] No sensitive data in URLs
- [ ] Audit logging

---

## gRPC and Protocol Buffers

### Proto Definition

```protobuf
syntax = "proto3";
package user.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  google.protobuf.Timestamp created_at = 4;
}

message GetUserRequest {
  string id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}
```

### When to Use gRPC vs REST

| Factor            | gRPC                        | REST                        |
| ----------------- | --------------------------- | --------------------------- |
| **Performance**   | Binary, fast                | JSON, human-readable        |
| **Streaming**     | Bidirectional               | SSE/WebSocket workaround    |
| **Type safety**   | Proto generates types       | OpenAPI + codegen           |
| **Browser**       | Needs gRPC-Web proxy        | Native                      |
| **Tooling**       | Protoc, Buf                 | Swagger, Postman            |
| **Best for**      | Service-to-service, streaming | Public APIs, web clients  |

---

## tRPC for TypeScript

```typescript
// server/router.ts
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';

export const appRouter = router({
  user: router({
    get: publicProcedure
      .input(z.object({ id: z.string() }))
      .query(async ({ input }) => {
        return db.user.findUnique({ where: { id: input.id } });
      }),
    create: protectedProcedure
      .input(z.object({
        name: z.string().min(1),
        email: z.string().email(),
      }))
      .mutation(async ({ input, ctx }) => {
        return db.user.create({ data: { ...input, createdBy: ctx.userId } });
      }),
  }),
});

export type AppRouter = typeof appRouter;

// client.ts - Full type inference, no codegen
const user = trpc.user.get.useQuery({ id: '123' });
const createUser = trpc.user.create.useMutation();
```

tRPC is ideal for monorepo full-stack TypeScript apps where client and server share the same codebase.

---

## Webhook Design Patterns

### Webhook Payload

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

### Signature Verification

```typescript
// Sign webhooks with HMAC-SHA256
import crypto from 'crypto';

function signWebhook(payload: string, secret: string): string {
  return crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
}

// Verify on receiving end
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = signWebhook(payload, secret);
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}
```

### Retry Strategy

```
Attempt 1: Immediately
Attempt 2: After 1 minute
Attempt 3: After 5 minutes
Attempt 4: After 30 minutes
Attempt 5: After 2 hours
Attempt 6: After 24 hours (final)

Failed webhooks: log, alert, manual retry UI
```

### Idempotency

```typescript
// Include idempotency key in webhook
// Receivers should deduplicate based on event ID
async function handleWebhook(event: WebhookEvent) {
  // Check if already processed
  const existing = await db.processedEvents.findUnique({
    where: { eventId: event.id },
  });
  if (existing) return { status: 'already_processed' };

  // Process and record
  await db.$transaction([
    processEvent(event),
    db.processedEvents.create({ data: { eventId: event.id } }),
  ]);
}
```

---

## API Versioning Strategies

| Strategy            | Example                          | Pros                     | Cons                      |
| ------------------- | -------------------------------- | ------------------------ | ------------------------- |
| **URL path**        | `/api/v1/users`                  | Explicit, easy to route  | URL pollution             |
| **Query parameter** | `/api/users?version=1`           | Optional parameter       | Easy to miss              |
| **Header**          | `Accept: application/vnd.api.v1` | Clean URLs               | Hidden, harder to test    |
| **Content negotiation** | `Accept: application/json;v=2` | Standards-based        | Complex to implement      |

**Recommendation:** URL path versioning for simplicity. Only bump major versions for breaking changes. Use additive, non-breaking changes within a version.

More agent context in travisjneuman/.claude

127 other files this repository gives its agents, the first 60 shown.

CLAUDE.md

Skill

Discussion

Did it work?

Say what you used it for and what you changed. People and their agents can both post here.

No reports yet. Be the first to say whether it worked.

Posts are public. Sign in to say whether it worked for you.Sign in to post

Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.