ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

APIs Are ForeverMistake 1: Inconsistent NamingMistake 2: No Versioning StrategyMistake 3: Leaking Internal DataMistake 4: No PaginationMistake 5: Ignoring Error ResponsesMistake 6: Chatty APIsMistake 7: No Rate LimitingMistake 8: Breaking Changes Without WarningThe API Design ChecklistThe Cost of Getting It Wrong
  1. Insights
  2. Architecture
  3. API Design Mistakes That Will Haunt You for Years

API Design Mistakes That Will Haunt You for Years

December 30, 2025·ScaledByDesign·
api-designarchitecturerestengineering

APIs Are Forever

A bad database schema can be migrated. A bad UI can be redesigned. A bad API, once published and consumed by external clients, is nearly impossible to change without breaking someone. Every API mistake becomes a permanent tax on your engineering velocity.

The tax is real: One client spent $380K over 18 months maintaining a parallel v2 API because their v1 design was so broken they couldn't incrementally fix it. Three engineers, full-time, just to support both versions while they migrated 200+ external integrations one by one.

Here are the mistakes we find in every audit.

Mistake 1: Inconsistent Naming

What we find:
  GET /api/getUsers
  GET /api/products/list
  POST /api/create-order
  PUT /api/users/updateProfile
  DELETE /api/removeItem/123

Five endpoints, five different naming conventions.

What it should be:
  GET    /api/users
  GET    /api/products
  POST   /api/orders
  PUT    /api/users/:id/profile
  DELETE /api/items/:id

Rules:
  - Nouns, not verbs (HTTP method IS the verb)
  - Plural resource names (users, not user)
  - Kebab-case for multi-word resources (order-items)
  - Consistent nesting (max 2 levels deep)

Mistake 2: No Versioning Strategy

Day 1: GET /api/users returns { name: "John" }
Day 90: Marketing wants full name split
Day 91: GET /api/users returns { firstName: "John", lastName: "Doe" }
Day 92: Every mobile app in production breaks
Day 93: Support tickets flood in, App Store rating drops from 4.7 to 3.9
Day 94: Emergency rollback, 16 engineering hours burned
Week 2: CTO mandate: "No API changes without major version bump"
Month 1: Still supporting both formats, technical debt created

This costs you: One breaking change = 40-80 hours of emergency fixes,
customer support surge, and permanent maintenance burden. We've seen
this cascade cost $45K-120K depending on integration count.

The fix: Version from day one.

Option A: URL versioning (simplest, recommended)
  GET /api/v1/users
  GET /api/v2/users

Option B: Header versioning
  GET /api/users
  Accept: application/vnd.myapp.v2+json

Option C: Query parameter
  GET /api/users?version=2

Pick one. URL versioning is the most visible and debuggable.

Mistake 3: Leaking Internal Data

// BAD: Returning the database row directly
app.get("/api/users/:id", async (req, res) => {
  const user = await db.query("SELECT * FROM users WHERE id = $1", [req.params.id]);
  res.json(user); // Exposes password_hash, internal_notes, stripe_customer_id
});
 
// GOOD: Explicit response shaping
app.get("/api/users/:id", async (req, res) => {
  const user = await db.query("SELECT * FROM users WHERE id = $1", [req.params.id]);
  res.json({
    id: user.id,
    name: user.name,
    email: user.email,
    createdAt: user.created_at,
    // password_hash, internal_notes, stripe_id — intentionally omitted
  });
});

Rule: Never return database rows directly. Always map to an explicit response shape. This prevents accidentally leaking sensitive data when new columns are added.

Mistake 4: No Pagination

GET /api/products → returns all 50,000 products

This works with 100 products. At 50,000, it:
  - Times out the request (30s timeout → 502 Bad Gateway)
  - Consumes 200MB of memory per request
  - Takes 8 seconds to serialize
  - Crashes the client trying to parse it
  - Kills your database connection pool (each request holds connection for 8s)

We've seen this fail: One API endpoint without pagination brought down
an entire platform during a product catalog sync. 500 concurrent requests
each holding a DB connection for 8+ seconds = pool exhaustion. Site down
for 23 minutes. Revenue lost during Black Friday: $127K.

Why this costs you: Without pagination, every endpoint is a potential DoS
vector. The fix takes 2 hours. The incident costs 100x that.

Standard pagination:
  GET /api/products?page=1&limit=20

  Response:
  {
    "data": [...20 items...],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 50000,
      "totalPages": 2500,
      "hasMore": true
    }
  }

Cursor pagination (better for large datasets):
  GET /api/products?limit=20&after=cursor_abc123

  Response:
  {
    "data": [...20 items...],
    "pagination": {
      "limit": 20,
      "nextCursor": "cursor_def456",
      "hasMore": true
    }
  }

Mistake 5: Ignoring Error Responses

What we see:
  Error: { "error": "Something went wrong" }
  Error: { "message": "Bad request" }
  Error: { "success": false }
  Error: 500 Internal Server Error (empty body)

What it should be (consistent error format):
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid email format",
      "details": [
        {
          "field": "email",
          "message": "Must be a valid email address",
          "received": "not-an-email"
        }
      ],
      "requestId": "req_abc123"
    }
  }

