Skip to content
Back to skills

Api Design Enact On

ASecurity

REST API, GraphQL, and API design patterns and best practices

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
developmenttypescriptrustgosqlnodegitapisecuritydocumentation

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill api-design-enact-on --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Api Design Enact On?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Api Design Enact On
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-api-design-enact-on/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-api-design-enact-on)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: api-design
description: REST API, GraphQL, and API design patterns and best practices
license: MIT
compatibility: opencode
---

# API Design Skill

Comprehensive patterns and best practices for designing REST APIs, GraphQL APIs, and API-first development.

## What I Know

### REST API Design Principles

**Resource Naming**
```
# Good - nouns, plural, lowercase, hyphenated
GET /users
GET /users/{id}
GET /users/{id}/posts
GET /blog-posts
GET /order-items

# Bad - verbs, singular, camelCase
GET /getUsers
GET /user
GET /getUserPosts
GET /blogPosts
```

**HTTP Methods**
| Method | Usage | Idempotent | Request Body | Response |
|--------|-------|------------|--------------|----------|
| GET | Read resource(s) | Yes | No | Resource(s) |
| POST | Create resource | No | Yes | Created resource |
| PUT | Replace resource | Yes | Yes | Updated resource |
| PATCH | Partial update | No* | Yes | Updated resource |
| DELETE | Remove resource | Yes | No | Empty or confirmation |

**Status Codes**
```
# Success
200 OK              - Successful GET, PUT, PATCH
201 Created         - Successful POST
204 No Content      - Successful DELETE

# Client Errors
400 Bad Request     - Invalid request body/params
401 Unauthorized    - Missing/invalid authentication
403 Forbidden       - Valid auth, insufficient permissions
404 Not Found       - Resource doesn't exist
409 Conflict        - Resource state conflict (duplicate)
422 Unprocessable   - Validation errors

# Server Errors
500 Internal Error  - Unexpected server error
502 Bad Gateway     - Upstream service error
503 Service Unavailable - Temporary overload
```

### Standardized Response Format

**Success Response**
```json
{
  "success": true,
  "data": {
    "id": 123,
    "email": "user@example.com",
    "name": "John Doe"
  }
}
```

**List Response with Pagination**
```json
{
  "success": true,
  "data": [
    { "id": 1, "name": "Item 1" },
    { "id": 2, "name": "Item 2" }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasMore": true
  }
}
```

**Error Response**
```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "password",
        "message": "Password must be at least 8 characters"
      }
    ]
  }
}
```

### Pagination Patterns

**Offset-Based (Simple)**
```
GET /users?page=2&limit=20

Response:
{
  "data": [...],
  "meta": {
    "page": 2,
    "limit": 20,
    "total": 150,
    "totalPages": 8
  }
}
```

**Cursor-Based (Scalable)**
```
GET /users?cursor=abc123&limit=20

Response:
{
  "data": [...],
  "meta": {
    "limit": 20,
    "nextCursor": "def456",
    "hasMore": true
  }
}
```

### Filtering & Sorting

**Query Parameters**
```
# Filtering
GET /users?status=active
GET /users?role=admin&status=active
GET /products?price_min=10&price_max=100
GET /posts?created_after=2024-01-01

# Sorting
GET /users?sort=name        # Ascending
GET /users?sort=-createdAt  # Descending
GET /users?sort=name,-createdAt  # Multiple

# Field Selection
GET /users?fields=id,name,email

# Search
GET /users?search=john
GET /products?q=laptop
```

### API Versioning

**URI Versioning (Recommended)**
```
GET /api/v1/users
GET /api/v2/users
```

**Header Versioning**
```
GET /api/users
Accept: application/vnd.api+json;version=1
```

**Query Parameter**
```
GET /api/users?version=1
```

### Authentication Patterns

**JWT Bearer Token**
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

**API Key**
```
# Header
X-API-Key: your-api-key

# Query (less secure)
GET /api/users?api_key=your-api-key
```

**OAuth 2.0 Flows**
```
# Authorization Code (Web apps)
# Client Credentials (Server-to-server)
# PKCE (Mobile/SPA)
```

### Rate Limiting

**Headers**
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
Retry-After: 60
```

**429 Response**
```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retryAfter": 60
  }
}
```

### OpenAPI/Swagger 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
    description: Production

paths:
  /users:
    get:
      summary: List users
      tags:
        - 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/UserList'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create user
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUser'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'

  /users/{id}:
    get:
      summary: Get user by ID
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        email:
          type: string
          format: email
        name:
          type: string
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - email
        - name

    CreateUser:
      type: object
      properties:
        email:
          type: string
          format: email
        name:
          type: string
          minLength: 2
          maxLength: 100
        password:
          type: string
          minLength: 8
      required:
        - email
        - name
        - password

    UserResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/User'

    UserList:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        meta:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: array
              items:
                type: object

  responses:
    BadRequest:
      description: Bad Request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []
```

