The Error Message That Tells the Developer Nothing at All

by Arif Ikhsanudin, Backend Developer

The message you shipped

{ "message": "Something went wrong" }

This is not an edge case. It ships in production APIs every week. Sometimes it comes dressed up:

{ "error": "Internal server error", "status": 500 }

Or with false precision:

{ "code": 1042, "message": "Operation failed" }

The code number implies a lookup table exists somewhere. It usually does not, or the table is internal and the developer has no access to it. Either way, the developer is stuck.

Why this keeps happening

The person who wrote the error handler knew exactly what went wrong. The exception was right there in the stack trace. The context was obvious from the surrounding code. So they wrote a message that made sense to them in that moment and moved on.

The problem is that the message has to make sense to someone who has none of that context, months or years later, under pressure.

There is also a habit of treating error messages as defensive — vague to avoid leaking internals. That instinct is not wrong, but it gets applied too broadly. The result is that legitimate integrators are treated the same as potential attackers.

The anatomy of a useless message

A message is useless when it:

  • Describes the category of failure without the cause ("Invalid input")
  • Names the system that failed without saying why ("Database error")
  • Gives a code without a stable reference ("Error 5023")
  • Is accurate but not actionable ("Request could not be completed")

None of these tell the developer what to fix. They tell them to go look somewhere else — logs, documentation, support tickets — for the real answer.

Rewriting useless messages

Take a common validation failure:

Before:

{ "error": "Validation error" }

After:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The 'interval' field must be one of: daily, weekly, monthly. Received: 'bi-weekly'."
  }
}

The after version answers the question immediately. The developer does not need to look anything up.

A dependency timeout:

Before:

{ "error": "Service unavailable" }

After:

{
  "error": {
    "code": "DEPENDENCY_TIMEOUT",
    "message": "The downstream inventory service did not respond within the 5000ms timeout. This is likely transient.",
    "retryable": true
  }
}

Now the developer knows it is not their fault, knows not to change their request, and knows to retry.

An authorization failure:

Before:

{ "error": "Forbidden" }

After:

{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "This endpoint requires the 'orders:write' scope. The token provided has scopes: ['orders:read', 'products:read']."
  }
}

The developer now knows exactly which scope to request. No support ticket needed.

The security objection

The common pushback: detailed messages expose internals. True, with caveats.

For authenticated API consumers — developers building on your platform — detailed errors are almost always the right call. They are already inside your trust boundary.

For unauthenticated or public-facing endpoints, be selective. You do not need to expose which specific database constraint failed on a signup endpoint. But you can still be specific without being insecure:

{
  "error": {
    "code": "EMAIL_ALREADY_REGISTERED",
    "message": "An account with this email address already exists."
  }
}

This is specific and actionable. Yes, it confirms whether an email is registered — but that same information is available via the "forgot password" flow. Obscuring it in the signup error does not meaningfully improve security while it definitely degrades the developer experience.

The question to ask: does this message help a legitimate developer more than it helps an attacker? For most API errors, the answer is yes.

Write the message for the person at 11pm

The heuristic that works: write the error message assuming the reader has no access to your codebase, your logs, or your documentation. They have only what you return. If that message does not give them a path forward, rewrite it until it does.

That constraint will surface every vague, defensive, or lazy message in your API. Fix them before you ship.

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

Logs Are Useless If Nobody Reads Them

Most applications produce logs. Few produce logs that are useful during an incident. The gap between logging and observable behavior is where debugging goes to die.

Read more

Rails API Mode vs Full Rails — When Lightweight Actually Makes Sense

Rails API mode strips out middleware and view layers to produce a leaner JSON backend, but the decision to use it is frequently made for the wrong reasons — and costs teams features they will want back within two months.

Read more

The Performance Bug That Only Appears Under Real Traffic

Some performance problems are invisible in staging, invisible in load tests, and only surface under the specific combination of data, concurrency, and access patterns that real users produce. Here is how to find them before they become incidents, and how to diagnose them when they do.

Read more