An API can pass every unit test and still make life difficult for everyone who uses it. The friction appears in small places: an unexpected field name, an ambiguous error, a retry that creates a duplicate order, or a release that quietly breaks a customer’s integration.
Good API design reduces that friction. It gives developers a consistent way to reason about your system—and gives your team a contract it can maintain as the business grows.
These eight principles provide a practical foundation for resource-oriented HTTP APIs. We’ll use a fictional commerce API throughout, so you can see how the decisions fit together.
1. Give resources stable identities
Start with the business objects your consumers recognize: customers, orders, products, and shipments. Give those objects clear addresses, then use HTTP methods to express the operation.
For example, an order can live at /orders/ord_42. Reading, replacing, and deleting that order need not introduce three unrelated route names. Collections such as /orders provide a natural place to list existing orders and submit new ones. This resource-oriented approach is also recommended in Microsoft’s web API design guidance.

An order resource has one address, with GET, PUT, and DELETE expressing different operations.
Why it matters: Stable identities make an API easier to navigate and discuss. Your documentation, support conversations, and client code can all refer to the same business concept.
Resources need not mirror database tables. A checkout or refund may represent a business workflow spanning several tables and services. Model what the consumer needs to accomplish; avoid exposing storage details merely because they are convenient for the implementation.
2. Establish a naming pattern people can learn once
A useful convention is a plural collection followed by a member identifier: /customers, then /customers/cus_17. Apply the same pattern to orders and products. Pick one style for multiword paths—such as /shipping-addresses—and document it.

Three collections follow the same collection-to-member URL pattern.
Why it matters: Every exception creates another fact a consumer must remember. A shared naming pattern lets developers apply what they learned from one endpoint to the next.
Use the same word for the same concept. If “customer” and “user” describe different things in your domain, explain the distinction. If they describe the same thing, avoid switching between them across endpoints. Consistency should make the business model clearer.
3. Choose methods with retries in mind
HTTP methods carry behavioral expectations. GET retrieves a representation; POST submits data for processing, often creating a resource; PUT creates or replaces the target’s state; and DELETE removes its association with the current functionality. GET, PUT, and DELETE are idempotent: repeating an identical request has the same intended server effect as making it once. Responses can still differ. See HTTP method semantics.
PATCH applies a partial modification. It is not inherently idempotent: setting a quantity to five differs from incrementing it by five. See the PATCH specification.

Why it matters: A timeout leaves a client uncertain. Did the server reject the request, or did it finish the work and lose the response? Your method choices and retry contract determine whether recovery is safe.
For operations such as order submission, consider an application-level idempotency key. Define how the server recognizes duplicates, handles simultaneous requests, and rejects reuse of a key with different data. The key only helps when the server actually enforces those rules.
4. Let the status code communicate the outcome
If an order lookup fails, returning 200 with an error hidden in the body makes the result misleading. Give clients a meaningful status before asking them to inspect the details.
| Status | Intended signal |
|---|---|
200 | Success |
201 | Resource created |
202 | Accepted; processing is incomplete |
204 | Success without response content |
400 | Invalid request |
401 | Missing or invalid authentication credentials |
403 | Request understood, but refused |
404 | Resource unavailable or its existence undisclosed |
409 | Conflict with current resource state |
422 | Content understood but instructions cannot be processed |
429 | Request rate exceeded |
These meanings come from HTTP status semantics and the rate-limiting specification. A 429 response may include Retry-After to guide the client’s next attempt.

Why it matters: Clear outcomes make client control flow easier to implement and failures easier to observe.
Distinguish an empty search from a missing individual resource. For this API, a successful order search with no matches returns 200 and an empty collection. Also give asynchronous operations a documented way to check progress; acceptance alone does not tell the caller when work finishes.
5. Make error details a dependable interface
Consumers need more than a failure category. They need to know which input was rejected and how to recover. Adopt one structured error format across the API.
RFC 9457 Problem Details supplies a standard foundation with type, title, status, detail, and instance. You can add documented extensions for application codes and field errors. For example, a validation failure could return:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json{
"type": "https://api.example.com/problems/invalid-quantity",
"title": "Invalid order quantity",
"status": 422,
"detail": "Correct the quantity before submitting the order.",
"instance": "/problems/occurrences/req_7f2",
"code": "INVALID_QUANTITY",
"errors": [
{
"field": "items[0].quantity",
"message": "Quantity must be at least 1."
}
]
}Here, code and errors are application-defined extensions. The example domain is illustrative.

