Install
$ agentstack add skill-sawrus-agent-guides-api-design ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
API Design Skill
> Expertise: REST API design, HTTP semantics, versioning strategies, error contracts, OpenAPI, pagination, idempotency.
HTTP Method & Status Code Reference
| Operation | Method | Success | Error cases | |---|---|---|---| | Create resource | POST | 201 Created | 400 Validation, 409 Conflict | | Read resource | GET | 200 OK | 404 Not Found, 403 Forbidden | | Full update | PUT | 200 OK | 400, 404, 409 | | Partial update | PATCH | 200 OK | 400, 404 | | Delete | DELETE | 204 No Content | 404, 409 (has dependents) | | Async action | POST | 202 Accepted | 400 |
URL Design Rules
✅ Nouns, plural, lowercase, kebab-case
GET /users/{id}
POST /orders
GET /product-categories
❌ Verbs in path
POST /createOrder
GET /getUser?id=123
✅ Nesting only for true ownership (max 2 levels)
GET /orders/{order_id}/items ✅ items belong to order
GET /orders/{order_id}/items/{item_id}/tags ❌ too deep
✅ Actions as sub-resources
POST /orders/{id}/cancel
POST /invoices/{id}/resend
Standard Error Contract
All error responses must follow the same shape:
{
"error": {
"code": "ORDER_NOT_FOUND", // machine-readable, stable
"message": "Order ord_123 not found", // human-readable
"details": [ // optional: per-field errors
{ "field": "items[0].quantity", "issue": "must be > 0" }
],
"request_id": "req_abc123" // traceable
}
}
# FastAPI implementation
from fastapi import HTTPException
from pydantic import BaseModel
from typing import Optional, List
class ErrorDetail(BaseModel):
field: str
issue: str
class ErrorResponse(BaseModel):
code: str
message: str
details: Optional[List[ErrorDetail]] = None
request_id: Optional[str] = None
# Usage
raise HTTPException(
status_code=404,
detail=ErrorResponse(
code="ORDER_NOT_FOUND",
message=f"Order {order_id} not found",
request_id=request.state.request_id
).model_dump()
)
Pagination Patterns
Cursor-based (preferred for large/live datasets)
class PaginatedResponse(BaseModel, Generic[T]):
items: List[T]
next_cursor: Optional[str] = None # opaque, base64-encoded
total_count: Optional[int] = None # only if cheap to compute
# Encode cursor: hide implementation detail from client
import base64, json
def encode_cursor(last_id: int, last_created_at: datetime) -> str:
payload = {"id": last_id, "ts": last_created_at.isoformat()}
return base64.b64encode(json.dumps(payload).encode()).decode()
def decode_cursor(cursor: str) -> dict:
return json.loads(base64.b64decode(cursor).decode())
Offset-based (only for small, static datasets)
# Only when total count is cheap and dataset doesn't change under pagination
@router.get("/products")
async def list_products(page: int = 1, page_size: int = Query(20, le=100)):
offset = (page - 1) * page_size
# ...
Versioning Strategy
URL versioning (recommended for major breaking changes):
/api/v1/orders
/api/v2/orders ← new schema, old still supported
Header versioning (for minor variations):
Accept: application/vnd.myapi.v2+json
Rules:
- v1 stays alive for minimum 6 months after v2 launch
- Deprecated endpoints return: Deprecation: true, Sunset: headers
- Never remove a field from a response without a major version bump
Idempotency
# Idempotency key pattern for POST mutations
@router.post("/orders", status_code=201)
async def create_order(
body: CreateOrderRequest,
idempotency_key: Optional[str] = Header(None, alias="X-Idempotency-Key"),
db: AsyncSession = Depends(get_db),
redis: Redis = Depends(get_redis),
):
if idempotency_key:
cached = await redis.get(f"idempotency:{idempotency_key}")
if cached:
return JSONResponse(json.loads(cached), status_code=200) # 200 = already done
order = await order_service.create(db, body)
response = OrderResponse.model_validate(order)
if idempotency_key:
await redis.setex(
f"idempotency:{idempotency_key}",
86400, # 24h TTL
response.model_dump_json()
)
return response
Security Checklist
- [ ] Auth middleware applied before any handler logic — never inside handler
- [ ] Ownership check:
if order.user_id != current_user.id: raise 403 - [ ] Input validation via Pydantic (FastAPI) or Joi/Zod (Node) on every endpoint
- [ ] Rate limiting on public endpoints (especially auth, password reset)
- [ ] No sensitive data in URLs (tokens, passwords) — use request body or headers
- [ ] CORS configured explicitly: no
allow_origins=["*"]in production
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: sawrus
- Source: sawrus/agent-guides
- License: MIT
- Homepage: https://sawrus.github.io/agent-guides
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.