# REST API Best Practices: A Developer's Guide to Building Reliable APIs

### Quick reference

- **Use nouns, not verbs.** URLs represent resources, and HTTP methods define what happens.
- **Use correct HTTP methods.** GET, POST, PUT, PATCH, and DELETE each have clear semantics.
- **Return meaningful status codes.** Clients should understand outcomes without parsing bodies.
- **Version your API.** Plan versioning from the start to manage changes without breaking clients.
- **Secure with HTTPS and authentication.** Never transmit sensitive data over unencrypted connections.
- **Paginate large collections.** Break large result sets into manageable chunks for better performance.
- **Standardize errors.** Consistent error shapes save debugging time.
- **Design for idempotency.** Retries should not create duplicates.
- **Observe everything.** Logs, metrics, and request IDs make issues diagnosable.
- **Keep documentation executable.** Examples should match reality.
 
[ Try Postman today →](<https://identity.getpostman.com/signup >)

 

A well-designed REST API is intuitive, secure, scalable, and easy to maintain. In production, reliability starts with consistency. Whether you're designing your first REST API or refining an existing one, following proven best practices helps teams avoid breaking clients, reduce support overhead, and make APIs easier to adopt.

This guide covers essential REST API design principles, along with practical examples that you can apply immediately.

## Use consistent and meaningful resource naming

Resource naming sets expectations for how your API behaves. Clear, consistent names make endpoints easier to understand and less likely to be misused.

### Use nouns, not verbs

REST endpoints should represent resources (things) rather than actions. HTTP methods already describe the action, so your URLs should focus on what you're working with.

**Good practice**

 ```
GET /users
POST /orders
DELETE /products/123
```

 **Bad practice** ```
GET /getUsers
POST /createOrder
DELETE /deleteProduct/123
```

### Keep URLs predictable

 Use plural nouns for collections and follow consistent patterns throughout your API. Developers should be able to predict endpoint names based on patterns they've already seen. ```
GET /users → List all users
GET /users/123 → Get specific user
POST /users → Create new user
PUT /users/123 → Update user
DELETE /users/123 → Delete user
```

 For nested resources, reflect relationships in the URL structure, but avoid going too deep. ```
GET /users/123/orders → Get orders for a specific user
GET /orders/456/items → Get items in a specific order>/code>
```

 **In production:** More than two levels of nesting often signals a resource modeling problem. Consider flattening or introducing a dedicated endpoint. ## Use proper HTTP methods

HTTP methods define the action being performed. Using them correctly makes your API predictable and allows clients to understand what each request will do.

### GET retrieves data

Use GET for read-only operations that don't modify server state. GET requests should be safe to call repeatedly without side effects.

 ```
GET /products?category=electronics&limit=20
```

 GET requests are cacheable and can include query parameters for filtering, sorting, and pagination. Never use GET for operations that change data. ### POST creates new resources

Use POST when creating new resources or triggering actions that change server state. The server typically assigns the new resource's identifier.

 ```
POST /users
Content-Type: application/json
{
"name": "Phil Ostman",
"email": "p.ostman@example.com"
}

```

 Return 201 Created and include a `Location` header pointing to the new resource. ### PUT updates or replaces resources

Use PUT for full updates of existing resources. PUT is idempotent, meaning multiple identical requests produce the same result.

 ```
PUT /users/123
Content-Type: application/json
{
"name": "Phil Ostman",
"email": "new-email@example.com",
"role": "Developer"
}

```

### PATCH makes partial updates

 Use PATCH when you only need to modify specific fields without sending the entire resource. ```
PATCH /users/123
Content-Type: application/json
{
"email": "updated@example.com"
}

```

PATCH is particularly useful for large resources where sending the complete object would be inefficient.

### DELETE removes resources

Use DELETE to remove resources from the server. Like PUT, DELETE should be idempotent.

 ```
DELETE /users/123
```

 After a successful deletion, return 204 No Content or 200 OK with details about what was deleted. ## Implement proper HTTP status codes

HTTP status codes communicate the result of each request. Using them correctly helps clients handle responses appropriately without parsing response bodies.

### Success codes (2xx)

- **200 OK**: Request succeeded, response contains data
- **201 Created**: New resource created successfully
- **204 No Content**: Request succeeded, no response body
- **202 Accepted**: Request accepted for processing but not completed
 
### Client error codes (4xx)

- **400 Bad Request**: Invalid request format or parameters
- **401 Unauthorized**: Authentication required or failed
- **403 Forbidden**: Authenticated but lacks permissions
- **404 Not Found**: Resource doesn't exist
- **409 Conflict**: Request conflicts with the current state
- **422 Unprocessable Entity**: Valid format, but semantic errors
 
### Server error codes (5xx)

- **500 Internal Server Error**: Generic server failure
- **502 Bad Gateway**: Invalid response from upstream server
- **503 Service Unavailable**: Server temporarily unavailable
- **504 Gateway Timeout**: Upstream server didn't respond
 
**In production:** Inconsistent status codes are one of the most common causes of client-side bugs.

## Provide meaningful, consistent error responses

Status codes tell clients what went wrong, but error response bodies explain why and how to fix it. Good error responses include enough detail for developers to troubleshoot issues without exposing sensitive information.

### Standardize error structure

Use a standard format for all error responses across your API.

 ```
{
"error": {
"code": "INVALID_EMAIL",
"message": "The email address format is invalid",
"details": "Email must contain an @ symbol and valid domain",
"field": "email"
}
}

```

 Use stable, machine-readable error codes along with human-readable messages. ### Return all validation errors at once

 When validation fails, tell users exactly what's wrong and how to fix it. ```
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Email address is required"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
}
}