HTTP status codes used correctly:
  400: Client sent bad data (validation errors)
  401: Not authenticated (no valid token)
  403: Authenticated but not authorized (wrong permissions)
  404: Resource doesn't exist
  409: Conflict (duplicate entry, version mismatch)
  422: Unprocessable entity (valid format, invalid business logic)
  429: Rate limited
  500: Server error (our fault, not yours)

Mistake 6: Chatty APIs

The mobile app needs to render a product page.
With a chatty API:

  GET /api/products/123           → Product details (200ms)
  GET /api/products/123/images    → Product images (200ms)
  GET /api/products/123/reviews   → Reviews (200ms)
  GET /api/products/123/related   → Related products (200ms)
  GET /api/inventory/123          → Stock status (200ms)
  GET /api/pricing/123            → Current price (200ms)

  6 round trips × 200ms latency = 1,200ms just waiting for network,
  before any rendering. Add processing time: 1,800ms to interactive.

Why this costs you: Every 100ms of delay costs you 1% of conversions
(Amazon research). At 1,800ms, you're losing 7-10% of potential buyers
before they even see the product. For a $2M/month mobile business,
that's $140K-200K in lost revenue annually. The fix (include parameter)
takes one day to implement.

Better: Include related data via query parameters
  GET /api/products/123?include=images,reviews,related,inventory,pricing

  1 round trip. Server joins the data. Response in 200ms.

Mistake 7: No Rate Limiting

Without rate limiting:
  - A single misbehaving client can DoS your API
  - Scraping bots consume your compute budget
  - Credential stuffing attacks go unchecked

Real failure: A client's API had no rate limits. A partner integration
had a bug that retried failed requests with no backoff. One bad deploy
on their side = 50,000 requests/second to the API. Site down for 94
minutes. AWS bill spiked from $4K to $31K that month. Three enterprise
customers churned because their integrations were down. Cost: $280K in
lost contracts plus the AWS overage.

Implement rate limiting at multiple levels:

Global: 1,000 requests per minute per IP
Per-user: 100 requests per minute per API key
Per-endpoint: Sensitive endpoints get tighter limits
  POST /api/auth/login → 5 per minute per IP
  POST /api/orders → 10 per minute per user
  GET /api/products → 100 per minute per user

Return rate limit headers:
  X-RateLimit-Limit: 100
  X-RateLimit-Remaining: 87
  X-RateLimit-Reset: 1706745600

When limited, return:
  429 Too Many Requests
  Retry-After: 30

Mistake 8: Breaking Changes Without Warning

API lifecycle:
  1. Design → Document → Build → Test → Release
  2. Monitor usage → Deprecation notice (90 days minimum)
  3. New version available → Migration guide published
  4. Old version sunset → Only after confirming no active users

Deprecation headers:
  Deprecation: true
  Sunset: Sat, 01 Jun 2026 00:00:00 GMT
  Link: <https://api.example.com/docs/migration-v3>; rel="successor-version"

The API Design Checklist

Before releasing any API endpoint:

□ Consistent naming (nouns, plural, kebab-case)
□ Versioned URL (/api/v1/...)
□ Request validation with clear error messages
□ Response shaping (no raw database rows)
□ Pagination for list endpoints
□ Rate limiting configured
□ Authentication/authorization checked
□ Error format follows standard schema
□ OpenAPI/Swagger documentation generated
□ At least one integration test
□ Monitoring and alerting configured

The Cost of Getting It Wrong

API mistake impact (real client data):

Breaking change without versioning:
  → 40-80 engineering hours fixing integrations
  → 200-500 support tickets
  → App Store rating drop
  → Cost: $45K-120K per incident

No pagination:
  → Production outage during traffic spike
  → 20-90 minutes downtime
  → Cost: $50K-200K in lost revenue (depends on GMV)

Chatty API design:
  → 7-10% conversion rate loss on mobile
  → Cost: $100K-400K annually for mid-market e-commerce

No rate limiting:
  → Single bad client takes down entire platform
  → AWS bill spike
  → Customer churn
  → Cost: $150K-500K per major incident

The pattern: Every mistake takes 2-8 hours to fix correctly but costs
30-100x that when fixed reactively. Design right from day one.

Every API decision you make today becomes a constraint you live with tomorrow. The cost of fixing these mistakes after launch is 10-100x the cost of getting them right the first time. Design your APIs as if they'll be consumed by developers you'll never meet — because they will be.

Previous
How to Run Execution Sprints That Actually Ship
Next
Observability That Actually Helps You Sleep at Night
Insights
Why You Should Start With a MonolithEvent-Driven Architecture for the Rest of UsThe Real Cost of Microservices at Your ScaleThe Caching Strategy That Cut Our Client's AWS Bill by 60%API Design Mistakes That Will Haunt You for YearsMulti-Tenant Architecture: The Decisions You Can't UndoCI/CD Pipelines That Actually Make You FasterThe Rate Limiting Strategy That Saved Our Client's APIWhen to Rewrite vs Refactor: The Decision Framework

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call