API Design Vocabulary

38 terms for talking about REST, GraphQL, and gRPC APIs in plain English — what each one means, an example, and the gotcha worth knowing.

Last reviewed:

Sections

REST fundamentals

resource

The core noun of a REST API — a thing (a user, an order, a product) identified by a URI and manipulated via HTTP methods.

GET    /users/42       # read the resource
DELETE /users/42       # remove the resource

💡 Good REST design is "resource-first": model the nouns, then attach verbs (HTTP methods) to them.

CRUD

Create, Read, Update, Delete — the four basic data operations, typically mapped to POST, GET, PUT/PATCH, and DELETE.

POST   /orders         # Create
GET    /orders/9       # Read
PATCH  /orders/9       # Update
DELETE /orders/9       # Delete

💡 Not every API operation fits neatly into CRUD — actions like "cancel" or "approve" often need their own endpoint.

PUT vs PATCH

PUT replaces the entire resource with the request body; PATCH applies a partial update to only the fields provided.

PUT   /users/42  { "name": "Alice", "email": "a@b.com" }  # full replace
PATCH /users/42  { "email": "new@b.com" }                  # partial update

💡 PUT is idempotent by definition; a naive PATCH implementation sometimes isn't — check the API docs.

nested resource

A resource scoped under a parent resource, expressed directly in the URI path.

GET /users/42/orders       # this user's orders
GET /users/42/orders/17    # one specific order

💡 Nest no more than 2-3 levels deep — beyond that, flatten with query params instead.

content negotiation

The client and server agreeing on a response format via the Accept and Content-Type headers.

Accept: application/json
Content-Type: application/json; charset=utf-8

💡 A 406 Not Acceptable means the server can't produce any format the Accept header asked for.

HATEOAS

Hypermedia As The Engine Of Application State — responses include links to related actions so clients discover the API instead of hardcoding URLs.

{ "id": 42, "status": "pending",
  "_links": { "cancel": "/orders/42/cancel", "self": "/orders/42" } }

💡 The most-cited-but-least-implemented part of the REST spec — most "RESTful" APIs skip it entirely.

request body / response body

The payload data sent with a request or returned with a response — distinct from headers (metadata) or the URL itself.

POST /users
Content-Type: application/json

{ "name": "Alice" }   ← this JSON is the request body

💡 GET requests conventionally have no body — put filters in query params instead.

error envelope

A consistent JSON shape every error response follows, so clients can parse failures the same way everywhere.

{ "error": { "code": "VALIDATION_FAILED",
             "message": "email is required",
             "field": "email" } }

💡 Inconsistent error shapes across endpoints are one of the most common API design complaints.

Pagination & querying

offset pagination

Paging results with a page number and page size, e.g. ?page=2&limit=20 — simple to implement but slow and unstable as data changes.

GET /orders?page=2&limit=20

💡 If a row is inserted while paging, "page 2" can shift — you'll see a duplicate or miss a row.

cursor pagination

Paging with an opaque cursor token that points at a specific record, rather than a page number — stable even as data changes.

GET /orders?after=eyJpZCI6NDJ9&limit=20

💡 Preferred for large or fast-changing datasets — GitHub and Stripe APIs both use cursor pagination.

filtering

Query parameters that narrow down a result set to matching records.

GET /orders?status=shipped&customer_id=42

💡 Keep filter syntax consistent across every endpoint in the same API — don't mix status=x here and filter[status]=x there.

sorting

Query parameters that control the order of returned results, often with a "-" prefix for descending.

GET /orders?sort=-created_at,total

💡 Always document (and validate) the allowed sort fields — sorting on an unindexed column can be a performance trap.

sparse fieldset

Letting a client request only the specific fields it needs, to reduce response payload size.

GET /users/42?fields=id,name,email

💡 A lightweight REST answer to the over-fetching problem GraphQL was built to solve more generally.

GraphQL

query (GraphQL)

A GraphQL operation that reads data — the client specifies exactly which fields it wants back, nothing more.

query {
  user(id: 42) { name email orders { id total } }
}

💡 One GraphQL query can replace several REST round-trips by fetching nested data in a single request.

mutation

A GraphQL operation that writes or changes data — the analogue of REST's POST, PUT, and DELETE.

mutation {
  updateUser(id: 42, email: "new@b.com") { id email }
}

💡 By convention, mutations are named as verbs (createX, updateX, deleteX), unlike queries.

subscription

A GraphQL operation that opens a long-lived connection (usually WebSocket) and pushes updates to the client as the underlying data changes.

subscription {
  orderStatusChanged(orderId: 42) { status updatedAt }
}

💡 The GraphQL equivalent of a webhook, but pushed directly to the connected client instead of a server endpoint.

resolver

The server-side function that fetches the actual data for one field in a GraphQL query.

const resolvers = {
  Query: { user: (_, { id }) => db.users.findById(id) },
  User:  { orders: (user) => db.orders.findByUserId(user.id) },
};

💡 Every field in the schema has (or inherits) a resolver — this is where the N+1 problem usually creeps in.

schema (GraphQL)

The strongly-typed contract that defines every type, field, and operation a GraphQL API supports.

type User {
  id: ID!
  name: String!
  orders: [Order!]!
}

💡 Unlike REST, the schema is queryable itself (introspection) — tools like GraphiQL read it to auto-generate docs.

fragment

A reusable, named selection of fields that can be shared across multiple GraphQL queries.

fragment UserCore on User { id name email }

query { user(id: 42) { ...UserCore } }

💡 Keeps large queries DRY — define a fragment once, reuse it everywhere that shape of data is needed.

over-fetching / under-fetching

The two REST problems GraphQL is designed to solve: over-fetching returns more fields than the client needs; under-fetching forces multiple requests to gather enough data.