```

This saves clients from slow, iterative fixes.

## Version your API deliberately

APIs evolve over time, and versioning allows you to make improvements without breaking existing clients. Plan your versioning strategy from the start, even if you only have one version initially.

### URI versioning

Include the version number in the URL path. This approach is explicit and easy to understand.

 ```
GET /v1/users
GET /v2/users
```

 URI versioning makes it immediately clear which version a client is using and allows different versions to coexist on the same server. ### Header versioning

 Pass the version in a custom header. This keeps URLs clean but requires clients to set headers correctly. ```
GET /users
Accept: application/vnd.api.v1+json
```

 Header versioning works well when you want cleaner URLs and have clients sophisticated enough to manage headers. ### When to create a new version

Introduce a new version for breaking changes: removed fields, type changes, or auth changes. Non-breaking additions usually don’t require a new version.

## Paginate large collections

Returning large datasets in a single response can create performance problems and a negative user experience. Pagination breaks large result sets into manageable chunks.

### Page number pagination

Use page numbers for a more traditional pagination experience.

 ```
GET /products?page=3&per_page=20
```

 Include pagination metadata in responses to help clients navigate result sets. ```
{
"data": [...],
"pagination": {
"current_page": 3,
"total_pages": 15,
"total_items": 287,
"per_page": 20
}
}

```

### Limit and offset pagination

 Allow clients to specify how many results they want and where to start. ```
GET /products?limit=20&offset=40
```

 This approach is straightforward but can miss items if the collection changes between requests. ### Cursor-based pagination

 Use cursor tokens that point to specific positions in result sets. ```
GET /products?limit=20&cursor=eyJpZCI6MTIzfQ
```

 Return a next cursor in the response that clients can use to fetch the next page. ```
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
}
}

```

**In production:** Cursor-based pagination is more reliable for feeds and high-write datasets.

## Support filtering, sorting, and searching

Beyond pagination, give clients control over what data they receive and how it's organized.

### Filtering

Allow clients to filter results using query parameters.

 ```
GET /products?category=electronics&price_min=100&price_max=500
```

 Use clear parameter names that make the filtering criteria obvious. ### Sorting

 Enable sorting with parameters that specify the field and direction. ```
GET /products?sort=price:asc
GET /users?sort=created_at:desc
```

 Support multiple sort criteria when appropriate. ```
GET /products?sort=category:asc,price:desc
```

### Searching

 Provide search functionality for text-based queries. ```
GET /products?search=wireless keyboard
GET /articles?q=api+design
```

Consider implementing more sophisticated search features like fuzzy matching or field-specific searches for complex use cases.

## Secure your API with proper authentication

Security is non-negotiable for APIs. Choose authentication methods appropriate for your use case and implement them consistently.

### API keys

Simple authentication using unique keys passed in headers.

 ```
GET /api/users
X-API-Key: sk_live_abc123xyz789
```

 API keys are well-suited for server-to-server communication, but they shouldn't be the only security layer for sensitive operations. ### Bearer tokens (OAuth 2.0)

 Use OAuth 2.0 for applications requiring user-specific authentication and authorization. ```
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

 Bearer tokens support time-limited access and can include scope restrictions to limit the actions that authenticated clients can perform. ### Always use HTTPS

 Encrypt all API traffic with HTTPS to protect authentication credentials and sensitive data in transit. HTTP transmits data in plain text, exposing it to interception. ### Rate limiting

 Implement rate limiting to prevent abuse and ensure fair resource allocation. Return appropriate headers to inform clients about their usage. ```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1640995200
```

When rate limits are exceeded, return a 429 Too Many Requests status with information about when the client can retry.

## Provide comprehensive documentation

Documentation transforms your API from a black box into a tool that developers can use with confidence. Good documentation includes examples, explains edge cases, and stays current with API changes.

### Document all endpoints

For each endpoint, document the HTTP method, URL, required parameters, request body format, response format, status codes, and authentication requirements.

### Include working examples

Show actual requests and responses that developers can copy and test.

 ```
Request:
POST /api/users
Content-Type: application/json
Authorization: Bearer token123

{
"name": "Phil Ostman",
"email": "p.ostman@example.com"
}

Response:
HTTP/1.1 201 Created
Location: /api/users/12345

{
"id": "12345",
"name": "Phil Ostman",
"email": "p.ostman@example.com",
"created_at": "2024-01-15T10:30:00Z"
}

```

