Back to skills
SKILL.md
Api Patterns
ASecurityUse when API design mastery. REST, GraphQL, tRPC, and gRPC selection. Request/response design, pagination (cursor/offset), filtering, versioning, rate limiting, error formats (RFC 9457), authentication (JWT/OAuth2/API keys), idempotency, file uploads, webhooks, and OpenAPI documentation. Use when designing APIs, choosing protocols, or implementing API standards.
- 5 stars
- 0 votes
- 0 copies
- 1 view
- Added September 27, 2026
Works with
Security analysis
100/100Pro scans all 2 files and shows the line behind each finding
npx -y skills add Harmitx7/tribunal-kit --skill api-patterns --agent claude-codeAre you the author of Api Patterns?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/harmitx7-api-patterns-tribunal-kit)---
name: api-patterns
description: "Use when API design mastery. REST, GraphQL, tRPC, and gRPC selection. Request/response design, pagination (cursor/offset), filtering, versioning, rate limiting, error formats (RFC 9457), authentication (JWT/OAuth2/API keys), idempotency, file uploads, webhooks, and OpenAPI documentation. Use when designing APIs, choosing protocols, or implementing API standards."
version: 5.0.0
last-updated: 2026-09-13
skills:
- api-security-auditor
- backend-security-expert
- schema-reviewer
tools: Read, Grep, Glob, Bash, Edit, Write
scripts-binding:
- .agent/scripts/lint_runner.js
- .agent/scripts/verify_all.js
---
# API Patterns β Design & Protocol Mastery
---
## π οΈ Technical Architecture & Reference Recipes
---
## Hallucination Traps (Read First)
- β JWT in URL query params β β
`Authorization: Bearer` header only. Query params get logged in server access logs.
- β Assuming JWT is encrypted β β
JWT is base64-encoded (NOT encrypted). Anyone can decode it. Never put secrets/PII in the payload.
- β Offset pagination on large tables β β
`OFFSET 100000` scans and discards 100K rows. Use cursor pagination for tables > 10K rows.
- β Verbs in REST URLs (`/api/getUsers`) β β
Nouns only (`GET /api/users`). HTTP method IS the verb.
- β `POST` is idempotent β β
`POST` is NOT idempotent β requires `Idempotency-Key` header for safe retries.
- β GraphQL has no security risks β β
Deeply nested queries are a DoS vector. Set max depth, query cost limits. Disable introspection in production.
---
## Protocol Selection Matrix
| Protocol | Use When |
| ------------- | ------------------------------------------------------------------------------------- |
| **REST** | Public APIs, 3rd-party consumers, standard CRUD, HTTP caching |
| **GraphQL** | Complex nested data, multiple clients, flexible queries, mobile bandwidth sensitivity |
| **tRPC** | Full-stack TypeScript (Next.js monorepo), shared types, no codegen |
| **gRPC** | Internal microservices, high-throughput, streaming, binary protocol |
| **WebSocket** | Bidirectional real-time (chat, gaming, live collaboration) |
| **SSE** | Server-to-client streaming only (AI token streaming, live feeds) |
---
## REST Design
### URL Conventions
```
β
GET /api/v1/users list users
β
GET /api/v1/users/123 get user by ID
β
POST /api/v1/users create user
β
PATCH /api/v1/users/123 partial update
β
DELETE /api/v1/users/123 delete user
β
GET /api/v1/users/123/posts nested resource
β /api/getUsers /api/createUser /api/user (singular) /api/Users (uppercase)
```
### HTTP Status Codes
```
200 OK β GET / PUT / PATCH success
201 Created β POST success (include Location: /api/v1/users/123 header)
204 No Content β DELETE success
400 Bad Request β Malformed request / missing fields
401 Unauthorized β Missing or invalid authentication
403 Forbidden β Authenticated but not authorized
404 Not Found β Resource does not exist
409 Conflict β Duplicate resource (email already exists)
422 Unprocessable β Valid JSON, semantically invalid data
429 Too Many Req β Rate limit exceeded
500 Internal β Unhandled server error β NEVER expose stack traces
```
### Response Envelope
```typescript
interface ApiResponse<T> {
data: T;
meta?: Record<string, unknown>;
}
interface ApiError {
error: {
code: string; // machine-readable: "VALIDATION_ERROR"
message: string; // human-readable: "Email is already in use"
details?: Array<{ field: string; message: string }>; // field-level errors
requestId?: string; // for support/tracing
};
}
```
---
## Pagination
```typescript
// β
Cursor-based β required for large/dynamic datasets
// GET /api/v1/posts?cursor=eyJpZCI6MTAwfQ&limit=20
const posts = await db.post.findMany({
where: { id: { lt: decodeCursor(req.query.cursor).id } },
orderBy: { id: 'desc' },
take: limit + 1, // fetch one extra to determine hasMore
});
const hasMore = posts.length > limit;
if (hasMore) posts.pop();
return { data: posts, meta: { hasMore, nextCursor: encodeCursor(posts.at(-1)) } };
// Offset-based β only for small datasets where users need page jumping
// GET /api/v1/posts?page=3&limit=20
// β TRAP: OFFSET 100000 scans and discards 100K rows β degrades badly at scale
```
---
## Idempotency
```typescript
// POST /api/v1/payments with header: Idempotency-Key: <uuid>
app.post('/api/v1/payments', async (req, res) => {
const key = req.headers['idempotency-key'];
if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key' });
const cached = await redis.get(`idempotency:${key}`);
if (cached) return res.status(200).json(JSON.parse(cached));
const result = await processPayment(req.body);
await redis.set(`idempotency:${key}`, JSON.stringify(result), 'EX', 86400);
return res.status(201).json(result);
});
// GET, PUT, DELETE β naturally idempotent (safe to retry without a key)
// POST, PATCH β NOT idempotent by default β require Idempotency-Key
```
---
## Webhooks
```typescript
// HMAC signature verification (always verify β never trust unsigned webhooks)
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(payload: string, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(payload).digest('hex');
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
app.post('/webhooks', (req, res) => {
if (
!verify(JSON.stringify(req.body), req.headers['x-webhook-signature'] as string, WEBHOOK_SECRET)
)
return res.status(401).send('Invalid signature');
res.status(200).send('OK'); // respond immediately
processWebhookAsync(req.body); // process asynchronously
});
// Retry policy: 3 retries with exponential backoff (1s β 10s β 100s)
// Include unique event ID in payload for receiver-side deduplication
```
---
## Versioning
```
URL path (recommended): /api/v1/users β simplest, most common, cache-friendly
Header: Accept: application/vnd.api.v1+json
Query param: /api/users?version=1 β messy, avoid
Rules:
- Start at v1, never v0
- Breaking changes = new major version (v2)
- Non-breaking additions (new optional fields) do NOT need a version bump
- Deprecate before removing β give consumers 6+ months notice
```
---
## Rate Limiting
```
Strategy How When
Token bucket β Burst allowed, refills Most APIs (recommended)
Sliding window β Smooth distribution Strict fairness required
Fixed window β Simple counter per period Basic needs only
Response headers to always include:
X-RateLimit-Limit (max requests in window)
X-RateLimit-Remaining (requests left)
X-RateLimit-Reset (Unix timestamp when limit resets)
Retry-After (seconds to wait on 429)
```
---
## GraphQL Security
```
Protect against:
Depth attacks β Set max query depth (typically 7β10)
Cost attacks β Calculate query complexity score, reject > threshold
Batch abuse β Limit batch size / alias count
Introspection β Disable in production (exposes full schema to attackers)
```
---
## Authentication Selection
| Pattern | Best For |
| ----------------------------------------------- | -------------------------------------- |
| **JWT** (short-lived access + httpOnly refresh) | Stateless services, microservices |
| **Session** | Traditional server-rendered apps |
| **OAuth 2.0 / OIDC** | Third-party login, delegated access |
| **API Key** | Server-to-server, public API consumers |
| **Passkey (WebAuthn)** | Modern passwordless (2026+) |
Files in this skill
- SKILL.md
- scripts/api_validator.py
Attribution
Comments
Loading commentsβ¦