Ideas Engineered for Tomorrow
We Engineer Services & Solutions for Your Business Needs
Home About
Products
Services
Hire
Industries
Consulting
Partners
Articles Careers Contact
Software Development

API Design: Building APIs That Developers Actually Want to Use

We've built over 50 APIs and consumed hundreds. The difference between a good API and a frustrating one isn't the technology — it's the design decisions.

November 15, 2025 13 min read

A well-designed API is invisible — developers use it, things work, they move on. A poorly designed API generates Slack messages like "what does error code 7 mean?" and "why does this endpoint return different formats for empty lists?" We've learned API design through building them and (more painfully) through consuming badly designed ones. Here's what we've distilled.

URL Design and Naming

URL design seems trivial but it's the first thing developers see. Get it right and the API feels intuitive. Get it wrong and every endpoint requires documentation lookup.

Rules We Follow

Rule Good Bad Why
Use nouns, not verbs GET /users GET /getUsers HTTP method already conveys the action
Plural resource names /users/123 /user/123 Consistent: /users returns list, /users/123 returns one
Nest related resources /users/123/orders /orders?userId=123 Hierarchy is clear. But don't nest more than 2 levels deep
Use kebab-case /payment-methods /paymentMethods URLs are case-insensitive. Kebab-case is the convention
Actions as sub-resources POST /orders/123/cancel POST /cancelOrder When an action doesn't map to CRUD, use a verb sub-resource

HTTP Methods: What They Mean

GET    /users          → List users (with pagination)
GET    /users/123      → Get single user
POST   /users          → Create user
PUT    /users/123      → Replace entire user (all fields)
PATCH  /users/123      → Update specific fields
DELETE /users/123      → Delete user

# Less common but useful:
HEAD   /users/123      → Check if exists (no body)
OPTIONS /users         → Return allowed methods (CORS preflight)

PUT vs PATCH: This confuses everyone. PUT replaces the entire resource — if you PUT without a field, it's set to null. PATCH updates only the provided fields. For most APIs, PATCH is what you actually want. We default to PATCH for updates unless there's a specific reason to use PUT.

Response Design

Consistent Response Envelope

Every API response should follow the same structure. Developers shouldn't have to guess what shape the response will be.

// Success response
{
  "success": true,
  "data": {
    "id": "usr_abc123",
    "name": "Priya Sharma",
    "email": "priya@example.com",
    "created_at": "2025-10-15T10:30:00Z"
  }
}

// List response
{
  "success": true,
  "data": [
    {"id": "usr_abc123", "name": "Priya Sharma"},
    {"id": "usr_def456", "name": "Rahul Patel"}
  ],
  "pagination": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "has_more": true
  }
}

// Error response
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email address is invalid",
    "details": [
      {"field": "email", "message": "Must be a valid email address"}
    ]
  }
}

Date Formats

