CloudOwl
HomeAbout
Services
How We WorkBlogContact
Schedule a callGet in touch
All posts
Engineering

February 24, 2026

14 min read

How to Design a REST API That Lasts

API design mistakes are expensive to fix once clients depend on them. Here are the patterns we use to get it right the first time.


A poorly designed API is one of the most expensive mistakes you can make in software. Once external clients or your own frontend depend on it, every change is a breaking change. Every inconsistency becomes permanent. Every shortcut you took in v1 haunts you for the life of the product.

We've built and consumed hundreds of APIs over the past 20+ years. Here are the patterns that hold up and the mistakes we keep cleaning up in inherited codebases.

URL structure and naming

Use nouns, not verbs. /users, not /getUsers. Use plural forms consistently. Nest resources logically: /users/123/orders. These seem like small choices, but they compound. A team that can guess the URL for an endpoint without checking the docs is a team that moves faster.

Keep nesting shallow - two levels deep at most. /users/123/orders/456 is fine. /users/123/orders/456/items/789/notes is a sign you should flatten the hierarchy. Deep nesting makes URLs fragile and hard to cache.

Version from day one

Put /v1/ in your URL path. You will need v2 eventually, and retrofitting versioning is painful. URL-based versioning is the simplest approach and the one your consumers will understand without reading documentation. Header-based versioning is technically cleaner but adds friction for anyone testing with curl or a browser.

HTTP methods and status codes

Use HTTP methods correctly: GET for reading, POST for creating, PUT/PATCH for updating, DELETE for deleting. Don't use POST for everything - it makes caching impossible and makes your API harder to reason about.

  • 200 OK for successful reads and updates
  • 201 Created for successful resource creation (return the created resource)
  • 204 No Content for successful deletes
  • 400 Bad Request for validation failures (include specific field errors)
  • 401 Unauthorized for missing or invalid authentication
  • 403 Forbidden for valid auth but insufficient permissions
  • 404 Not Found for resources that don't exist
  • 409 Conflict for duplicate entries or state conflicts
  • 429 Too Many Requests for rate limiting (include Retry-After header)
  • 500 Internal Server Error only for actual server errors, never for client mistakes

Error responses are a feature

Every error response should include: an HTTP status code that matches the problem, a machine-readable error code your client can switch on, a human-readable message, and enough context to debug without asking. Don't return 200 with an error message in the body. Don't return 500 for validation failures.

For validation errors, include per-field error messages so the client can show them inline. A response like { "errors": { "email": "must be a valid email", "name": "is required" } } saves a round trip compared to a generic "invalid input" message.

Pagination

  • Always paginate list endpoints, even if the list is short today. An unpaginated endpoint is a ticking time bomb
  • Use cursor-based pagination for large or frequently-changing datasets. Offset-based pagination breaks when records are inserted or deleted between pages
  • Include total count and next/previous links in the response metadata
  • Set sensible defaults (page size 20-50) and enforce maximums (100-200)

Filtering, sorting, and searching

Use query parameters for filtering: /orders?status=pending&created_after=2025-01-01. Use a sort parameter with a consistent format: /users?sort=created_at:desc. For full-text search, use a dedicated query parameter: /products?q=blue+widget. Keep filtering logic on the server - don't return all records and let the client filter.

Authentication and rate limiting

Use Bearer tokens in the Authorization header for API authentication. Include rate limit information in response headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) so clients can adapt their request patterns. Return 429 with a Retry-After header when limits are exceeded.

Document as you build

An undocumented API is a private API, no matter how many people use it. OpenAPI/Swagger specs generated from your code keep docs in sync with reality. Write them alongside the code, not after. Include example requests and responses for every endpoint - these are worth more than paragraphs of description.


Ben Arledge
Ben Arledge
CEO & CTO, CloudOwl

Have a project in mind?

No sales pitch, just an honest conversation about what you're building.

Start a conversationSee how we work

More from the blog

Leadership
Why Your Business Needs a Fractional CTO in 2026
7 min read
Technology
Next.js vs. Traditional CMS: Which Is Right for Your Business Website?
9 min read
Strategy
How to Scope a Software Project Without Getting Burned
8 min read
CloudOwlCode you own. Team that stays.
Company
AboutServicesHow We WorkBlogFAQContact
Industries
SaaS & Software ProductsE-Commerce & RetailFood & TravelEnergy, Construction & Field ServicesFinance & FintechGaming & EntertainmentEducation & eLearning
Technologies
AI & Machine LearningReact & Next.jsNode.jsReact NativeAWS
Cities we serve
EdmontonCalgaryRed DeerLethbridgeSaskatoonReginaWinnipegKelownaVancouverTorontoDetroitKansas CityNashvilleCharlotteIndianapolisMiamiPittsburghDes MoinesBoise

© 2026 CloudOwl. All rights reserved.
Schedule a callhello@cloudowl.com587-872-5683