# REST: GET /users/42 returns 40 fields when the UI needs 3 (over-fetching)
# REST: needs a 2nd call GET /users/42/orders to get order data (under-fetching)

💡 GraphQL trades this for a new problem: an unconstrained client query can still overload the server (see N+1 problem).

N+1 problem

A performance bug where resolving a list of N items triggers one extra database query per item instead of a single batched query.

# Naive: 1 query for orders, then N queries — one per order — for its user
# Fixed: batch-load all users in one query keyed by the order IDs (DataLoader)

💡 The single most common GraphQL performance bug — almost always fixed with a batching/caching layer like DataLoader.

gRPC & RPC

protocol buffers (protobuf)

Google's language-neutral binary serialization format — the schema file and compact wire format gRPC is built on.

message User {
  int32 id = 1;
  string name = 2;
}

💡 Smaller and faster to (de)serialize than JSON, but not human-readable on the wire — you need the .proto to decode it.

service definition

The .proto contract declaring a gRPC service's methods, and each method's request and response types.

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}

💡 Code generators turn this one file into client and server stubs for every supported language.

unary RPC

A standard one request, one response gRPC call — the gRPC equivalent of a normal REST request/response.

rpc GetUser (GetUserRequest) returns (User);

💡 The default and most common gRPC call shape — reach for streaming only when you actually need it.

streaming RPC

A gRPC call where the client, the server, or both send a continuous stream of messages instead of a single request/response.

rpc WatchOrders (WatchRequest) returns (stream OrderUpdate); // server streaming

💡 Comes in three flavours: server streaming, client streaming, and bidirectional streaming.

stub (RPC)

The generated client-side code that lets you call a remote gRPC method as if it were a local function call.

const client = new UserServiceClient(address);
const user = await client.getUser({ id: 42 });

💡 Same word as the testing "stub", but a different concept — here it means auto-generated client code, not a fake dependency.

IDL

Interface Definition Language — a language (like .proto for gRPC, or WSDL for SOAP) for describing an API's types and operations independent of any one programming language.

// user.proto is the IDL file; protoc generates Go, Python, Java clients from it

💡 OpenAPI serves a similar role for REST APIs, though it's usually described as a "spec" rather than an IDL.

Versioning & lifecycle

API versioning

Signalling which version of an API contract a client is using — commonly via the URI path, a custom header, or a query parameter.

GET /v2/users/42
GET /users/42  (Accept-Version: 2)

💡 URI versioning (/v2/) is the most visible and cache-friendly; header versioning keeps URLs stable but is easy for clients to forget.

breaking change

A change to an API's contract that could break existing clients — removing a field, renaming an endpoint, tightening validation on an existing parameter.

// Breaking: removing "phone" from the User response
// Non-breaking: adding a new optional "phone2" field

💡 Adding new optional fields is safe; removing or renaming anything a client might already depend on is not.

deprecation

Marking an API version, endpoint, or field as scheduled for removal, usually with a communicated sunset date.

Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Deprecation: true

💡 The Sunset and Deprecation HTTP response headers are the standard way to signal this machine-readably.

backward compatibility

The property that new versions of an API keep working correctly with clients written against an older version.

// v2 adds a required field with a sensible default so v1 clients don't break

💡 The core discipline behind "don't make breaking changes" — the goal most versioning strategies exist to protect.

semantic versioning (semver)

A MAJOR.MINOR.PATCH numbering scheme — bump MAJOR for breaking changes, MINOR for backward-compatible additions, PATCH for backward-compatible fixes.

v1.4.2 → v2.0.0   # a breaking change bumped the major version

💡 Widely used for package versions; APIs often borrow just the "major version = breaking change" convention without the full scheme.

Gateway, auth & ops

API gateway

A single entry point that routes, authenticates, rate-limits, and logs requests before they reach the actual backend services.

Client → API Gateway → { users-service, orders-service, payments-service }

💡 Centralizes cross-cutting concerns (auth, rate limiting, logging) so individual services don't each reimplement them.

rate limiting

Capping how many requests a client can make in a given time window, to protect the API from overload or abuse.

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1735689600

💡 A 429 Too Many Requests response usually comes with a Retry-After header telling the client when to try again.

API key

A simple, static credential a client includes with each request to identify itself and authorize access.

curl -H "X-API-Key: sk_live_abc123..." https://api.example.com/orders

💡 Simpler than OAuth but weaker — a leaked key grants full access until manually revoked, with no built-in expiry.

throttling

Deliberately slowing or delaying requests that exceed a rate limit, instead of rejecting them outright with an error.

# Instead of a hard 429, the gateway queues and delays excess requests

💡 Gentler than rate-limit rejection but can mask the actual load a client is generating.

OpenAPI spec (Swagger)

A machine-readable YAML/JSON document describing every endpoint, parameter, request/response shape of a REST API.

paths:
  /users/{id}:
    get:
      parameters: [{ name: id, in: path, required: true }]
      responses: { '200': { description: OK } }

💡 "Swagger" is the older brand name for the same spec format and its tooling (Swagger UI, Swagger Editor).

idempotency key

A client-generated unique value attached to a request so a retried POST is safely deduplicated by the server instead of processed twice.

POST /payments
Idempotency-Key: 7c3b1e9a-...

💡 Critical for payment APIs — a network timeout followed by a client retry must not charge the customer twice.

English phrases engineers use

  • "This is a breaking change — we need to bump the major version."
  • "Let's switch to cursor pagination, the offset approach is skipping rows under load."
  • "That N+1 problem is killing our GraphQL response time — we need a DataLoader."
  • "Don't forget the idempotency key on the retry, or we'll double-charge the customer."
  • "Is this field required by the OpenAPI spec, or just convention?"
  • "We're deprecating v1 — check the Sunset header for the exact date."