Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Design clear, scalable REST and GraphQL APIs including resource modeling, HTTP methods, pagination, error handling, versioning, and schema best practices.
Design clear, scalable REST and GraphQL APIs including resource modeling, HTTP methods, pagination, error handling, versioning, and schema best practices.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run.
Design intuitive, scalable REST and GraphQL APIs that developers love. Covers resource modeling, HTTP semantics, pagination, error handling, versioning, and GraphQL schema patterns.
Designing new REST or GraphQL APIs Reviewing API specifications before implementation Establishing API design standards for teams Refactoring APIs for better usability Migrating between API paradigms
REST, GraphQL, API design, HTTP methods, pagination, error handling, versioning, OpenAPI, HATEOAS, schema design
Choose REST when...Choose GraphQL when...Simple CRUD operationsComplex nested data requirementsPublic APIs with broad audienceMobile apps needing bandwidth optimizationHeavy caching requirementsClients need to specify exact data shapeTeam is unfamiliar with GraphQLAggregating multiple data sourcesSimple response structuresRapidly evolving frontend requirements
โ Plural nouns for collections GET /api/users GET /api/orders GET /api/products โ Avoid verbs (let HTTP methods be the verb) POST /api/createUser โ Wrong POST /api/users โ Correct โ Nested resources (max 2 levels) GET /api/users/{id}/orders โ Avoid deep nesting GET /api/users/{id}/orders/{orderId}/items/{itemId}/reviews โ Too deep GET /api/order-items/{id}/reviews โ Better
MethodPurposeSuccessCommon ErrorsGETRetrieve200 OK404 Not FoundPOSTCreate201 Created400/422 ValidationPUTReplace200 OK404 Not FoundPATCHPartial update200 OK404 Not FoundDELETERemove204 No Content404/409 Conflict
SUCCESS = { 200: "OK", # GET, PUT, PATCH success 201: "Created", # POST success 204: "No Content", # DELETE success } CLIENT_ERROR = { 400: "Bad Request", # Malformed syntax 401: "Unauthorized", # Missing/invalid auth 403: "Forbidden", # Valid auth, no permission 404: "Not Found", # Resource doesn't exist 409: "Conflict", # State conflict (duplicate email) 422: "Unprocessable Entity", # Validation errors 429: "Too Many Requests", # Rate limited } SERVER_ERROR = { 500: "Internal Server Error", 503: "Service Unavailable", # Temporary downtime }
Offset-Based (Simple) GET /api/users?page=2&page_size=20 { "items": [...], "page": 2, "page_size": 20, "total": 150, "pages": 8 } Cursor-Based (For Large Datasets) GET /api/users?limit=20&cursor=eyJpZCI6MTIzfQ { "items": [...], "next_cursor": "eyJpZCI6MTQzfQ", "has_more": true }
# Filtering GET /api/users?status=active&role=admin # Sorting (- prefix for descending) GET /api/users?sort=-created_at,name # Search GET /api/users?search=john # Field selection GET /api/users?fields=id,name,email
Always use consistent structure: { "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": [ {"field": "email", "message": "Invalid email format"} ], "timestamp": "2025-10-16T12:00:00Z" } }
from fastapi import FastAPI, Query, Path, HTTPException, status from pydantic import BaseModel, Field, EmailStr from typing import Optional, List from datetime import datetime app = FastAPI(title="API", version="1.0.0") # Models class UserCreate(BaseModel): email: EmailStr name: str = Field(..., min_length=1, max_length=100) class User(BaseModel): id: str email: str name: str created_at: datetime class PaginatedResponse(BaseModel): items: List[User] total: int page: int page_size: int pages: int # Endpoints @app.get("/api/users", response_model=PaginatedResponse) async def list_users( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), status: Optional[str] = Query(None), search: Optional[str] = Query(None) ): """List users with pagination and filtering.""" total = await count_users(status=status, search=search) offset = (page - 1) * page_size users = await fetch_users(limit=page_size, offset=offset, status=status, search=search) return PaginatedResponse( items=users, total=total, page=page, page_size=page_size, pages=(total + page_size - 1) // page_size ) @app.post("/api/users", response_model=User, status_code=status.HTTP_201_CREATED) async def create_user(user: UserCreate): """Create new user.""" if await user_exists(user.email): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={"code": "EMAIL_EXISTS", "message": "Email already registered"} ) return await save_user(user) @app.get("/api/users/{user_id}", response_model=User) async def get_user(user_id: str = Path(...)): """Get user by ID.""" user = await fetch_user(user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return user @app.delete("/api/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user(user_id: str): """Delete user.""" if not await fetch_user(user_id): raise HTTPException(status_code=404, detail="User not found") await remove_user(user_id)
# Types type User { id: ID! email: String! name: String! createdAt: DateTime! orders(first: Int = 20, after: String): OrderConnection! } # Pagination (Relay-style) type OrderConnection { edges: [OrderEdge!]! pageInfo: PageInfo! totalCount: Int! } type OrderEdge { node: Order! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } # Queries type Query { user(id: ID!): User users(first: Int = 20, after: String, search: String): UserConnection! } # Mutations with Input/Payload pattern input CreateUserInput { email: String! name: String! password: String! } type CreateUserPayload { user: User errors: [Error!] } type Error { field: String message: String! code: String! } type Mutation { createUser(input: CreateUserInput!): CreateUserPayload! }
from aiodataloader import DataLoader class UserLoader(DataLoader): async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]: """Load multiple users in single query.""" users = await fetch_users_by_ids(user_ids) user_map = {user["id"]: user for user in users} return [user_map.get(uid) for uid in user_ids] # In resolver @user_type.field("orders") async def resolve_orders(user: dict, info): loader = info.context["loaders"]["orders_by_user"] return await loader.load(user["id"])
# Depth limiting MAX_QUERY_DEPTH = 5 # Complexity limiting MAX_QUERY_COMPLEXITY = 100 # Timeout QUERY_TIMEOUT_SECONDS = 10
/api/v1/users /api/v2/users Pros: Clear, easy to route, cacheable Cons: Multiple URLs for same resource
GET /api/users Accept: application/vnd.api+json; version=2 Pros: Clean URLs Cons: Less visible, harder to test
Add deprecation headers: Deprecation: true Document migration path Give 6-12 months notice Monitor usage before removal
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 742 X-RateLimit-Reset: 1640000000 # When limited: 429 Too Many Requests Retry-After: 3600
from datetime import datetime, timedelta class RateLimiter: def __init__(self, calls: int, period: int): self.calls = calls self.period = period self.cache = {} def check(self, key: str) -> tuple[bool, dict]: now = datetime.now() if key not in self.cache: self.cache[key] = [] # Remove old requests cutoff = now - timedelta(seconds=self.period) self.cache[key] = [ts for ts in self.cache[key] if ts > cutoff] remaining = self.calls - len(self.cache[key]) if remaining <= 0: return False, {"limit": self.calls, "remaining": 0} self.cache[key].append(now) return True, {"limit": self.calls, "remaining": remaining - 1}
Nouns, not verbs Plural for collections Max 2 levels nesting
Correct method for each action Correct status codes Idempotent operations are idempotent
All collections paginated Filtering/sorting supported Error format consistent
Authentication defined Rate limiting configured Input validation on all fields HTTPS enforced
OpenAPI spec generated All endpoints documented Examples provided
Verbs in URLs: /api/getUser โ use /api/users/{id} with GET POST for Retrieval: Use GET for safe, idempotent reads Inconsistent Errors: Always same error format Unbounded Lists: Always paginate collections Secrets in URLs: Query params are logged Breaking Changes Without Versioning: Plan for evolution from day 1 Database Schema as API: API should be stable even when schema changes Ignoring HTTP Semantics: Status codes and methods have meaning
Code helpers, APIs, CLIs, browser automation, testing, and developer operations.
Largest current source with strong distribution and engagement signals.