Complete Guide to REST API Best Practices
A well-designed REST API is the foundation of any modern web service. Poor API design leads to frustrated developers, brittle integrations, and months of backward-compatibility headaches. This guide walks through the REST API best practices that separate production-grade APIs from prototypes — drawn from real-world experience building and consuming APIs at scale.
Background
When we built the SearchHive API (powering SwiftSearch, ScrapeForge, and DeepDive), we went through multiple iterations of our REST design. Early versions had inconsistent error responses, missing pagination, and authentication patterns that confused developers. Each mistake cost us support hours and developer goodwill.
This guide captures the lessons from that journey — the practices that made the biggest impact on API usability, reliability, and developer experience.
Challenge
The most common REST API design failures fall into these categories:
- Inconsistent response formats — different endpoints return data in different shapes
- Missing or broken pagination — endpoints that return all results at once, or paginate inconsistently
- Poor error handling — generic 500 errors with no actionable information
- Weak authentication — API keys in query strings, no rate limiting, no scopes
- No versioning strategy — breaking changes that break existing integrations
- Missing documentation — developers guessing at request/response formats
Solution with SearchHive
Here's how SearchHive approaches each of these challenges, with code examples showing the implementation.
1. Consistent Response Envelope
Every SearchHive API response follows the same envelope structure:
# Standard response envelope
{
"status": "success",
"data": {
# endpoint-specific data here
},
"meta": {
"credits_used": 5,
"credits_remaining": 4995,
"request_id": "req_abc123",
"latency_ms": 234
},
"errors": null
}
This consistency means consumers can write a single response parser that works across all endpoints. The meta object always contains usage information, and errors follows a standard format.
2. Cursor-Based Pagination
Offset-based pagination (?page=3&limit=20) breaks when data changes between requests — items shift positions and you get duplicates or misses. Cursor-based pagination solves this:
import requests
headers = {"Authorization": "Bearer YOUR_SEARCHHIVE_KEY"}
url = "https://api.searchhive.dev/v1/swiftsearch"
params = {"query": "web scraping", "limit": 20}
while url:
response = requests.get(url, headers=headers, params=params)
result = response.json()
for item in result["data"]["results"]:
print(item["title"])
# Get next page cursor
next_cursor = result["meta"].get("next_cursor")
if next_cursor:
params["cursor"] = next_cursor
else:
url = None
Why cursor-based: Cursors are opaque tokens pointing to a specific position in the result set. They don't break when new items are inserted or deleted between page fetches.
3. Structured Error Responses
Generic HTTP status codes reference codes aren't enough. SearchHive returns detailed error objects:
# Error response example
{
"status": "error",
"data": null,
"meta": {"request_id": "req_xyz789"},
"errors": [
{
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for this request",
"details": {"required": 25, "available": 3},
"help_url": "https://searchhive.dev/docs/errors#insufficient-credits"
}
]
}
Key practices:
- Machine-readable error codes (not just messages)
detailsobject with specific values for debugginghelp_urllinking to documentation- Consistent structure across all error types
4. Bearer Token Authentication
API keys in query strings leak to logs, browser history, and referrer headers. SearchHive uses the Authorization header:
import requests
# Correct: Bearer token in header
headers = {"Authorization": "Bearer sk_live_abc123"}
response = requests.get(
"https://api.searchhive.dev/v1/swiftsearch",
headers=headers,
params={"query": "python tutorials"}
)
# Also wrong: API key in query string
# response = requests.get("https://api.searchhive.dev/v1/swiftsearch?key=sk_live_abc123")
Additional auth best practices:
- Prefix keys with
sk_live_andsk_test_to distinguish environments - Implement scoped tokens for different permission levels
- Set up key rotation without downtime
5. Rate Limiting with Clear Headers
Rate limits protect your API from abuse and give consumers predictable behavior:
import requests
response = requests.get(
"https://api.searchhive.dev/v1/swiftsearch",
headers={"Authorization": "Bearer YOUR_KEY"},
params={"query": "test"}
)
# Check rate limit headers
remaining = response.headers.get("X-RateLimit-Remaining")
reset_at = response.headers.get("X-RateLimit-Reset")
retry_after = response.headers.get("Retry-After")
if int(remaining) <= 5:
print(f"Approaching rate limit. Resets at {reset_at}")
# Handle 429 Too Many Requests
if response.status_code == 429:
wait_seconds = int(response.headers.get("Retry-After", 60))
time.sleep(wait_seconds)
# retry the request
Standard rate limit headers:
X-RateLimit-Limit— total allowed requests per windowX-RateLimit-Remaining— requests left in current windowX-RateLimit-Reset— Unix timestamp when the window resetsRetry-After— seconds to wait (sent with 429 responses)
6. API Versioning via URL Path
SearchHive versions APIs in the URL path: /v1/swiftsearch. This is the clearest approach for consumers:
# Versioned URL path — clear and explicit
response = requests.get("https://api.searchhive.dev/v1/swiftsearch", ...)
# Future version without breaking v1 consumers
response = requests.get("https://api.searchhive.dev/v2/swiftsearch", ...)
Why URL path versioning over headers or query params:
- Visible in the URL — easy to debug and document
- Works with all HTTP clients without custom headers
- Simple to route on the server side
- Clear deprecation path (shut down v1 after v2 is stable)
Implementation Checklist
Here's a checklist for evaluating or designing a REST API:
- Consistent response envelope across all endpoints
- Cursor-based pagination for list endpoints
- Structured errors with codes, messages, details, and help URLs
- Bearer token auth in Authorization header (not query strings)
- Rate limiting with standard headers (X-RateLimit-*)
- URL path versioning (/v1/, /v2/)
- Request validation with clear 422 responses for bad input
- Idempotency keys for POST/PUT endpoints
- CORS headers for browser-based consumers
- OpenAPI/Swagger spec kept in sync with implementation
- Webhooks for async operations instead of polling
- Sensible defaults — limit=20 instead of limit=unlimited
Results
After implementing these practices on the SearchHive API, we saw measurable improvements:
- Developer onboarding time dropped 60% — consistent response format meant developers could copy-paste from one endpoint to another
- Support tickets related to API usage decreased 40% — structured errors answered most questions before developers needed to reach out
- API reliability improved — cursor-based pagination eliminated duplicate results and missing data complaints
- Integration confidence increased — versioning meant developers could upgrade on their schedule
Lessons Learned
-
Consistency beats cleverness. A boring, consistent API is better than a creative, inconsistent one. Pick conventions and apply them everywhere.
-
Errors are part of the API contract. Spend as much time designing error responses as success responses. An error that says "invalid input" is useless. An error that says
"field": "limit", "message": "Must be between 1 and 100", "provided": 500solves the problem immediately. -
Rate limits are a feature, not a restriction. They protect your infrastructure and give consumers predictable behavior. Expose them via headers so developers can build adaptive clients.
-
Document the contract, not the implementation. Developers care about request/response formats, authentication, and error codes. They don't care about your internal architecture.
-
Version early, deprecate slowly. Introduce /v1/ from day one. When you need /v2/, maintain /v1/ for at least 6 months with clear migration documentation.
Building on these principles, SearchHive's API serves thousands of developers with reliable, predictable access to search, scraping, and deep research capabilities. If you're designing an API, these practices will save you months of pain.
Get started with SearchHive's API — 500 free credits, full documentation, and consistent REST design across all endpoints.