Always use ISO 8601 with timezone: 2025-10-15T10:30:00Z. Never use Unix timestamps in responses (humans can't read them during debugging). Never use locale-specific formats ("15/10/2025" — is that October 15 or March 10?). Include timezone always — Z for UTC or +05:30 for IST.

Null vs Absent Fields

Be consistent. We include all fields in the response even if null — this makes it easier for clients to deserialize without checking for field existence. The alternative (omitting null fields) saves bandwidth but adds complexity on the client side.

Error Handling That Helps Developers

The quality of your error responses determines how much time developers waste integrating with your API. A good error message saves support tickets.

HTTP Status When to Use Error Code Example
400 Client sent invalid data (validation failure) VALIDATION_ERROR, INVALID_FORMAT
401 Authentication missing or invalid UNAUTHORIZED, TOKEN_EXPIRED
403 Authenticated but not authorized for this action FORBIDDEN, INSUFFICIENT_PERMISSIONS
404 Resource doesn't exist NOT_FOUND
409 Conflict (duplicate email, concurrent edit) DUPLICATE_EMAIL, CONFLICT
422 Valid format but business logic rejection INSUFFICIENT_BALANCE, ORDER_ALREADY_SHIPPED
429 Rate limit exceeded RATE_LIMITED
500 Server error (your fault, not the client's) INTERNAL_ERROR
Never return 200 for errors. We've seen APIs that return 200 OK with {"error": "not found"} in the body. This breaks HTTP caching, confuses monitoring tools, and makes client error handling a nightmare.

Pagination Done Right

Every list endpoint needs pagination. No exceptions. An endpoint that returns all 50,000 users in one response will work today and crash tomorrow.

Offset vs Cursor Pagination

Approach How It Works Pros Cons
Offset ?page=3&per_page=20 Simple, jump to any page, total count easy Slow on large datasets (OFFSET scans rows). Inconsistent with real-time data (items shift between pages)
Cursor ?cursor=eyJpZCI6MTIzfQ&limit=20 Fast (index-based), consistent with real-time data Can't jump to arbitrary page, total count is expensive

Our default: Cursor pagination for high-volume, real-time data (feeds, logs, events). Offset pagination for admin UIs where page jumping matters and dataset size is manageable.

Versioning: Plan for Change

Your API will change. Plan for it from day one.

URL Path Versioning (Our Recommendation)

https://api.example.com/v1/users
https://api.example.com/v2/users

Pros: obvious, easy to route, easy to document. Cons: "not RESTful" say the purists (the resource hasn't changed, just its representation). We don't care about purity — we care about developer experience, and /v1/ is instantly clear.

When to Bump the Version

  • Bump version: Removing a field, changing a field's type, changing error response format, renaming a resource
  • Don't bump: Adding a new field (additive), adding a new endpoint, adding a new query parameter

Rule of thumb: if existing clients break, it's a new version. If they don't notice, it's a backward-compatible change.

Rate Limiting

Rate limiting protects your API from abuse and ensures fair usage. Always include rate limit headers so clients can self-regulate.

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000        # Max requests per window
X-RateLimit-Remaining: 847     # Requests left in current window
X-RateLimit-Reset: 1697382400  # Unix timestamp when window resets
Retry-After: 30                # Seconds to wait (only on 429)

Rate Limit Strategies

  • Per-API-key: Most common. 1000 requests/minute per key. Simple to implement and explain
  • Per-endpoint: Expensive endpoints (search, reports) get lower limits than cheap ones (reads). Prevents one expensive operation from consuming the entire budget
  • Sliding window: Smoother than fixed windows. Prevents the "burst at window boundary" problem where a client sends 1000 requests in the last second of one window and 1000 in the first second of the next

Documentation That Developers Read

The best API documentation has three layers:

  1. Quick start (5 minutes): One curl command that works. Copy, paste, see result. Nothing kills adoption faster than spending 30 minutes before making a single successful request
  2. Reference (complete): Every endpoint, every parameter, every response. OpenAPI/Swagger generated. Machine-readable for SDK generation
  3. Guides (use-case-driven): "How to implement authentication," "How to handle webhooks," "How to paginate through large datasets." These are the pages developers actually bookmark

We use OpenAPI 3.1 with Swagger UI or Redoc for the reference layer. The guides live as Markdown in the repo alongside the code — this keeps them in sync with changes.

Frequently Asked Questions

Should we use REST or GraphQL?

REST for public APIs and simple CRUD. GraphQL when clients need flexible queries across related data (dashboards, mobile apps with varying data needs). We default to REST unless there's a specific reason for GraphQL — REST is simpler to cache, monitor, and rate-limit.

How do we handle authentication in APIs?

API keys for server-to-server. OAuth 2.0 with JWT for user-facing applications. Never send credentials in URL parameters (they appear in logs). Always use HTTPS. Rotate keys regularly — make key rotation painless by supporting multiple active keys during transition.

What response format should we use?

JSON. It's the universal standard for web APIs. Use camelCase for field names (JavaScript convention) or snake_case (Python/Ruby convention) — pick one and be consistent. We use snake_case because it's more readable and aligns with our PHP/Python backend convention.

How do we handle API deprecation?

Announce deprecation 6 months in advance. Add a Sunset header to responses. Send email notifications to API key owners. Monitor usage of deprecated endpoints — don't remove them until traffic drops to near zero. Provide a migration guide, not just a deprecation notice.

Pillai Infotech Engineering Team

We've designed APIs for fintech, healthcare, and SaaS platforms. Our APIs serve millions of requests daily — these practices come from production experience, not theory.

Need API Design or Development Help?

From API architecture to implementation and documentation — we build APIs that developers love to use.

Get a Free Consultation Our Services