
REST API Development Patterns and Pitfalls
Begin
14 pages · ~28 min
REST API Development Patterns and Pitfalls
Learn REST API development patterns, strengths, and common pitfalls to build robust, scalable APIs effectively. Ideal for developers seeking practical design guidance.
What you’ll learn
- 01Rest API Development Example: Patterns, Strengths, and PitfallsWelcome. If you're designing APIs that need to survive contact with the outside world, REST is still the default in 2026. It's not the flashiest choice, but it's the one every language and every client understands. In this session, we're going to move past the theory and look at the real patterns that hold up under production traffic. We'll cover resource modeling, HTTP semantics, and the kind of security that doesn't fall apart in a review. We'll also get into contract automation with OpenAPI, because that spec is no longer optional documentation—it's the source of truth for your code generation and your test suite. You'll learn how to shape resources that don't force a rewrite, use status codes to communicate clearly, and design pagination that scales beyond page one. But we're not just here for the wins. We'll flag the pitfalls: the 200 OK with an error body that breaks monitoring, the nested URLs that become a refactor trap, and the versioning decisions you'll regret on day four hundred. This is about building durable, predictable contracts. Let's get into it, starting with the foundational model that underpins all of these decisions.
moesif.comcadence.withremote.aienv.dev+22 min - 02REST Foundations and the Richardson Maturity ModelLet's ground ourselves in what REST actually is before we talk patterns. REST is an architectural style, not a protocol, defined by constraints like statelessness, cacheability, and a uniform interface. The Richardson Maturity Model gives us a practical yardstick, four levels spanning from simple RPC-style calls up to full hypermedia. Level one introduces resources, level two brings in proper HTTP verbs and status codes, and level three adds HATEOAS-style controls. Here's the reality you'll likely see: most production APIs intentionally stop at level two. They deem HATEOAS's dynamic discovery overhead not worth its complexity, choosing instead to publish an OpenAPI specification as their single source of truth for discoverability. That shift is deliberate. OpenAPI gives clients machine-readable contracts and SDK generation that link traversal struggles to match. Just remember the semantics matter. Calling any HTTP plus JSON service RESTful ignores the constraints that make the style genuinely useful. Getting the verbs, resource boundaries, and cache behavior right is what separates a level two API from one that merely travels over HTTP. With that foundation set, let's move on to resource modeling and URI design.
moesif.comcadence.withremote.aienv.dev+21 min - 03Resource Modeling and URI DesignNow let’s talk about the foundation: resource modeling and URI design. The core principle is simple: model domain nouns, not database tables. Your consumers think in terms of orders and invoices, not payment_attempts tables. Leaking storage internals into your URLs couples them to your schema and invites data leakage. Use plural collections with singular items; /orders for the set, /orders/{id} for one. This makes routing predictable and caching straightforward. Paths should be lowercase, kebab-case, and never carry trailing slashes or verbs. The HTTP method is the verb; the URL is the noun, so GET on /users, not /getUsers. Nest ownership up to two levels deep. /customers/{id}/orders is fine. Beyond that, flatten with query parameters, like /refunds?order={id}. Here’s the pitfall: once you go deeper, you build brittle coupling. And for non-regular actions, don’t scatter RPC verbs. Use a scoped action endpoint: POST /orders/{id}/cancel, giving the verb a clear home where it shows up in your spec for auditing. Next, let’s move to the verbs themselves in HTTP semantics in practice.
moesif.comcadence.withremote.aienv.dev+21 min - 04HTTP Semantics in PracticeNow let’s talk about HTTP semantics in practice. These are the rules that make your API predictable at the protocol level. GET, PUT, and DELETE are idempotent—repeating them yields the same server state, so retries are safe. POST is not idempotent. A retried POST can create duplicate resources, which is why you'll likely see modern APIs accept an idempotency key header on writes. Map your outcomes to precise status codes. Return 200 for a successful read, 201 with a Location header when a POST creates a resource, 204 for a successful DELETE, and 404 when a resource doesn't exist. One hard rule: never return 200 with an error body. That forces every client to parse the payload just to detect failure, and it breaks monitoring and retry logic that key off the status code. PATCH is for partial updates, but be careful—it is not guaranteed to be idempotent. A client retrying a patch may not get the same result if the request is based on stale data. Finally, content negotiation keeps resources stable across versions by letting clients specify the representation they can handle. That way, you evolve the schema without breaking existing consumers. Remember, the status code is your primary signal. Make it honest.
moesif.comcadence.withremote.aienv.dev+22 min - 05Consistent Errors with RFC 9457Let's talk about how you communicate errors to your clients. Your API's error responses are a contract, just like your resource definitions. RFC 9457 gives you a standard for that contract with the application/problem+json media type. The core fields are type, a URI that identifies the problem category, title, a stable short summary, status, mirroring the HTTP code, detail, a client-focused explanation of this specific occurrence, and instance, a URI pointing to this particular error event. Use absolute type URIs and ensure they resolve to documentation your consumers can read. That's a strength worth implementing from day one. Now for the pitfalls. Never leak stack traces or internal error codes; those belong in your logs. And do not return a single validation failure at a time. Collect every field-level issue and return them together. Use an extension with an array of error objects, each carrying a JSON pointer to the offending field, a human-readable message, and a machine-readable code. Your clients will be able to highlight all invalid form fields in one pass without parsing prose from the detail field. Diagram: clients should display all validation issues simultaneously, not sequentially. Keep detail focused on what the client can correct, not on debugging your implementation. Structuring errors this way turns a chaotic guessing game into a predictable integration. Next up, we’ll cover how to handle pagination, filtering, and sorting so your collection endpoints scale gracefully.
rfc-editor.orgrfc-editor.orgdatatracker.ietf.org+22 min - 06Pagination, Filtering, and SortingLet’s talk about list endpoints, because this is where performance and correctness often go to die. The decision starts with pagination. Cursor-based pagination uses an indexed seek, giving you logarithmic performance and stability even when records are inserted mid-scroll. Offset pagination, by contrast, degrades to linear scans and causes duplicate or skipped records as the dataset shifts under active writes. You’ll likely see that failure in production before you see it in staging. For deterministic ordering, sort by a composite key—created_at paired with id—so ties never break unpredictably. Encode that pair into an opaque cursor; never expose raw database identifiers. Now, a caution here. If you keep offset pagination for admin tables under ten thousand rows, that’s defensible. Just don’t ship it on public, high-volume endpoints. Enforce a default limit, and cap the maximum—twenty-five default, one hundred max is a sane baseline. Unbounded limits turn one careless client into an accidental denial-of-service. Finally, standardize your filter and sort conventions. A leading minus for descending sort, and consistent parameter names like limit and after. Predictable query semantics mean cachable, repeatable results. Nail these patterns now, because retrofitting cursor pagination later is a breaking change. Next, we’ll look at versioning, compatibility, and deprecation.
moesif.comcadence.withremote.aienv.dev+21 min - 07Versioning, Compatibility, and DeprecationNow let's talk about how you evolve an API without breaking the clients who depend on it. URI path versioning, starting with v1, is the pragmatic default. It's visible, cacheable, and every engineer can debug a request from a single curl line. Date-based header versioning works at Stripe and GitHub scale, but be honest about the cost. It requires transformation layers that translate new responses back to old wire shapes, and that's real engineering you'll carry for years. So what warrants a version bump? Additive-only changes are safe. Adding a new optional field? Fine. Anything else is breaking. Renames, removed fields, tightened validation, changed enum values—all of it forces a new version. When you do deprecate, don't rely on blog posts. Send the Deprecation and Sunset headers on every response, and give clients a minimum twelve-month runway. That's the industry baseline at Shopify, Twilio, and Slack. Finally, mark deprecated operations in your OpenAPI spec with the deprecated flag, so code generators automatically warn your clients before sunset day arrives. Get this right, and you ship breaking changes without the three AM support calls. Next, we'll cover authentication, authorization, and token hygiene.
moesif.comcadence.withremote.aienv.dev+22 min - 08Authentication, Authorization, and Token HygieneLet's talk about authentication, authorization, and token hygiene. OAuth 2.1 is now the baseline. It consolidates years of security guidance into one framework. You'll likely see PKCE with S256 hashing enforced across all clients, not just public ones. Use exact redirect URI matching, and keep access tokens short-lived. Five to fifteen minutes is the common window. The implicit and password grants are gone. Authorization Code with PKCE is the single flow for every client type. That removes entire classes of token leakage. Scopes enforce least privilege, but checking a scope is not enough. You must verify object-level authorization on every request. This is where broken object level authorization, or BOLA, slips in. A valid token with broad scopes can access another user's resource if you only check the token. Confirm ownership against the resource itself. For token transport, consider sender-constrained tokens. mTLS works well for server-to-server, DPoP fits SPAs and mobile. Both reduce replay risk if a token is stolen. And rotate refresh tokens on every use, invalidating the previous one. This limits the damage from a leaked refresh token. Get these fundamentals right, and your API surface holds up far better under attack. Next, we'll cover rate limiting and traffic control.
moesif.comcadence.withremote.aienv.dev+21 min - 09Rate Limiting and Traffic ControlRate limiting is where good API design protects both your service and your consumers. Enforce limits at the gateway with separate policies per tenant and per endpoint. This centralizes enforcement and keeps your backend logic clean. When you return a 429, always include a Retry-After header so clients know exactly when to retry. You'll likely see the X-RateLimit-Limit, Remaining, and Reset headers on major APIs, and exposing them lets proactive clients self-throttle before they trip the limit. On the client side, exponential backoff with jitter is non-negotiable. Without jitter, clients that hit the limit together retry together, and you've created a synchronized retry storm that can take down the very endpoint you're protecting. Conditional requests are your low-cost escape hatch. If a client sends If-None-Match and the resource hasn't changed, you return a 304 Not Modified. That response typically doesn't consume rate-limit quota, so clients stay within budget while keeping their data fresh. The pattern pays off on high-frequency polling endpoints. Takeaway: rate limiting isn't just about rejecting excess traffic; it's about giving clients the information and mechanisms to avoid hitting the wall in the first place. Next, we'll look at caching, performance, and observability.
moesif.comcadence.withremote.aienv.dev+21 min - 10Caching, Performance, and ObservabilityLet's talk about the operational side of your REST API—caching, performance, and observability. Your API's performance isn't just about server speed; it's about how you manage load. Use the Cache-Control header to define caching policies, and lean on ETags and Last-Modified validators. A conditional GET with If-None-Match can return a 304 Not Modified, sparing the server and the network a full payload. That's a win you'll feel in your latency numbers. Now, about measuring those numbers. Track latency at the p50, p95, and p99 percentiles. Averages hide tail behavior, and tails are where user experience goes to die. If your p99 is two seconds while your average is two hundred milliseconds, you have a problem—and an average won't tell you. On metrics, start with the four golden signals: latency, traffic, errors, and saturation. For logs and traces, OpenTelemetry is the answer. It unifies metrics, logs, and traces under one vendor-neutral SDK. And crucially, propagate a correlation ID with every request. That one identifier links a log line to a trace span, letting you see the entire journey of a single request across services. That's how you debug a production incident in minutes, not hours. Invest in these patterns early, or you'll be retrofitting them during your first major outage. Next up, let's look at OpenAPI and contract-first development.
moesif.comcadence.withremote.aienv.dev+22 min - 11OpenAPI and Contract-First DevelopmentNow let's talk about where your contract actually lives. OpenAPI 3.1 is the de facto standard for dictating that contract, and the spec must be the source of truth, not an afterthought written once the code is already shipped. You'll likely see the drift problem immediately if you try to generate documentation from code. The spec should come first. Lint it with Spectral to enforce structure, and validate that your examples actually conform to their schemas, because a broken example is the cheapest contract failure you'll ever catch. Then, the critical layer: run contract tests before you implement the endpoint. Tools like Dredd replay your documented examples against the server, while Schemathesis fuzzes the spec with property-based testing to catch edge cases your examples never cover. Both prove the running service honors its promises. One thing to flag here: if you treat the spec as documentation rather than an executable contract, you will discover breaking changes when a consumer reports them in production, not when your CI pipeline rejects them. Finally, think about AI agents consuming your API. They read your spec literally. Treat operationId, summary, and description as user-facing copy, and use intent-revealing field names. Vague descriptions cause wrong tool selection. Your spec is now the interface for both human developers and autonomous agents. Coming up, we'll look at how contract testing and change detection keep that spec honest over time.
moesif.comcadence.withremote.aienv.dev+22 min - 12Contract Testing and Change DetectionLet’s talk about contract testing and change detection. These tools verify that your live responses match your OpenAPI spec, checking status codes, types, schemas, and headers. Schemathesis fuzzes your API with property-based tests, generating hundreds of edge cases per endpoint. Dredd replays your documented examples deterministically. For static checks, openapi-examples-validator confirms your spec's examples conform to their own schemas in milliseconds. In CI, oasdiff compares your spec against the main branch and flags breaking changes, like removing a required field or narrowing an enum. The key distinction: Pact suits internal services where you know your consumers and their expectations. OpenAPI verification covers public APIs with unknown consumers. You'll likely see a robust pipeline use both, layering fast static checks ahead of live contract tests. All of this is about one promise: documentation and behavior never diverge. And these checks should gate every pull request, so a breaking change requires explicit approval. Now let's look at how to turn that spec into a great developer experience.
github.commock-server.comqaskills.sh+22 min - 13Documentation, SDKs, and Developer ExperienceNow let’s talk about documentation, SDKs, and developer experience — the layer that determines whether consumers actually adopt your API. The single highest-leverage move is to make your OpenAPI spec the source of truth. Generate your docs, your SDKs, and your mock servers from it. When the spec is the contract, those artifacts stay in sync automatically. For most APIs, generated SDKs are the right call. Hand-written ones only pay off for high-volume or highly specialized surfaces. For everything else, a generated SDK beats no SDK by a wide margin. Surface request IDs in every response — that header is what turns a support ticket into a log lookup. And document retry behavior explicitly. Tell clients which errors are retryable — typically 429 and 5xx — and which are not. Don’t forget the sandbox. If developers can’t try your API without touching production data, they’ll move on to something that lets them. Here’s the 2026 twist: treat your spec’s operation IDs, summaries, and descriptions as user-facing copy. AI agents read those fields literally when they decide which tool to call. Vague descriptions cause wrong endpoint selection. So write them as if a very literal engineer is reading them. That same clarity helps your human developers too. Keep the spec sharp, keep artifacts generated, and your developer experience will compound. Next, let’s look at the pitfalls — the trade-offs and the cases where REST isn’t the right fit.
moesif.comcadence.withremote.aienv.dev+22 min - 14Pitfalls, Trade-offs, and When REST Is Not the Right FitLet’s close with the hard truths. First, the anti-patterns. Returning two hundred for an error forces your clients to parse the body to find failure. Mixed casing across fields is a permanent paper cut. And deep nesting is a refactor waiting to happen, so flatten past two levels. When REST is right, it earns its keep with broad client support, clean cacheability, and stable contracts that survive any single consumer. But be honest about the costs. HATEOAS adds real complexity, and most teams skip it in favor of an OpenAPI spec. Cursor pagination scales beautifully, but you lose the ability to jump to page eight. Now the key decision. If your traffic is service to service inside a mesh, reach for gRPC. If clients need flexible, client-driven queries, GraphQL. For one-way real-time pushes, Server-Sent Events. REST remains the safe default for public, multi-client, long-lived APIs. Match the tool to the problem, and you will make the right call.
moesif.comcadence.withremote.aienv.dev+22 min
Take the deck with you
Download this course as a file — free, no sign-up needed.
- PDF handoutEvery slide page, ready to print or share.15 pages · 3.8 MBDownload
- Narrated PowerPointThe deck that presents itself — every slide carries the digital human's narration video.15 pages · 16.3 MBDownload
- PowerPoint slidesThe full deck as a .pptx — open it in PowerPoint, Keynote, or Google Slides.15 pages · 3.7 MBDownload
Free to use in your own training — please keep the PersonWise credit page at the end.
Have your own deck? Turn it into a course
Sources consulted
Web sources consulted while building this course.
- 12 REST API Best Practices That Hold Up in 2026 — moesif.com
- How to design RESTful API endpoints in 2026 | Cadence blog — cadence.withremote.ai
- REST API Best Practices: Design Guide — env.dev — env.dev
- Web API Design Best Practices - Azure Architecture Center — learn.microsoft.com
- REST API Design in 2026: A Full Engineering Reference — digitalapplied.com
- RFC 9457: Problem Details for HTTP APIs — rfc-editor.org
- RFC 9457: Problem Details for HTTP APIs — rfc-editor.org
- RFC 9457 - Problem Details for HTTP APIs — datatracker.ietf.org
- RFC 9457: Problem Details for HTTP APIs — rfc-editor.org
- Problem Details (RFC 9457): Doing API Errors Well - Swagger — swagger.io
- studio-design/openapi-contract-testing — github.com
- OpenAPI Contract Verification with AI | MockServer — mock-server.com
- OpenAPI 3.1 Contract Testing Guide: Spec-Driven QA in ... — qaskills.sh
- studio-design/gesso — github.com
- API contract testing from OpenAPI using Arazzo — redocly.com