Stop Designing APIs for Yourself. Design Them for the Person Calling Them.

by Eric Hanson, Backend Developer at Clean Systems Consulting

The API makes sense—to the people who built it

You can usually tell who an API was designed for by how easy it is to use without context.

If it was designed for backend engineers:

  • endpoints mirror database tables
  • responses expose internal IDs and join structures
  • workflows require multiple round trips and orchestration

If it was designed for consumers:

  • endpoints map to real use cases
  • responses contain everything needed for the task
  • common workflows are simple and predictable

Most APIs fall into the first category. Not because teams don’t care, but because designing for yourself is the default.

You already understand the system. Your consumers don’t.

The symptom: clients doing the heavy lifting

A dead giveaway that your API is self-centered:

GET /users/{id}
GET /orders?user_id={id}
GET /order_items?order_id={orderId}

This forces the client to:

  • understand relationships between entities
  • orchestrate multiple requests
  • handle partial failures across calls

That’s not just inconvenient. It creates duplication across every client.

Better:

GET /users/{id}/orders?include=items

Now the server handles aggregation. The client gets a complete view aligned with its needs.

Yes, it’s more work on the backend. That’s the point.

Think in workflows, not endpoints

Consumers don’t think in terms of endpoints. They think in terms of tasks:

  • “Show a user their recent orders”
  • “Cancel an order”
  • “Create a checkout session”

If your API forces them to map tasks to multiple low-level calls, you’ve shifted complexity in the wrong direction.

Example: canceling an order.

Backend-centric design:

GET /orders/{id}
PATCH /orders/{id} { "status": "cancelled" }
POST /refunds

Consumer-centric design:

POST /orders/{id}/cancel

This is one place where strict REST purity often loses to usability. Modeling everything as state transitions can make simple workflows harder than they need to be.

Be pragmatic. The goal is not ideological correctness. It’s reducing friction for the caller.

Design responses to eliminate guesswork

Another common issue: responses that technically contain the data, but require interpretation.

Example:

{
  "status": "2",
  "flag": true
}

What does 2 mean? What does flag represent?

Now compare:

{
  "status": "shipped",
  "is_expedited": true
}

The second version removes ambiguity.

Even better, include context where useful:

{
  "status": "shipped",
  "estimated_delivery": "2026-04-21",
  "can_cancel": false
}

Now the client doesn’t need to infer business rules.

You’re not just returning data. You’re communicating intent.

Reduce the number of decisions the client has to make

Every optional parameter, every conditional field, every branching behavior is a decision pushed to the client.

Example:

GET /orders?include_items=true&include_payments=true&include_shipping=true

This looks flexible, but it forces the client to understand what combinations make sense.

A more consumer-friendly approach:

GET /orders/{id}/summary

or:

GET /orders/{id}?view=full

You define the shapes that matter. Clients pick from known-good options.

Flexibility is useful, but too much of it shifts responsibility outward.

Error messages should guide, not just report

If an API is designed for its consumers, errors help them recover.

Bad:

{
  "error": "Invalid request"
}

Better:

{
  "error": {
    "code": "INVALID_STATUS",
    "message": "Status must be one of: pending, shipped, cancelled",
    "details": {
      "field": "status"
    }
  }
}

Now the client can:

  • display meaningful feedback
  • correct the issue programmatically
  • avoid guesswork

This reduces support overhead and speeds up integration.

Internal models are not your API

One of the hardest habits to break: exposing internal structures directly.

Example:

{
  "user_id": "123",
  "order_ids": ["a", "b", "c"]
}

This forces the client to fetch more data.

Instead:

{
  "user": {
    "id": "123",
    "orders": [
      { "id": "a", "total": 100 },
      { "id": "b", "total": 50 }
    ]
  }
}

Yes, this duplicates some data across endpoints. That’s fine.

Your API is not a normalized database. It’s an interface optimized for consumption.

The tradeoff: backend complexity vs client simplicity

Designing for the caller has a cost:

  • more complex query logic (joins, aggregations)
  • heavier responses (larger payloads)
  • more opinionated endpoints (less raw flexibility)

But pushing that complexity to clients is worse:

  • every consumer reimplements the same logic
  • inconsistencies multiply across clients
  • bugs become harder to track

You want complexity in one place—the backend—where you control it.

A practical way to shift perspective

When designing or reviewing an endpoint, ask:

  • How many requests does a client need to complete a common task?
  • What assumptions does the client have to make?
  • What errors are ambiguous or hard to recover from?
  • Would someone unfamiliar with the system use this correctly on the first try?

If the answers are uncomfortable, the API is still designed for you.

What to do differently this week

Pick a common client workflow in your system.

Trace every API call required to complete it.

Then ask: how many of those calls could be collapsed into one?

Implement that as a new endpoint or improve an existing one.

You don’t need to redesign everything. Just remove one point of friction.

That’s how APIs start feeling intentional instead of accidental.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

The Real Cost of a Backend Team in Manhattan — And How Async Contractors Change the Equation

You approved the budget for two backend hires. Then HR came back with the fully loaded numbers and suddenly the math didn't work anymore.

Read more

How to Learn Fast Without Wasting Time on Tutorials

Tutorials can feel like a shortcut—but often they slow you down. Here’s how to learn fast by doing, not just watching.

Read more

The Difference Between Remote Work and Remote Micromanagement

Remote work can be liberating and productive—until it turns into constant oversight. Understanding the difference is key to keeping teams effective and sane.

Read more

How to Transition from Employee to Independent Contractor

Quitting your job sounds exciting… until you realize you have to replace your salary. The shift isn’t just about freedom — it’s about learning how to operate like a business.

Read more