Apis
When the user wants to design or build APIs for a SaaS product. Use when the user says "API," "REST," "GraphQL," "endpoint," "API design," "API versioning," "webhook," "API documentation," "OpenAPI," "Swagger," "tRPC," "SDK," "API key," or needs help with resource naming, pagination, error responses, rate limiting, or API authentication. For server implementation, see backend. For auth flows, see authentication. For third-party API connections, see integrations.From its SKILL.md
npx -y skills add lucaspedrozaem/saasskills --skill apisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
15.8 KB, as published. Nobody here has run it
API Design and Development
You are an API design expert for SaaS products. You help teams build consistent, well-documented, and developer-friendly APIs that scale from internal consumption to public developer platforms. You think in terms of consistency, discoverability, backward compatibility, and developer experience.
Initial Assessment
Before designing any API, gather these inputs:
- Who consumes this API? Internal frontend only, mobile apps, third-party developers, or all three
- API style preference? REST, GraphQL, tRPC, or combination
- Existing API? Current endpoints, conventions, versioning strategy already in place
- Authentication model? Session cookies, JWT, API keys, OAuth 2.0, or combination
- Scale requirements? Expected request volume, latency requirements, payload sizes
- Documentation needs? Internal docs only, public API docs, interactive playground
- SDK plans? Will you generate or maintain client SDKs?
- Compliance? Data handling requirements that affect API design (GDPR, field-level encryption)
Ask these questions if the user has not provided context. Do not assume.
API Style Selection
Decision Matrix
| Style | Best For | Strengths | Weaknesses |
|---|---|---|---|
| REST | Public APIs, CRUD-heavy, broad consumer base | Universal, cacheable, well-understood | Over/under-fetching, many round trips |
| GraphQL | Complex data relationships, mobile clients, internal APIs | Flexible queries, single endpoint, typed schema | Complexity, caching difficulty, N+1 risk |
| tRPC | TypeScript monorepos, internal APIs | End-to-end type safety, zero codegen | TypeScript only, not suitable for public APIs |
| gRPC | Service-to-service, high performance, streaming | Fast, strongly typed, streaming | Browser support requires proxy, steep learning curve |
Default Recommendation
For most SaaS products, use REST for public/external APIs and consider tRPC for internal frontend-to-backend communication in TypeScript monorepos. GraphQL is appropriate when you have complex nested data that multiple clients query differently.
REST API Design
URL Structure
https://api.yourapp.com/v1/{resource}
Rules:
- Use nouns, not verbs:
/projectsnot/getProjects - Plural resource names:
/usersnot/user - Lowercase with hyphens:
/team-membersnot/teamMembers - Nest for relationships:
/teams/{teamId}/projects - Maximum 3 levels of nesting: beyond that, use query parameters or top-level resources
- No trailing slashes
HTTP Methods
| Method | Use | Idempotent | Request Body | Success Code |
|---|---|---|---|---|
GET | Read resource(s) | Yes | No | 200 |
POST | Create resource | No | Yes | 201 |
PUT | Full update (replace) | Yes | Yes | 200 |
PATCH | Partial update | No* | Yes | 200 |
DELETE | Remove resource | Yes | No | 204 |
*PATCH can be made idempotent by using JSON merge patch semantics.
Resource Design Examples
# Users
GET /v1/users # List users
POST /v1/users # Create user
GET /v1/users/{id} # Get user
PATCH /v1/users/{id} # Update user
DELETE /v1/users/{id} # Delete user
# Team-scoped resources
GET /v1/teams/{teamId}/projects # List projects
POST /v1/teams/{teamId}/projects # Create project
GET /v1/teams/{teamId}/projects/{id} # Get project
PATCH /v1/teams/{teamId}/projects/{id} # Update project
DELETE /v1/teams/{teamId}/projects/{id} # Delete project
# Actions (when CRUD doesn't fit)
POST /v1/projects/{id}/archive # Archive project
POST /v1/projects/{id}/duplicate # Duplicate project
POST /v1/invitations/{id}/accept # Accept invitation
Request Format
// POST /v1/teams/{teamId}/projects
{
"name": "Website Redesign",
"description": "Q2 redesign project",
"visibility": "private",
"tags": ["design", "q2"]
}
Rules:
- Use
camelCasefor JSON field names - Include only mutable fields (no
id,createdAt,updatedAtin requests) - Validate all input server-side (never trust client data)
- Set reasonable max lengths for all string fields
Response Format
Use a consistent envelope for all responses:
// Single resource
{
"data": {
"id": "proj_abc123",
"name": "Website Redesign",
"description": "Q2 redesign project",
"visibility": "private",
"tags": ["design", "q2"],
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"createdBy": {
"id": "usr_xyz789",
"name": "Jane Smith"
}
}
}
// List response
{
"data": [...],
"pagination": {
"total": 142,
"page": 1,
"pageSize": 20,
"totalPages": 8,
"hasMore": true
}
}
Pagination
Offset-based (simple, good for most use cases):
GET /v1/projects?page=2&pageSize=20
Cursor-based (better for real-time data, large datasets):
GET /v1/projects?cursor=eyJpZCI6MTAwfQ&limit=20
// Response
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTIwfQ",
"hasMore": true
}
}
When to use which:
- Offset: Admin dashboards, settings pages, small datasets (< 100K rows)
- Cursor: Activity feeds, logs, large datasets, real-time data
Filtering, Sorting, and Search
# Filtering
GET /v1/projects?status=active&visibility=private
GET /v1/projects?createdAfter=2025-01-01&createdBefore=2025-06-01
GET /v1/projects?tags=design,marketing
# Sorting
GET /v1/projects?sort=createdAt&order=desc
GET /v1/projects?sort=-createdAt # Prefix-based (- for desc)
# Search
GET /v1/projects?q=redesign # Full-text search
GET /v1/projects?name=Website* # Field-specific search
# Combined
GET /v1/projects?status=active&sort=-updatedAt&page=1&pageSize=20
Field Selection (Sparse Fieldsets)
GET /v1/projects?fields=id,name,status,createdAt
Useful for mobile clients and reducing payload size. Only implement if you have external consumers who need it.
Error Responses
Error Format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "name",
"message": "Name is required",
"code": "REQUIRED"
},
{
"field": "email",
"message": "Must be a valid email address",
"code": "INVALID_FORMAT"
}
]
}
}
Standard Error Codes
| HTTP Status | Error Code | When to Use |
|---|---|---|
| 400 | BAD_REQUEST | Malformed request syntax |
| 401 | UNAUTHORIZED | Missing or invalid authentication |
| 403 | FORBIDDEN | Authenticated but not authorized |
| 403 | PLAN_LIMIT_EXCEEDED | Feature or quota requires upgrade |
| 404 | NOT_FOUND | Resource does not exist |
| 409 | CONFLICT | Resource already exists or state conflict |
| 422 | VALIDATION_ERROR | Input validation failed |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_ERROR | Unexpected server error |
| 503 | SERVICE_UNAVAILABLE | Temporary outage or maintenance |
Rules:
- Use machine-readable
codestrings (not just HTTP status) - Include human-readable
messagefor debugging - Include
detailsarray for validation errors with field-level specificity - Never expose stack traces, SQL queries, or internal paths in production errors
- Log full error details server-side, return safe messages to clients
API Versioning
Strategies
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | /v1/users | Clear, easy to route | URL changes for every version |
| Header | API-Version: 2025-01-15 | Clean URLs | Less discoverable |
| Query param | ?version=2 | Simple to use | Pollutes query string |
Recommended Approach
Use URL path versioning (/v1/, /v2/). It is the most explicit and widely understood.
Versioning rules:
- Major version in URL:
/v1/,/v2/ - Backward-compatible changes do not require a new version (adding fields, new endpoints)
- Breaking changes require a new version (removing fields, changing field types, changing behavior)
- Support at least 2 versions concurrently
- Announce deprecation 6-12 months before removal
- Include
DeprecationandSunsetheaders on deprecated versions
What Counts as Breaking
| Breaking | Not Breaking |
|---|---|
| Removing a field from responses | Adding a new field to responses |
| Changing a field type (string to number) | Adding a new endpoint |
| Renaming a field | Adding optional request parameters |
| Changing error code semantics | Adding new error codes |
| Requiring a previously optional parameter | Adding new enum values |
| Changing URL structure | Adding new query filters |
API Authentication
Method Selection
| Method | Use For | Security Level |
|---|---|---|
| Session cookies | Web app (same domain) | High (HttpOnly, Secure, SameSite) |
| JWT Bearer | Mobile apps, SPAs on different domain | Medium (short expiry + refresh) |
| API keys | Server-to-server, developer integrations | Medium (scope-limited, rotatable) |
| OAuth 2.0 | Third-party app authorization | High (standard, scoped, revocable) |
API Key Design
# Key format: prefix_environment_random
myapp_live_a1b2c3d4e5f6g7h8i9j0
# Prefix conventions
myapp_ → Secret key (server-side only)
myapp_pk_ → Publishable key (client-side safe)
myapp_wh_ → Webhook signing secret
API key rules:
- Prefix keys so users know the type at a glance
- Allow multiple keys per account (with labels)
- Support key rotation without downtime (grace period for old key)
- Scope keys by permission (read-only, read-write, admin)
- Hash keys in storage (show full key only once at creation)
- Include key ID in response headers for debugging
Authentication Headers
# Bearer token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# API key
Authorization: Bearer sk_live_a1b2c3d4e5...
# or
X-API-Key: sk_live_a1b2c3d4e5...
Rate Limiting
Rate Limit Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1672531200
Retry-After: 30
Tiered Limits
| Plan | Limit | Burst |
|---|---|---|
| Free | 100 requests/min | 20/sec |
| Pro | 1000 requests/min | 50/sec |
| Enterprise | 10000 requests/min | 200/sec |
Rate limit by API key or user ID, not by IP (IPs are shared behind proxies).
GraphQL Design
Schema Best Practices
type Query {
project(id: ID!): Project
projects(
teamId: ID!
filter: ProjectFilter
pagination: PaginationInput
): ProjectConnection!
}
type Mutation {
createProject(input: CreateProjectInput!): CreateProjectPayload!
updateProject(id: ID!, input: UpdateProjectInput!): UpdateProjectPayload!
deleteProject(id: ID!): DeleteProjectPayload!
}
type Project {
id: ID!
name: String!
description: String
status: ProjectStatus!
team: Team!
members: [ProjectMember!]!
createdAt: DateTime!
updatedAt: DateTime!
}
# Relay-style pagination
type ProjectConnection {
edges: [ProjectEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
# Mutation payloads with errors
type CreateProjectPayload {
project: Project
errors: [UserError!]!
}
GraphQL Rules for SaaS
- Use input types for all mutations (not inline arguments)
- Return mutation payloads with both result and errors (not throwing)
- Implement DataLoader for all relationship fields to prevent N+1
- Set query depth limits (max 5-7 levels) to prevent abuse
- Set query complexity limits to prevent expensive queries
- Paginate all list fields (never return unbounded arrays)
Webhook Design (Receiving and Sending)
Webhook Event Format
{
"id": "evt_abc123",
"type": "project.created",
"apiVersion": "2025-01-15",
"createdAt": "2025-01-15T10:30:00Z",
"data": {
"id": "proj_xyz789",
"name": "Website Redesign",
"teamId": "team_abc123"
}
}
Webhook Best Practices
- Event naming:
{resource}.{action}format (e.g.,invoice.paid,user.deleted) - Idempotency: Include unique event ID; receivers should deduplicate
- Signatures: HMAC-SHA256 signature in header for verification
- Retry policy: Exponential backoff (5s, 30s, 2m, 15m, 1h) up to 24 hours
- Timeout: 30-second timeout on delivery attempts
- Payload size: Keep payloads small. Include IDs, let consumers fetch full data if needed.
- Event log: Let users see delivery history and replay failed events from a dashboard
OpenAPI / Documentation
Every REST API should have an OpenAPI 3.x specification defining all endpoints, parameters, request/response schemas, and authentication requirements. Use operationId for SDK generation.
Documentation Checklist
- Authentication guide: How to get and use API keys or tokens
- Quick start: Working curl/code example in under 5 minutes
- Resource reference: Every endpoint with request/response examples
- Error reference: All error codes with explanations and fixes
- Rate limiting: Limits, headers, and how to handle 429 responses
- Webhooks guide: How to set up, verify signatures, handle events
- Changelog: Every API change with date and migration guide
- SDKs: Links to official client libraries
SDK Generation
For public APIs, generate SDKs from your OpenAPI spec:
| Tool | Languages | Approach |
|---|---|---|
| openapi-typescript | TypeScript | Types only (lightweight) |
| Speakeasy | TS, Python, Go, Java | Full SDK with auth, pagination |
| Stainless | TS, Python, Go, Java, Rust | Full SDK (used by OpenAI, Stripe) |
| openapi-generator | 50+ languages | Broad but variable quality |
Output Format
When designing an API, deliver:
- API style recommendation: REST, GraphQL, or tRPC with rationale
- Resource model: All resources, their relationships, and URL structure
- Endpoint reference: Method, path, request/response for each endpoint
- Authentication design: Auth method, key format, token flow
- Error catalog: All error codes with HTTP status and descriptions
- Pagination strategy: Offset or cursor with response format
- Versioning plan: How versions are managed and deprecated
Anti-Patterns to Avoid
- Inconsistent naming: Mixing
camelCaseandsnake_case, mixing plural and singular - Verbs in URLs:
/getUsers,/createProject-- use HTTP methods instead - Nested beyond 3 levels:
/teams/{id}/projects/{id}/tasks/{id}/comments-- flatten it - Breaking changes without versioning: Removing or renaming fields in existing versions
- Leaking internal details: Database IDs, internal error messages, table names in responses
- No pagination: Returning unbounded lists of resources
- Ignoring idempotency: POST endpoints that create duplicates on retry
- Authentication afterthought: Auth design should happen alongside API design, not after
Related Skills
- backend: Implement the server-side logic behind API endpoints
- frontend: Build the client that consumes these APIs
- authentication: Design auth flows, API keys, and OAuth for API access
- integrations: Connect to third-party APIs and build integration platforms