Skip to content
Development
Skill

/api-design

REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.

From plugin
spellbook
25893 skills10 agents
Install
$ npx -y skills add majiayu000/spellbook --skill api-design --agent claude-code

How 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-design

Context preview

The summary Claude sees to decide when to auto-load this skill.

REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.

SKILL.md

api-design.SKILL.md
name: api-design
description: REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.

API Design

Core Principles

  • **Contract-First** — Define API spec before implementation
  • **OpenAPI 3.2** — Use OpenAPI for REST API documentation
  • **URL Versioning** — Version in path `/v1/`, with Sunset headers
  • **Idempotency** — PUT/DELETE must be idempotent, POST uses Idempotency-Key
  • **Cursor Pagination** — Avoid offset-based pagination
  • **RFC 7807 Errors** — Standard Problem Details format
  • **No backwards compatibility** — Delete, don't deprecate

---

Quick Reference

When to Use What

| Scenario | Choice | Reason | |----------|--------|--------| | Public API / MVP | REST | Simple, universal, easy debugging | | Frontend-driven / Mobile | GraphQL | Fetch exactly what you need | | Microservices internal | gRPC | High performance, strong typing | | Real-time data | gRPC / GraphQL Subscriptions | Bidirectional streaming |

---

REST API Design

Resource Naming

# Good
GET  /users              # List users
GET  /users/123          # Get user
POST /users              # Create user
PUT  /users/123          # Replace user
PATCH /users/123         # Update user
DELETE /users/123        # Delete user

# Nested resources
GET /users/123/orders    # User's orders

# Actions (when CRUD doesn't fit)
POST /users/123/activate # Action on resource

# Query parameters for filtering
GET /users?status=active&role=admin&limit=20

HTTP Methods

| Method | Purpose | Idempotent | Safe | |--------|---------|------------|------| | GET | Read | Yes | Yes | | POST | Create | No | No | | PUT | Replace | Yes | No | | PATCH | Update | No | No | | DELETE | Remove | Yes | No |

Status Codes

# Success
200 OK              - Successful GET/PUT/PATCH
201 Created         - Successful POST (include Location header)
204 No Content      - Successful DELETE

# Client Errors
400 Bad Request     - Malformed request syntax
401 Unauthorized    - Missing/invalid authentication
403 Forbidden       - Authenticated but not authorized
404 Not Found       - Resource doesn't exist
409 Conflict        - Duplicate/conflict (e.g., unique constraint)
422 Unprocessable   - Validation failed
429 Too Many        - Rate limited

# Server Errors
500 Internal Error  - Unexpected server error
503 Unavailable     - Service temporarily down

Error Response (RFC 7807)

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request contains invalid parameters",
  "instance": "/users/123",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "age", "message": "Must be positive integer" }
  ]
}

Pagination (Cursor-Based)

// Request
GET /users?limit=20&cursor=eyJpZCI6MTAwfQ

// Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "prev_cursor": "eyJpZCI6ODB9",
    "has_next": true,
    "has_prev": true,
    "limit": 20
  }
}

Versioning

# URL versioning (recommended)
GET /v1/users
GET /v2/users

# Deprecation headers
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: </v2/users>; rel="successor-version"

Idempotency

# For non-idempotent operations (POST)
POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

# Server stores result and returns same response for duplicate key

---

GraphQL Design

Schema Principles

  • **Domain-driven** — Schema reflects business domain, not database
  • **Descriptive names** — Clear field/type names for monitoring
  • **Limit nesting** — Deep nesting hurts performance
  • **Use @key** — Mark entity identifiers for Federation

Type Definitions

type Query {
  user(id: ID!): User
  users(first: Int, after: String, filter: UserFilter): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}

type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
  orders(first: Int, after: String): OrderConnection!
  createdAt: DateTime!
}

# Relay-style pagination
type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Error Handling

type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
}

# Union for typed errors
union CreateUserPayload = User | ValidationError | ConflictError

type ValidationError {
  message: String!
  field: String
  code: String!
}

type ConflictError {
  message: String!
  existingId: ID!
}

N+1 Prevention

// Use DataLoader for batching
const userLoader = new DataLoader(async (ids: string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });
  return ids.map(id => users.find(u => u.id === id));
});

// Resolver
const resolvers = {
  Order: {
    user: (order) => userLoader.load(order.userId),
  },
};

---

gRPC Design

Proto Definition

syntax = "proto3";

package api.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service UserService {
  // Unary
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);

  // Server streaming
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // Client streaming
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);

  // Bidirectional streaming
  rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate);
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  google.protobuf.Timestamp created_at = 4;
}

message GetUserRequest {
  stri
Read more
Ships withspellbook

Cross-runtime skills for Claude Code, Codex, and multi-agent workflows.

Get the whole plugin

Other skills on spellbook.