/api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
$ npx -y skills add softspark/ai-toolkit --skill api-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/api-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
SKILL.md
api-patterns.SKILL.mdname: api-patterns
description: "REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit."
effort: medium
user-invocable: false
allowed-tools: Read
API Patterns Skill
REST API Design
Resource Naming
# Collection
GET /api/v1/documents # List documents
POST /api/v1/documents # Create document
# Single resource
GET /api/v1/documents/{id} # Get document
PUT /api/v1/documents/{id} # Replace document
PATCH /api/v1/documents/{id} # Update document
DELETE /api/v1/documents/{id} # Delete document
# Nested resources
GET /api/v1/users/{id}/documents # User's documentsHTTP Status Codes
| Code | Meaning | When to Use | |------|---------|-------------| | 200 | OK | Successful GET/PUT/PATCH | | 201 | Created | Successful POST | | 204 | No Content | Successful DELETE | | 400 | Bad Request | Invalid input | | 401 | Unauthorized | Missing/invalid auth | | 403 | Forbidden | No permission | | 404 | Not Found | Resource doesn't exist | | 409 | Conflict | Duplicate resource | | 422 | Unprocessable | Validation error | | 429 | Too Many Requests | Rate limited | | 500 | Internal Error | Server error |
Response Format
{
"data": {
"id": "123",
"type": "document",
"attributes": {
"title": "Example",
"content": "..."
}
},
"meta": {
"total": 100,
"page": 1,
"per_page": 10
}
}Error Response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{"field": "title", "message": "Title is required"},
{"field": "limit", "message": "Must be between 1 and 100"}
]
}
}---
FastAPI Implementation
from fastapi import FastAPI, HTTPException, Query, Path
from pydantic import BaseModel, Field
app = FastAPI(title="RAG-MCP API", version="1.0.0")
class SearchRequest(BaseModel):
query: str = Field(..., min_length=1, description="Search query")
limit: int = Field(10, ge=1, le=100, description="Max results")
class SearchResult(BaseModel):
id: str
title: str
score: float
content: str
class SearchResponse(BaseModel):
results: list[SearchResult]
total: int
@app.post("/api/v1/search", response_model=SearchResponse)
async def search(request: SearchRequest):
"""Search the knowledge base.
Args:
request: Search parameters
Returns:
Search results with scores
"""
try:
results = await perform_search(request.query, request.limit)
return SearchResponse(results=results, total=len(results))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))---
Parameter Documentation Conventions
The same rules apply to OpenAPI `description` fields, Pydantic `Field(description=...)`, and MCP tool parameters: the description should encode the *workflow*, not just restate the type. A consumer (human or LLM) reads it to know how to supply a valid value, not what language primitive it is.
Prefer enums with per-value descriptions for closed sets
A free-form `string` for `status` forces the caller to guess valid values. Constrain it and document each one:
class ListReposRequest(BaseModel):
visibility: Literal["PUBLIC", "PRIVATE", "INTERNAL"] = Field(
"PUBLIC",
description=(
"Repository visibility filter. "
"PUBLIC = visible to anyone; "
"PRIVATE = only members with explicit access; "
"INTERNAL = visible to all org members (Enterprise only)."
),
)In OpenAPI, pair `enum` with the value meanings in the description (or `x-enum-descriptions` if your tooling renders it). Avoid documenting a closed set as plain `string` — the caller cannot tell `INTERNAL` is valid but `internal` is not.
Encode cross-field dependencies in the description
If a field is only valid given another, say so where the dependent field is defined — schemas cannot express "required when":
cursor: str | None = Field(
None,
description=(
"Pagination cursor. Requires a `next_cursor` value obtained from a prior "
"GET /api/v1/documents response. Omit on the first page; do not synthesize."
),
)State the source call by name (`next_cursor` from the previous list response), not just "an opaque token".
Add provenance and exactness constraints for opaque IDs
Opaque identifiers (resource IDs, idempotency keys, cursors) are the most common source of bad calls because they look like something the caller can invent. Pin them down:
document_id: str = Field(
...,
description=(
"Exact document id, e.g. `doc_9f3a21`. Copy it verbatim from a search or "
"list response — case-sensitive, do not type from memory or guess the format. "
"Obtain it from GET /api/v1/documents or the search results."
),
)The two load-bearing phrases: **where it comes from** (`from a search or list response`) and **how to handle it** (`copy verbatim, case-sensitive, do not type from memory`). Both belong in the description, not a separate doc.
Descriptions encode workflow, not type
| Weak | Strong | |------|--------| | `id: The document id` | `id: Exact document id (e.g. doc_9f3a21), copied verbatim from a list/search response — case-sensitive` | | `status: The status string` | `status: One of OPEN, MERGED, CLOSED (see per-value meanings); filters the result set` | | `cursor: Pagination cursor` | `cursor: next_cursor from the previous page response; omit on first request` | | `since: A timestamp` | `since: RFC 3339 UTC timestamp; returns records created strictly after it` |
---
JSON-RPC 2.0 (MCP Pattern)
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {"query": "test"}
}
}###
Read more
name: api-patterns description: "REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit." effort: medium user-invocable: false allowed-tools: Read
API Patterns Skill
REST API Design
Resource Naming
# Collection
GET /api/v1/documents # List documents
POST /api/v1/documents # Create document
# Single resource
GET /api/v1/documents/{id} # Get document
PUT /api/v1/documents/{id} # Replace document
PATCH /api/v1/documents/{id} # Update document
DELETE /api/v1/documents/{id} # Delete document
# Nested resources
GET /api/v1/users/{id}/documents # User's documentsHTTP Status Codes
| Code | Meaning | When to Use | |------|---------|-------------| | 200 | OK | Successful GET/PUT/PATCH | | 201 | Created | Successful POST | | 204 | No Content | Successful DELETE | | 400 | Bad Request | Invalid input | | 401 | Unauthorized | Missing/invalid auth | | 403 | Forbidden | No permission | | 404 | Not Found | Resource doesn't exist | | 409 | Conflict | Duplicate resource | | 422 | Unprocessable | Validation error | | 429 | Too Many Requests | Rate limited | | 500 | Internal Error | Server error |
Response Format
{
"data": {
"id": "123",
"type": "document",
"attributes": {
"title": "Example",
"content": "..."
}
},
"meta": {
"total": 100,
"page": 1,
"per_page": 10
}
}Error Response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{"field": "title", "message": "Title is required"},
{"field": "limit", "message": "Must be between 1 and 100"}
]
}
}---
FastAPI Implementation
from fastapi import FastAPI, HTTPException, Query, Path
from pydantic import BaseModel, Field
app = FastAPI(title="RAG-MCP API", version="1.0.0")
class SearchRequest(BaseModel):
query: str = Field(..., min_length=1, description="Search query")
limit: int = Field(10, ge=1, le=100, description="Max results")
class SearchResult(BaseModel):
id: str
title: str
score: float
content: str
class SearchResponse(BaseModel):
results: list[SearchResult]
total: int
@app.post("/api/v1/search", response_model=SearchResponse)
async def search(request: SearchRequest):
"""Search the knowledge base.
Args:
request: Search parameters
Returns:
Search results with scores
"""
try:
results = await perform_search(request.query, request.limit)
return SearchResponse(results=results, total=len(results))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))---
Parameter Documentation Conventions
The same rules apply to OpenAPI `description` fields, Pydantic `Field(description=...)`, and MCP tool parameters: the description should encode the *workflow*, not just restate the type. A consumer (human or LLM) reads it to know how to supply a valid value, not what language primitive it is.
Prefer enums with per-value descriptions for closed sets
A free-form `string` for `status` forces the caller to guess valid values. Constrain it and document each one:
class ListReposRequest(BaseModel):
visibility: Literal["PUBLIC", "PRIVATE", "INTERNAL"] = Field(
"PUBLIC",
description=(
"Repository visibility filter. "
"PUBLIC = visible to anyone; "
"PRIVATE = only members with explicit access; "
"INTERNAL = visible to all org members (Enterprise only)."
),
)In OpenAPI, pair `enum` with the value meanings in the description (or `x-enum-descriptions` if your tooling renders it). Avoid documenting a closed set as plain `string` — the caller cannot tell `INTERNAL` is valid but `internal` is not.
Encode cross-field dependencies in the description
If a field is only valid given another, say so where the dependent field is defined — schemas cannot express "required when":
cursor: str | None = Field(
None,
description=(
"Pagination cursor. Requires a `next_cursor` value obtained from a prior "
"GET /api/v1/documents response. Omit on the first page; do not synthesize."
),
)State the source call by name (`next_cursor` from the previous list response), not just "an opaque token".
Add provenance and exactness constraints for opaque IDs
Opaque identifiers (resource IDs, idempotency keys, cursors) are the most common source of bad calls because they look like something the caller can invent. Pin them down:
document_id: str = Field(
...,
description=(
"Exact document id, e.g. `doc_9f3a21`. Copy it verbatim from a search or "
"list response — case-sensitive, do not type from memory or guess the format. "
"Obtain it from GET /api/v1/documents or the search results."
),
)The two load-bearing phrases: **where it comes from** (`from a search or list response`) and **how to handle it** (`copy verbatim, case-sensitive, do not type from memory`). Both belong in the description, not a separate doc.
Descriptions encode workflow, not type
| Weak | Strong | |------|--------| | `id: The document id` | `id: Exact document id (e.g. doc_9f3a21), copied verbatim from a list/search response — case-sensitive` | | `status: The status string` | `status: One of OPEN, MERGED, CLOSED (see per-value meanings); filters the result set` | | `cursor: Pagination cursor` | `cursor: next_cursor from the previous page response; omit on first request` | | `since: A timestamp` | `since: RFC 3339 UTC timestamp; returns records created strictly after it` |
---
JSON-RPC 2.0 (MCP Pattern)
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {"query": "test"}
}
}###
Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /app-builder
App scaffolding: Next.js, Vite, Nuxt, Astro, FastAPI, Django, Laravel, RN, Flutter. Triggers: scaffold, bootstrap, new project, starter, dashboard, mobile app.
Open skill

