# Send API Design Principles to your agent
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
## Fast path
- Download the package from Yavira.
- Extract it into a folder your agent can access.
- Paste one of the prompts below and point your agent at the extracted folder.
## Suggested prompts
### New install

```text
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.
```
### Upgrade existing

```text
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.
```
## Machine-readable fields
```json
{
  "schemaVersion": "1.0",
  "item": {
    "slug": "api-design-principles",
    "name": "API Design Principles",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/wpank/api-design-principles",
    "canonicalUrl": "https://clawhub.ai/wpank/api-design-principles",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadUrl": "/downloads/api-design-principles",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=api-design-principles",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "packageFormat": "ZIP package",
    "primaryDoc": "SKILL.md",
    "includedAssets": [
      "README.md",
      "SKILL.md",
      "templates/fastapi-template.py",
      "references/quick-reference.md"
    ],
    "downloadMode": "redirect",
    "sourceHealth": {
      "source": "tencent",
      "slug": "api-design-principles",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-29T05:55:07.913Z",
      "expiresAt": "2026-05-06T05:55:07.913Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=api-design-principles",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=api-design-principles",
        "contentDisposition": "attachment; filename=\"api-design-principles-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "api-design-principles"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/api-design-principles"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    }
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/api-design-principles",
    "downloadUrl": "https://openagent3.xyz/downloads/api-design-principles",
    "agentUrl": "https://openagent3.xyz/skills/api-design-principles/agent",
    "manifestUrl": "https://openagent3.xyz/skills/api-design-principles/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/api-design-principles/agent.md"
  }
}
```
## Documentation

### WHAT

Design intuitive, scalable REST and GraphQL APIs that developers love. Covers resource modeling, HTTP semantics, pagination, error handling, versioning, and GraphQL schema patterns.

### WHEN

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

### KEYWORDS

REST, GraphQL, API design, HTTP methods, pagination, error handling, versioning, OpenAPI, HATEOAS, schema design

### Decision Framework: REST vs GraphQL

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

### Resource Naming Rules

✓ 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

### HTTP Methods and Status Codes

MethodPurposeSuccessCommon ErrorsGETRetrieve200 OK404 Not FoundPOSTCreate201 Created400/422 ValidationPUTReplace200 OK404 Not FoundPATCHPartial update200 OK404 Not FoundDELETERemove204 No Content404/409 Conflict

### Complete Status Code Reference

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
}

### Pagination

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 and Sorting

# 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

### Error Response Format

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"
  }
}

### FastAPI Implementation

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)

### Schema Structure

# 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!
}

### DataLoader (Prevent N+1)

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"])

### Query Protection

# Depth limiting
MAX_QUERY_DEPTH = 5

# Complexity limiting
MAX_QUERY_COMPLEXITY = 100

# Timeout
QUERY_TIMEOUT_SECONDS = 10

### URL Versioning (Recommended)

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

Pros: Clear, easy to route, cacheable
Cons: Multiple URLs for same resource

### Header Versioning

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

Pros: Clean URLs
Cons: Less visible, harder to test

### Deprecation Strategy

Add deprecation headers: Deprecation: true
Document migration path
Give 6-12 months notice
Monitor usage before removal

### Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1640000000

# When limited:
429 Too Many Requests
Retry-After: 3600

### Implementation

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}

### Resources

Nouns, not verbs
 Plural for collections
 Max 2 levels nesting

### HTTP

Correct method for each action
 Correct status codes
 Idempotent operations are idempotent

### Data

All collections paginated
 Filtering/sorting supported
 Error format consistent

### Security

Authentication defined
 Rate limiting configured
 Input validation on all fields
 HTTPS enforced

### Documentation

OpenAPI spec generated
 All endpoints documented
 Examples provided

### NEVER

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
## Trust
- Source: tencent
- Verification: Indexed source record
- Publisher: wpank
- Version: 1.0.0
## Source health
- Status: healthy
- Item download looks usable.
- Yavira can redirect you to the upstream package for this item.
- Health scope: item
- Reason: direct_download_ok
- Checked at: 2026-04-29T05:55:07.913Z
- Expires at: 2026-05-06T05:55:07.913Z
- Recommended action: Download for OpenClaw
## Links
- [Detail page](https://openagent3.xyz/skills/api-design-principles)
- [Send to Agent page](https://openagent3.xyz/skills/api-design-principles/agent)
- [JSON manifest](https://openagent3.xyz/skills/api-design-principles/agent.json)
- [Markdown brief](https://openagent3.xyz/skills/api-design-principles/agent.md)
- [Download page](https://openagent3.xyz/downloads/api-design-principles)