All skills
asyrafhussin avatar

/api-design-patterns

@1de3a7a

RESTful API design, error handling, versioning, and best practices. Use when designing APIs, reviewing endpoints, implementing error responses, or setting up API structure. Triggers on "design API", "review API", "REST best practices", or "API patterns".

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/api-design-patterns

This session only. Nothing lands on disk.

ruleserror-consistent-format.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Consistent Error Response Format

Impact: CRITICAL (Enables predictable error handling across API)

Inconsistent error formats force API consumers to handle multiple error structures, leading to fragile client code. A consistent error format makes APIs predictable, easier to debug, and simpler to integrate.

Incorrect

// ❌ Different formats across endpoints
// Endpoint A
{ "error": "Not found" }

// Endpoint B
{ "message": "Invalid email", "status": 400 }

// Endpoint C
{ "errors": ["Field required", "Invalid format"] }

// Endpoint D
{
  "success": false,
  "errorMessage": "Something went wrong"
}

// Endpoint E - just a string
"User not found"

Problems:

  • Clients can't predict error structure
  • Different parsing logic needed for each endpoint
  • Hard to build generic error handlers
  • Inconsistent developer experience

Correct

Standard Error Envelope

// ✅ Every error follows the same structure
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message",
    "details": [],
    "request_id": "req_abc123"
  }
}

Validation Errors (422)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Please provide a valid email address"
      },
      {
        "field": "password",
        "code": "TOO_SHORT",
        "message": "Password must be at least 8 characters",
        "meta": {
          "min_length": 8,
          "actual_length": 5
        }
      },
      {
        "field": "age",
        "code": "OUT_OF_RANGE",
        "message": "Age must be between 18 and 120",
        "meta": {
          "min": 18,
          "max": 120,
          "actual": 15
        }
      }
    ],
    "request_id": "req_abc123"
  }
}

Not Found (404)

{
  "error": {
    "code": "NOT_FOUND",
    "message": "User with ID 'usr_123' not found",
    "details": [
      {
        "resource": "user",
        "field": "id",
        "value": "usr_123"
      }
    ],
    "request_id": "req_def456"
  }
}

Authentication Error (401)

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required",
    "details": [
      {
        "code": "TOKEN_EXPIRED",
        "message": "Your access token has expired"
      }
    ],
    "request_id": "req_ghi789"
  }
}

Authorization Error (403)

{
  "error": {
    "code": "FORBIDDEN",
    "message": "You don't have permission to access this resource",
    "details": [
      {
        "resource": "order",
        "action": "delete",
        "reason": "Only admins can delete orders"
      }
    ],
    "request_id": "req_jkl012"
  }
}

Conflict Error (409)

{
  "error": {
    "code": "CONFLICT",
    "message": "A user with this email already exists",
    "details": [
      {
        "field": "email",
        "code": "DUPLICATE",
        "value": "john@example.com"
      }
    ],
    "request_id": "req_mno345"
  }
}

Rate Limit Error (429)

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Please retry after 60 seconds.",
    "details": [
      {
        "limit": 100,
        "window": "1 minute",
        "retry_after": 60
      }
    ],
    "request_id": "req_pqr678"
  }
}

Server Error (500)

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred. Please try again later.",
    "request_id": "req_stu901"
  }
}

Note: Never expose stack traces or internal details in production.

Implementation

TypeScript/Node.js

// ✅ Error classes
abstract class AppError extends Error {
  abstract readonly code: string;
  abstract readonly statusCode: number;
  readonly details: ErrorDetail[];

  constructor(message: string, details: ErrorDetail[] = []) {
    super(message);
    this.details = details;
  }

  toJSON() {
    return {
      error: {
        code: this.code,
        message: this.message,
        details: this.details.length > 0 ? this.details : undefined,
      }
    };
  }
}

class ValidationError extends AppError {
  readonly code = 'VALIDATION_ERROR';
  readonly statusCode = 422;
}

class NotFoundError extends AppError {
  readonly code = 'NOT_FOUND';
  readonly statusCode = 404;
}

class UnauthorizedError extends AppError {
  readonly code = 'UNAUTHORIZED';
  readonly statusCode = 401;
}

// Error handler middleware
function errorHandler(err, req, res, next) {
  const requestId = req.id || generateRequestId();

  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: {
        ...err.toJSON().error,
        request_id: requestId,
      }
    });
  }

  // Log unexpected errors
  logger.error('Unexpected error', { error: err, requestId });

  // Generic response for unknown errors
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
      request_id: requestId,
    }
  });
}

Laravel/PHP

<?php

// ✅ app/Exceptions/AppException.php
abstract class AppException extends Exception
{
    abstract public function getErrorCode(): string;
    abstract public function getStatusCode(): int;

    protected array $details = [];

    public function setDetails(array $details): self
    {
        $this->details = $details;
        return $this;
    }

    public function render(): JsonResponse
    {
        return response()->json([
            'error' => [
                'code' => $this->getErrorCode(),
                'message' => $this->getMessage(),
                'details' => $this->details ?: null,
                'request_id' => request()->id(),
            ],
        ], $this->getStatusCode());
    }
}

class ValidationException extends AppException
{
    public function getErrorCode(): string
    {
        return 'VALIDATION_ERROR';
    }

    public function getStatusCode(): int
    {
        return 422;
    }

    public static function fromValidator(Validator $validator): self
    {
        $details = [];
        foreach ($validator->errors()->toArray() as $field => $messages) {
            $details[] = [
                'field' => $field,
                'code' => 'INVALID',
                'message' => $messages[0],
            ];
        }

        return (new self('The request contains invalid data'))
            ->setDetails($details);
    }
}

// Handler
class Handler extends ExceptionHandler
{
    public function render($request, Throwable $e)
    {
        if ($e instanceof AppException) {
            return $e->render();
        }

        if ($e instanceof ModelNotFoundException) {
            return response()->json([
                'error' => [
                    'code' => 'NOT_FOUND',
                    'message' => 'Resource not found',
                    'request_id' => $request->id(),
                ],
            ], 404);
        }

        // Log and return generic error
        Log::error($e->getMessage(), ['exception' => $e]);

        return response()->json([
            'error' => [
                'code' => 'INTERNAL_ERROR',
                'message' => 'An unexpected error occurred',
                'request_id' => $request->id(),
            ],
        ], 500);
    }
}

Error Code Naming

// ✅ Use SCREAMING_SNAKE_CASE
VALIDATION_ERROR
NOT_FOUND
UNAUTHORIZED
FORBIDDEN
RATE_LIMITED
INTERNAL_ERROR

// ✅ Be specific
INVALID_EMAIL_FORMAT
PASSWORD_TOO_SHORT
DUPLICATE_EMAIL
TOKEN_EXPIRED
INSUFFICIENT_FUNDS

// ❌ Avoid vague codes
ERROR
FAILED
BAD_REQUEST

Benefits:

  • Predictable API behavior across all endpoints
  • Reusable client-side error handling logic
  • Easier debugging with request IDs
  • Clear error codes for programmatic handling
  • Human-readable messages for display
  • Detailed validation feedback for field-level errors

Reference: RFC 7807 - Problem Details for HTTP APIs

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a comprehensive documentation library for RESTful API design patterns. It provides best practices for resource design, error handling, security, and documentation. The skill contains no executable code, malicious instructions, or hidden functionality, and its references are restricted to well-known technical documentation sources.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    4/28 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1de3a7a. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "agent-skills",
  "version": "2.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/api-design-patterns