### Explain error cases

 Document common error scenarios and how clients should handle them. Include examples of error responses and their meanings. Tools like Postman make it easy to create interactive documentation that developers can use to explore your API without needing to write code. ## Design for idempotency

 Idempotent operations produce the same result regardless of the number of times they're executed. Network issues and retries are inevitable, and idempotency prevents duplicate actions. Safe methods (GET, HEAD) are naturally idempotent because they don't modify state. PUT and DELETE should also be idempotent by design. ```
"PUT /users/123 → Updates user 123, same result if called multiple times
DELETE /users/123 → Deletes user 123, subsequent calls return 404
```

 POST is typically not idempotent because each request might create a new resource. To make POST operations safer, implement idempotency keys. ```
POST /payments
Idempotency-Key: unique-key-abc123
Content-Type: application/json { "amount": 100.00, "currency": "USD" } >
```

 The server stores the idempotency key and returns the same response if the request is repeated with the same key, preventing duplicate payments. ## Handle CORS properly

 Cross-Origin Resource Sharing (CORS) allows web applications from different domains to access your API. Configure CORS headers to specify which origins can access your API. ```
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
```

 For public APIs, you might use a wildcard to allow all origins, but this reduces security. For private APIs, explicitly list allowed origins. ## Use appropriate content types

 Specify content types clearly so clients know how to parse responses and servers know how to interpret requests. ### JSON is the standard

 JSON has become the standard for REST APIs. Use application/json as your primary content type. ```
Content-Type: application/json
Accept: application/json
```

### Support content negotiation

 Allow clients to request different formats when appropriate. ```
GET /api/report
Accept: application/pdf
```

 The server can return different representations based on the Accept header, though JSON should remain your primary format. ## Keep responses focused and efficient

 Design response bodies to include what clients need without excess data. Large responses slow down transfers and force clients to parse unnecessary information. ### Use field selection

 Allow clients to specify which fields to include. ```
GET /users/123?fields=name,email
```

 This reduces payload size and improves performance, particularly for mobile clients or those with slow connections. Avoid deeply nested structures Keep response structures relatively flat. Deep nesting makes responses harder to parse and navigate. **Good practice** ```
{
"user": {
"id": "123",
"name": "Phil Ostman"
},
"organization_id": "org-456",
"organization_name": "Tech Corp"
}

```

 **Bad practice** ```
{
"user": {
"id": "123",
"name": "Phil Ostman",
"organization": {
"id": "org-456",
"name": "Tech Corp",
"details": {
"industry": "Technology",
"settings": {
"timezone": "UTC"
        }
      }
    }
   }
}

```

**In production:** Deep nesting often leaks internal data models into public APIs.

## Observe, log, and monitor API usage

Reliable APIs are observable APIs. Comprehensive logging helps you understand how your API is being used, diagnose problems, and identify performance bottlenecks:

- Log request method, endpoint, status code, and latency
- Track error rates and slow endpoints
- Alert on anomalies
 
### Use request IDs

Include unique identifiers in responses that clients can reference when reporting issues.

 ```
X-Request-ID: f47ac10b-58cc-4372-a567-0e02b2c3d479
```

 This makes it easy to correlate client reports with server logs and trace requests through your systems. ## Plan for backward compatibility

 Once clients depend on your API, changes become risky. Design with future evolution in mind. ### Add, don't remove

 When possible, add new fields or endpoints rather than removing or changing existing ones. Clients that ignore new fields won't break, but removing fields will. ### Deprecation process

 When you must remove or change functionality, follow a clear deprecation process: - Announce changes early
- Provide migration guidance
- Support old and new versions in parallel
 
 Include deprecation warnings in responses to notify clients they're using outdated endpoints. ```
Deprecated: true
X-API-Warn: This endpoint is deprecated and will be removed on 2025-06-01. Use /v2/users instead.

```

## Test APIs continuously

 Rigorous testing catches issues before they affect clients. Test APIs across multiple dimensions to ensure reliability, beyond the happy paths: - Authentication failures
- Validation errors
- Rate limiting
- Edge cases and retries
 
 Unit tests verify that individual components work correctly. Integration tests confirm components work together properly. End-to-end tests validate complete workflows from the client's perspective. ## Common mistakes to avoid

Understanding pitfalls helps you avoid them in your designs.

### Using GET for state changes

Never use GET for operations that modify data. GET requests should be safe and idempotent.

**Bad practice**

 ```
GET /users/123/delete
GET /cart/add?product_id=789
```

 **Good practice** ```
DELETE /users/123
POST /cart/items
```

### Exposing implementation details

 Keep internal system details out of your API design. URLs and field names should reflect domain concepts, not database tables or internal architecture. **Bad practice** ```
GET /user_table_v2/query?db_id=123
```

 **Good practice** ```
GET /users/123
```

### Inconsistent naming and conventions

 Use consistent patterns throughout your API. If you use snake\_case for field names in one endpoint, use it everywhere. If you pluralize collection names, do it consistently. ## Final thoughts

 APIs can fail for many reasons, but one common culprit is drift. URLs, examples, error shapes, and documentation slowly fall out of sync with the published version. Treat all of your artifacts, including examples, tests, documentation, and monitoring, as part of the API contract. When they stay connected and executable, teams can catch breaking changes earlier and ship more reliable APIs.