Why it matters: A single error handler can serve many endpoints. Applications can branch on a stable identifier while people receive an explanation they can act on.
Keep internal exceptions, credentials, and stack traces out of public responses. Human-readable wording may improve over time; clients should not depend on matching its exact text.
6. Separate resource identity from selection criteria
A path identifies what you are addressing. Query parameters are a useful convention for filters, sorting, search, and pagination. Keep these responsibilities recognizable:
GET /products?category=books&inStock=true&sort=price&limit=20
Why it matters: Optional criteria can combine without multiplying routes. A consumer can narrow a collection while continuing to use the same endpoint.
Define the supported filter fields, sort directions, defaults, and page-size limits. Decide how unsupported criteria are rejected. These details deserve the same attention as the route name; Microsoft’s filtering and pagination guidance provides useful background.
Never hide a destructive operation inside a read request such as GET /products?id=prod_9&action=delete. A read must remain safe to invoke. Put resource data in an appropriate request body and use the method that matches the operation.
7. Plan change from the consumer’s perspective
Once another application relies on a field, that field becomes part of a shared agreement. A seemingly tidy refactor can become an integration outage.
Imagine a client reading totalCents: 4999. Replacing that number with an object containing amount and currency breaks the client’s assumptions. A migration could introduce the richer representation alongside the old field, or offer it through a new major version.

Why it matters: Your release schedule rarely matches every consumer’s release schedule. A migration path lets both sides make progress.
Classify changes before shipping. Removing fields, changing types, or altering established behavior can break consumers. Additive changes are often compatible, but still need care: strict parsers and clients with exhaustive enum handling may fail. Google’s backward-compatibility guidance explains why compatibility includes behavior as well as schema.
Choose one versioning mechanism—such as /v1/orders and /v2/orders—and publish migration instructions. Communicate retirement dates, check adoption, and verify representative consumer workflows before switching off an older contract. A new version number does not complete the migration.
8. Standardize the entire request and response experience
Naming discipline should extend inside payloads. Pick a property style, such as camelCase, and apply it across request bodies, responses, and examples. Establish explicit rules for missing values, empty lists, money, and identifiers.
Use an unambiguous timestamp convention. For example, 2026-10-11T14:30:00Z follows RFC 3339; the Z indicates UTC. Avoid timestamps that leave the timezone to interpretation.

Why it matters: Shared conventions let teams reuse parsing, validation, pagination, and support tooling. Endpoint-specific exceptions make those common components harder to build.
For our example API, a collection could consistently use this envelope:
{
"items": [],
"nextCursor": null
}Document how the cursor is supplied on the next request and what ends pagination. Keep filtering and ordering rules consistent where resources support the same capabilities. Choose JSON for ordinary structured payloads if that suits your consumers, and document any justified alternatives.
Turn the principles into a working team agreement
These conventions are a starting point for a usable API, not a complete test of REST conformance or production readiness. Security, authorization, performance, and operational reliability still need their own design work.
Before building the next endpoint, agree on a compact contract: resource vocabulary, route patterns, method behavior, outcomes, error details, query rules, compatibility policy, and payload conventions. Record the contract in an API specification and review examples with the people who will consume it.
Then test the uncomfortable cases: an empty result, invalid input, a lost response followed by a retry, and a client running an older version. Those are the moments when design decisions become visible to customers.
Build better APIs with The Training Boss
The Training Boss helps organizations of every size turn API design principles into systems their teams can confidently build and maintain. Whether you are a startup defining your first public API, a growing business connecting partners, or an enterprise managing a large integration portfolio, our experts are ready to engage.
We can review and audit your API architecture, identify inconsistencies and integration risks, and help prioritize practical improvements. We also train development teams to design well-structured systems, make sound architectural decisions, and apply shared standards in their everyday work.
Bring us your existing APIs, your next architecture proposal, or the integration problems slowing your team down. Contact The Training Boss to discuss an API architecture review and training tailored to your organization.