### GraphQL Patterns

**Schema Design**
```graphql
type Query {
  user(id: ID!): User
  users(
    page: Int = 1
    limit: Int = 20
    filter: UserFilter
  ): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): UserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UserPayload!
  deleteUser(id: ID!): DeletePayload!
}

type User {
  id: ID!
  email: String!
  name: String!
  posts(first: Int): [Post!]!
  createdAt: DateTime!
}

input CreateUserInput {
  email: String!
  name: String!
  password: String!
}

input UpdateUserInput {
  email: String
  name: String
}

input UserFilter {
  status: UserStatus
  role: UserRole
  search: String
}

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type UserPayload {
  user: User
  errors: [Error!]
}

type DeletePayload {
  success: Boolean!
  errors: [Error!]
}

type Error {
  field: String
  message: String!
}
```

**Resolver Patterns**
```typescript
const resolvers = {
  Query: {
    user: async (_, { id }, context) => {
      return context.dataSources.users.findById(id)
    },
    users: async (_, { page, limit, filter }, context) => {
      return context.dataSources.users.findAll({ page, limit, filter })
    },
  },
  Mutation: {
    createUser: async (_, { input }, context) => {
      try {
        const user = await context.dataSources.users.create(input)
        return { user, errors: [] }
      } catch (error) {
        return { user: null, errors: [{ message: error.message }] }
      }
    },
  },
  User: {
    posts: async (user, { first }, context) => {
      return context.dataSources.posts.findByUserId(user.id, { first })
    },
  },
}
```

### Error Handling Best Practices

**Error Codes**
```typescript
enum ErrorCode {
  // Authentication
  INVALID_TOKEN = 'INVALID_TOKEN',
  TOKEN_EXPIRED = 'TOKEN_EXPIRED',
  UNAUTHORIZED = 'UNAUTHORIZED',

  // Authorization
  FORBIDDEN = 'FORBIDDEN',
  INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',

  // Validation
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  INVALID_INPUT = 'INVALID_INPUT',

  // Resource
  NOT_FOUND = 'NOT_FOUND',
  ALREADY_EXISTS = 'ALREADY_EXISTS',
  CONFLICT = 'CONFLICT',

  // Rate Limiting
  RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',

  // Server
  INTERNAL_ERROR = 'INTERNAL_ERROR',
  SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
}
```

### HATEOAS (Hypermedia)

```json
{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "links": {
    "self": "/api/v1/users/123",
    "posts": "/api/v1/users/123/posts",
    "update": "/api/v1/users/123",
    "delete": "/api/v1/users/123"
  }
}
```

### Caching Headers

```
# Cache for 1 hour
Cache-Control: max-age=3600, public

# No cache
Cache-Control: no-store, no-cache, must-revalidate

# ETag for conditional requests
ETag: "abc123"
If-None-Match: "abc123"

# Last-Modified
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
```

### Security Best Practices

1. **Always use HTTPS**
2. **Validate all input** - Never trust client data
3. **Rate limit endpoints** - Prevent abuse
4. **Use proper authentication** - JWT, OAuth 2.0
5. **Implement authorization** - Check permissions
6. **Sanitize output** - Prevent XSS
7. **Use parameterized queries** - Prevent SQL injection
8. **Log security events** - Audit trail
9. **Don't expose internals** - Generic error messages
10. **Set security headers** - CORS, CSP, etc.

### API Documentation

**Good Documentation Includes:**
- Authentication guide
- Request/response examples
- Error code reference
- Rate limiting info
- Changelog/versioning
- SDKs and code samples
- Interactive playground (Swagger UI)

### Common Pitfalls

1. **Inconsistent naming** - Stick to one convention
2. **Missing validation** - Always validate input
3. **No versioning** - Version from the start
4. **Poor error messages** - Be helpful, not cryptic
5. **No rate limiting** - Protect from abuse
6. **Exposing internals** - Don't leak stack traces
7. **Ignoring caching** - Set proper cache headers
8. **No pagination** - Always paginate lists
9. **Breaking changes** - Use versioning properly
10. **Missing documentation** - Document everything

---

*Part of SuperAI GitHub - Centralized OpenCode Configuration*

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.