A Good API Is One Developers Never Have to Ask Questions About

by Eric Hanson, Backend Developer at Clean Systems Consulting

The cost of unclear APIs shows up in Slack, not logs

If your API requires a Slack thread to understand how to use it, it’s already broken.

Not because it doesn’t work, but because it creates friction at the exact point where developers expect speed: integration. Every “quick question” about payload shape, error meaning, or authentication nuance is a tax on both teams. Multiply that across services and you get slow onboarding, fragile integrations, and silent misuse.

The goal isn’t just correctness. It’s obviousness. A good API removes the need for interpretation.

Design for zero-questions, not flexibility

Most API design mistakes come from overvaluing flexibility and undervaluing predictability.

Example of a flexible but confusing endpoint:

POST /user
{
  "type": "admin",
  "data": {
    "name": "Alice",
    "permissions": ["read", "write"]
  }
}

What’s unclear:

  • Is type required?
  • Does permissions apply only to admins?
  • What happens if permissions is omitted?
  • Are there other types?

Now compare with a more explicit design:

POST /admins
{
  "name": "Alice",
  "permissions": ["read", "write"]
}

You’ve traded flexibility for clarity. That’s usually the right trade.

If you must support multiple variants, make them structurally explicit:

POST /users/admin
POST /users/guest

Or at least enforce schemas with clear validation errors.

Error messages are part of the API contract

Most teams treat error responses as an afterthought. That’s where confusion explodes.

Bad error:

{
  "error": "Invalid request"
}

Useful error:

{
  "error": "INVALID_PERMISSION",
  "message": "Permission 'delete' is not allowed for role 'editor'",
  "field": "permissions"
}

This removes guesswork. The developer knows:

  • what failed
  • where it failed
  • why it failed

If you’re using something like Spring Boot, define a consistent error structure via @ControllerAdvice:

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(InvalidPermissionException.class)
    public ResponseEntity<ApiError> handleInvalidPermission(InvalidPermissionException ex) {
        return ResponseEntity.badRequest().body(
            new ApiError("INVALID_PERMISSION", ex.getMessage(), ex.getField())
        );
    }
}

Consistency matters more than completeness. A predictable error format beats a “smart” but inconsistent one.

Naming is a bigger deal than architecture

You can have perfect infrastructure—Kafka, Redis, rate limiting—and still ship a painful API if naming is sloppy.

Common mistakes:

  • Overloaded terms (data, info, payload)
  • Inconsistent pluralization (/user vs /users)
  • Ambiguous verbs (/process, /handle, /execute)

Prefer nouns and clear intent:

  • /orders/{id}/cancel instead of /cancelOrder
  • /invoices/{id}/payments instead of /handlePayment

Developers should be able to guess endpoints without documentation—and be right most of the time.

Defaults should be safe and unsurprising

If an API behaves differently based on undocumented defaults, it’s going to cause production bugs.

Example:

GET /transactions

What’s unclear:

  • Is it paginated?
  • What’s the default page size?
  • Is it sorted?

Make defaults explicit in both behavior and response:

{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 842
  }
}

And enforce them consistently across endpoints. Don’t make /users behave differently from /transactions.

If you’re using Spring Boot, lock this down at the controller level:

@GetMapping("/transactions")
public Page<TransactionDto> getTransactions(
    @PageableDefault(size = 25, sort = "createdAt", direction = Sort.Direction.DESC)
    Pageable pageable
) {
    return transactionService.findAll(pageable);
}

Now the behavior is predictable without reading docs.

Versioning is about stability, not control

Versioning is often used as a safety net for bad design. That’s backwards.

If you need frequent breaking changes, your API is too ambiguous or too tightly coupled to internal models.

Use versioning sparingly and deliberately:

  • URI versioning: /v1/orders
  • Header versioning: Accept: application/vnd.api.v1+json

But more importantly, design for forward compatibility:

  • Add fields, don’t repurpose them
  • Avoid enum values that might change meaning
  • Treat unknown fields as ignorable (per JSON RFC 8259)

A stable API reduces the need for versioning in the first place.

Documentation should confirm, not explain

If your documentation is doing heavy lifting, your API design is compensating for something.

Good docs:

  • confirm assumptions
  • show examples
  • list edge cases

Bad docs:

  • explain basic usage that should be obvious
  • clarify inconsistent behavior
  • include warnings like “be careful when…”

If you rely on OpenAPI (Swagger), generate it from code and enforce schema validation. But don’t expect it to fix poor design—it only reflects it.

Tradeoffs you can’t avoid

Designing for zero-questions comes with real costs:

  • Less flexibility: You may need more endpoints or stricter schemas.
  • Slower initial design: You’ll spend more time naming and structuring.
  • Migration overhead: Fixing a bad API later is expensive.

But the upside compounds:

  • Faster onboarding for new engineers
  • Fewer integration bugs
  • Less cross-team coordination

In most backend systems, that trade is worth it.

What to fix this week

Pick one API your team owns and look at it from a fresh developer’s perspective.

  • Rename one ambiguous field or endpoint.
  • Standardize one inconsistent error response.
  • Make one default behavior explicit in the response.

If someone has to ask how to use it, treat that as a bug—not a documentation gap.

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

How Remote Teams Manage Projects Without Chaos

Managing projects remotely can feel like herding cats. With the right approach, teams stay organized, aligned, and stress-free.

Read more

Questions to Ask Before Starting a Backend Project

“We just need an API… should be quick, right?” That sentence has started more fragile backend systems than anyone admits.

Read more

When Staging Access Requires Manager Approval

Ever waited hours just to test a feature on staging? When every access request has to go through a manager, productivity takes a hit.

Read more

How to Set Clear Expectations Before Starting a Project

Nothing derails a project faster than mismatched expectations. Setting them clearly from the start saves time, stress, and headaches later.

Read more