Best for
- Designing new API endpoints
- Defining module boundaries or contracts between teams
- Creating component prop interfaces
addyosmani/agent-skills/skills/api-and-interface-design/SKILL.md
Guides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.
Decision brief
Guides stable API and interface design. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/addyosmani/agent-skills --skill "skills/api-and-interface-design"Inspect the Agent Skill "api-and-interface-design" from https://github.com/addyosmani/agent-skills/blob/bdf76c7c6b7b3b3e01bb15c9fdc42ac5351855c1/skills/api-and-interface-design/SKILL.md at commit bdf76c7c6b7b3b3e01bb15c9fdc42ac5351855c1. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
[ ] Every endpoint has typed input and output schemas
Designing new API endpoints
With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract.
With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract.
Avoid forcing consumers to choose between multiple versions of the same dependency or API. Diamond dependency problems arise when different consumers need different versions of the same thing. Design for a world where only one version exists at a time — extend rather than fork.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 89/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 81,553 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Design stable, well-documented interfaces that are hard to misuse. Good interfaces make the right thing easy and the wrong thing hard. This applies to REST APIs, GraphQL schemas, module boundaries, component props, and any surface where one piece of code talks to another.
With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract.
This means: every public behavior — including undocumented quirks, error message text, timing, and ordering — becomes a de facto contract once users depend on it. Design implications:
deprecation-and-migration for how to safely remove things users depend on.Avoid forcing consumers to choose between multiple versions of the same dependency or API. Diamond dependency problems arise when different consumers need different versions of the same thing. Design for a world where only one version exists at a time — extend rather than fork.
Define the interface before implementing it. The contract is the spec — implementation follows.
// Define the contract first
interface TaskAPI {
// Creates a task and returns the created task with server-generated fields
createTask(input: CreateTaskInput): Promise<Task>;
// Returns paginated tasks matching filters
listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
// Returns a single task or throws NotFoundError
getTask(id: string): Promise<Task>;
// Partial update — only provided fields change
updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
// Idempotent delete — succeeds even if already deleted
deleteTask(id: string): Promise<void>;
}
Pick one error strategy and use it everywhere:
// REST: HTTP status codes + structured error body
// Every error response follows the same shape
interface APIError {
error: {
code: string; // Machine-readable: "VALIDATION_ERROR"
message: string; // Human-readable: "Email is required"
details?: unknown; // Additional context when helpful
};
}
// Status code mapping
// 400 → Client sent invalid data
// 401 → Not authenticated
// 403 → Authenticated but not authorized
// 404 → Resource not found
// 409 → Conflict (duplicate, version mismatch)
// 422 → Validation failed (semantically invalid)
// 500 → Server error (never expose internal details)
Don't mix patterns. If some endpoints throw, others return null, and others return { error } — the consumer can't predict behavior.
Trust internal code. Validate at system edges where external input enters:
// Validate at the API boundary
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid task data',
details: result.error.flatten(),
},
});
}
// After validation, internal code trusts the types
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
Where validation belongs:
Third-party API responses are untrusted data. Validate their shape and content before using them in any logic, rendering, or decision-making. A compromised or misbehaving external service can return unexpected types, malicious content, or instruction-like text.
Where validation does NOT belong:
Extend interfaces without breaking existing consumers:
// Good: Add optional fields
interface CreateTaskInput {
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high'; // Added later, optional
labels?: string[]; // Added later, optional
}
// Bad: Change existing field types or remove fields
interface CreateTaskInput {
title: string;
// description: string; // Removed — breaks existing consumers
priority: number; // Changed from string — breaks existing consumers
}
| Pattern | Convention | Example |
|---|---|---|
| REST endpoints | Plural nouns, no verbs | GET /api/tasks, POST /api/tasks |
| Query params | camelCase | ?sortBy=createdAt&pageSize=20 |
| Response fields | camelCase | { createdAt, updatedAt, taskId } |
| Boolean fields | is/has/can prefix | isComplete, hasAttachments |
| Enum values | UPPER_SNAKE | "IN_PROGRESS", "COMPLETED" |
GET /api/tasks → List tasks (with query params for filtering)
POST /api/tasks → Create a task
GET /api/tasks/:id → Get a single task
PATCH /api/tasks/:id → Update a task (partial)
DELETE /api/tasks/:id → Delete a task
GET /api/tasks/:id/comments → List comments for a task (sub-resource)
POST /api/tasks/:id/comments → Add a comment to a task
Paginate list endpoints:
// Request
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
// Response
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 142,
"totalPages": 8
}
}
Use query parameters for filters:
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
Accept partial objects — only update what's provided:
// Only title changes, everything else preserved
PATCH /api/tasks/123
{ "title": "Updated title" }
// Good: Each variant is explicit
type TaskStatus =
| { type: 'pending' }
| { type: 'in_progress'; assignee: string; startedAt: Date }
| { type: 'completed'; completedAt: Date; completedBy: string }
| { type: 'cancelled'; reason: string; cancelledAt: Date };
// Consumer gets type narrowing
function getStatusLabel(status: TaskStatus): string {
switch (status.type) {
case 'pending': return 'Pending';
case 'in_progress': return `In progress (${status.assignee})`;
case 'completed': return `Done on ${status.completedAt}`;
case 'cancelled': return `Cancelled: ${status.reason}`;
}
}
// Input: what the caller provides
interface CreateTaskInput {
title: string;
description?: string;
}
// Output: what the system returns (includes server-generated fields)
interface Task {
id: string;
title: string;
description: string | null;
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };
// Prevents accidentally passing a UserId where a TaskId is expected
function getTask(id: TaskId): Promise<Task> { ... }
| Rationalization | Reality |
|---|---|
| "We'll document the API later" | The types ARE the documentation. Define them first. |
| "We don't need pagination for now" | You will the moment someone has 100+ items. Add it from the start. |
| "PATCH is complicated, let's just use PUT" | PUT requires the full object every time. PATCH is what clients actually want. |
| "We'll version the API when we need to" | Breaking changes without versioning break consumers. Design for extension from the start. |
| "Nobody uses that undocumented behavior" | Hyrum's Law: if it's observable, somebody depends on it. Treat every public behavior as a commitment. |
| "We can just maintain two versions" | Multiple versions multiply maintenance cost and create diamond dependency problems. Prefer the One-Version Rule. |
| "Internal APIs don't need contracts" | Internal consumers are still consumers. Contracts prevent coupling and enable parallel work. |
/api/createTask, /api/getUsers)After designing an API:
Alternatives
coreyhaines31/marketingskills
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
JasonColapietro/suede-creator-skills
Suede-owned experimentation discipline for hypotheses, sample sizing, test duration, significance, and repeatable experiment programs. Use when comparing variants, deciding whether a result is reliable, or building an experiment backlog and cadence. NOT FOR: analytics instrumentation (use suede-analytics), post-click conversion diagnosis (use suede-site-alchemy), or writing the variant copy itself (use suede-copy).
narrative-io/narrative-skills-marketplace
Translate a fuzzy analytical question into a rigorous investigation plan. Interrogates the ask, grounds the plan in the available data dictionary, applies analytical best practices, and produces a structured brief of query specifications for a downstream query-writing skill. Plans, does not write SQL. Use when: "why did X drop", "is there a relationship between A and B", "who are our highest-value customers", "what's driving the change in Y", "investigate this trend", "design an analysis for", "
event4u-app/agent-config
Use BEFORE writing or editing any non-trivial UI — inventories components, design tokens, shadcn primitives, and reusable patterns into state.ui_audit. Hard gate for the ui directive set.