بنقرة واحدة
api-design-review
Review an API design against consistency, versioning, auth, and error-handling best practices.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Review an API design against consistency, versioning, auth, and error-handling best practices.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Review code changes and report issues by severity with actionable fixes.
Sharpen a fuzzy intention into one measurable objective string that drives the rest of the work.
Convert a Prompt Flow PRS pipeline submission to run a Microsoft Agent Framework workflow.
Build a Model Context Protocol (MCP) server that lets an LLM call into external tools and resources.
Summarize PDF documents into concise bullet-point digests.
Bump a dependency version across a pnpm workspace and update lockfile.
| slug | api-design-review |
| name | API Design Review |
| version | 0.2.0 |
| description | Review an API design against consistency, versioning, auth, and error-handling best practices. |
| category | dev-tools |
| tags | ["api","design","review","rest","graphql"] |
| inputs | [{"name":"api_spec","type":"string","required":true,"description":"OpenAPI spec, schema, or endpoint description"},{"name":"context","type":"string","required":false,"description":"Team conventions or existing API examples"}] |
| output | {"format":"markdown","description":"Review with severity-ranked findings and concrete recommendations."} |
| author | badhope |
| license | MIT |
| created | "2026-06-21T00:00:00.000Z" |
| updated | "2026-06-22T00:00:00.000Z" |
Before publishing a new API, adding a breaking endpoint, or standardizing an existing surface.
Provide the API spec or endpoint list plus any team conventions the API should align with. Include the protocol (REST/GraphQL/gRPC) if known.
A markdown report: summary score, findings grouped by severity, and actionable recommendations with example rewrites.
You are a senior API architect reviewing the provided API design.
Evaluate across these dimensions:
1. Consistency: naming, paths, HTTP methods, status codes, error shapes
2. Versioning: strategy (URL / header / deprecation), breaking-change handling
3. Authentication & authorization: scheme choice, scope granularity
4. Error handling: structured errors, status-code correctness, retry signals
5. Pagination & filtering: cursor vs offset, query param design
6. Documentation: example requests/responses, required vs optional fields
Output format:
## Score: X/10
## Findings
- **[Severity] Title**: explanation and recommended fix
## Recommendations
1. ...
Keep findings specific; avoid generic advice like "use REST".
Choose the right protocol based on your use case.
┌─────────────────────┐
│ What's your main │
│ client type? │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ Web/Mobile │ │ Service │ │ IoT / │
│ (BFF pattern) │ │ to Service│ │ Embedded │
└───────┬───────┘ └─────┬──────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌───────────┐ ┌─────────────┐
│ REST or GraphQL│ │ gRPC or │ │ gRPC │
│ (flexibility) │ │ REST │ │ (binary, │
└────────────────┘ └───────────┘ │ low bandwidth)
└─────────────┘
GraphQL advantages:
- Multiple clients with different data needs from same endpoint
- Mobile clients needing to minimize payload
- Rapid iteration on mobile (no backend changes for field selection)
- Complex domain where client-driven queries reduce over-fetching
gRPC advantages:
- High-frequency service-to-service calls
- Binary protocol (smaller payloads, faster parsing)
- Strong typing + code generation for 10+ languages
- Streaming support (bidirectional)
- Well-suited for microservices interop
REST advantages:
- Simple public APIs
- HTTP infrastructure (caching, proxies, auth)
- Human-readable payloads
- Best for CRUD on resources
- Mature tooling and documentation
Specific things to check for GraphQL schemas.
# Bad: Fetching entire User object for just name
query { user { name email createdAt updatedAt role permissions } }
# Good: Use fragments to select only needed fields
query { user { ...UserName } }
fragment UserName on User { name }
# Bad: Causes N+1 queries (1 for users, N for each post)
query {
users {
name
posts { title } # Triggers separate query per user
}
}
# Solution: Use DataLoader batching
# Bad: No pagination on large collections
type Query {
articles: [Article!]!
}
# Good: Connection pattern with cursor pagination
type Query {
articles(first: Int, after: String): ArticleConnection!
}
type ArticleConnection {
edges: [ArticleEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ArticleEdge {
cursor: String!
node: Article!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
# Bad: Using errors array for application errors
{
"errors": [
{ "message": "User not found", "extensions": { "code": "NOT_FOUND" } }
],
"data": null
}
# Good: Use union or interface for expected errors
type Query {
user(id: ID!): UserResult!
}
union UserResult = User | UserNotFoundError
type UserNotFoundError {
message: String!
userId: ID!
}
# Client code:
# result.user.on(User, u => render(u))
# result.user.on(UserNotFoundError, e => render404(e.userId))
| Anti-pattern | Problem | Solution |
|---|---|---|
input FooInput { id: ID! } | Input types with ID fields promote leaky references | Use createFoo vs updateFoo separate inputs |
| Nullable fields without meaning | Can't tell if null means "unknown" or "not set" | Use explicit sentinel values or separate fields |
String for everything | Loses type safety | Use custom scalars (DateTime, URL, JSON) |
Boolean field isActive | Fragile naming | Consider enum status for more states |
| No versioning strategy | Breaking changes affect all clients | Use @deprecated(reason: "Use fieldV2") |
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details map[string]any `json:"details,omitempty"`
RequestID string `json:"request_id"`
Stack string `json:"-"` // Only in dev
}
func (e *APIError) Error() string {
return fmt.Sprintf("%s: %s (request_id=%s)", e.Code, e.Message, e.RequestID)
}
// HTTP status: 400 for validation, 401 for auth, 403 for forbidden, 404 for not found
from fastapi import HTTPException
raise HTTPException(
status_code=400,
detail={
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{"field": "email", "issue": "invalid format"},
{"field": "age", "issue": "must be positive"}
],
"request_id": "req_abc123"
}
)
interface ApiError {
code: string; // Machine-readable: VALIDATION_ERROR, NOT_FOUND
message: string; // Human-readable
details?: Record<string, unknown>; // Structured extra info
request_id: string; // For log correlation
errors?: Array<{ field: string; message: string }>; // Field-level
}
class ApiError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public requestId: string,
public details?: Record<string, unknown>
) {
super(message);
this.name = 'ApiError';
}
toJSON(): ApiError {
return {
code: this.code,
message: this.message,
request_id: this.requestId,
...(this.details && { details: this.details })
};
}
}
public class ApiError {
private String code;
private String message;
private String requestId;
private Map<String, Object> details;
// Builder pattern
public static ApiError of(String code, String message, String requestId) {
return new ApiError(code, message, requestId, Collections.emptyMap());
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ApiException.class)
public ResponseEntity<ApiError> handle(ApiException ex) {
return ResponseEntity
.status(ex.getStatusCode())
.body(ApiError.of(ex.getCode(), ex.getMessage(), ex.getRequestId()));
}
}
| Anti-pattern | Example | Fix |
|---|---|---|
| Verbs in URL | POST /users/create | POST /users |
| Nested resources too deep | GET /orgs/{id}/teams/{id}/members/{id}/roles/{id} | Flatten or use query: GET /team-members/{id}/roles |
| Inconsistent naming | /user-profiles vs /users_info | Pick one: /users |
| Missing pluralization | GET /user/{id} | GET /users/{id} |
| Content type in URL | GET /users.json | Use Accept: application/json header |
# Bad: Version in URL but no deprecation path
GET /v1/users/123
# Good: Version in URL WITH deprecation headers
GET /v1/users/123
Deprecated: true
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v2/users/123>; rel="successor-version"
# Alternative: Header-based versioning
GET /users/123
API-Version: 2024-01-01
| Anti-pattern | Problem | Fix |
|---|---|---|
| API keys in URL query params | Leaks in server logs, browser history | Use Authorization: Bearer <token> header |
| No scopes / overly broad tokens | Can't limit access granularly | Define scopes: users:read, users:write |
| Rolling your own auth | Crypto vulnerabilities | Use OAuth2/OIDC, mTLS for service auth |
| No rate limiting headers | Clients can't back off | Return X-RateLimit-Remaining, Retry-After |
# Bad: Offset-based on large datasets
GET /events?page=1000000&limit=20
# Skipping millions of rows is slow in DB
# Good: Cursor-based
GET /events?after=cursor_abc&limit=20
Response: { "data": [...], "next_cursor": "cursor_xyz", "has_more": true }
# For stable sorting with cursor: include sort key with cursor
GET /events?after=2024-01-15T10:00:00Z_id_123&limit=20
[] not null for collectionsIdempotency-Key headerContent-Type on errors — Even error responses should be JSON with proper Content-TypeX-Request-ID or similar for tracingAccess-Control-Allow-Origin: * for authenticated APIsInput:
api_spec: 'GET /users/{id}/orders?limit=20&page=1 returns 200 or 404'
context: 'We use URL versioning and OAuth2 scopes'
Output:
## Score: 6/10
## Findings
- **[MED] Pagination uses page/limit**: Offset-based pagination on high-churn collections (orders) causes duplicate/missing items as data changes. Prefer cursor-based.
- **[MED] 404 for empty orders**: Returns 404 when user has no orders. This breaks client caching and forces error handling for a normal case. Return 200 with `{ "orders": [], "total": 0 }`.
- **[LOW] Missing RateLimit headers**: No `X-RateLimit-Remaining` or `Retry-After` on 429 responses.
- **[LOW] No idempotency key support**: POST to `/orders` (implied) should accept `Idempotency-Key` to prevent duplicate charges.
## Recommendations
1. Replace `page`/`limit` with `cursor` + `after`/`before` parameters. Return `next_cursor` in response envelope.
2. Return 200 with empty array instead of 404 for no orders.
3. Add rate limit headers and document retry strategy.
4. Add `Idempotency-Key` header support for payment-creating POSTs.
## Improved Response Shape
```json
{
"orders": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTAwfQ==",
"has_more": true,
"total": null
}
}
# See Also
- [RFC 7807 Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807)
- [REST API Design Rulebook](https://restfulapi.net/)
- [Google API Design Guide](https://cloud.google.com/apis/design)
- [GraphQL Schema Design](https://graphql.org/learn/)
- [gRPC Documentation](https://grpc.io/docs/)
## Footguns
These are the bugs that bite every new user.
Check them before shipping:
- **200 for empty results**: Returning 404 for "no results" breaks client caching. Return `[]` with 200 instead.
- how to detect: clients report caching errors when no results
- how to fix: return empty array with 200, use 404 only when resource doesn't exist
- **No idempotency keys on POST**: Creating resources without idempotency keys risks duplicate charges/operations on retry.
- how to detect: look for duplicate resource creation in logs
- how to fix: accept `Idempotency-Key` header and cache responses
- **Pagination uses offset**: Offset-based pagination on large datasets causes duplicates/missing items as data changes.
- how to detect: clients report seeing same items twice or missing items during pagination
- how to fix: use cursor-based pagination instead
- **API keys in URL query params**: Tokens leak in server logs, browser history, and proxies.
- how to detect: search logs for tokens in query strings
- how to fix: use `Authorization: Bearer <token>` header instead
- **Missing rate limit headers**: Clients can't back off properly without `X-RateLimit-Remaining` or `Retry-After`.
- how to detect: clients report getting rate limited without warning
- how to fix: return rate limit headers on all responses