Skip to content
Fixing API errors

Reading a 500 from someone else's API: what you can and cannot do

A 5xx is the provider's bug, not yours. Here is how to confirm that, what to do while it lasts, and how to keep your own product usable when a dependency fails.

SandyPublished 7 min read
a rack of electronic equipment in a dark room, illustrating reading a 500 from someone else's api: what you can and cannot do
Photo by Tyler on Unsplash.
{ "error": "Internal Server Error" }

Status 500. Your request was well formed, your credentials were fine, and the API fell over anyway.

The unwelcome truth is that you cannot fix this. What you can do is confirm it is genuinely theirs, decide how long to keep trying, and make sure your own product degrades gracefully rather than collapsing alongside it.

Short answer

A 5xx means the server failed, not your request. Confirm it with a known-good request and a check from a second network, then retry idempotent calls with backoff and a small cap. Beyond that, the fix is architectural: cache aggressively so you can serve stale data, and fail in a way that tells users what is unavailable rather than hiding it behind a generic error.

Which 5xx you got tells you something

The codes are not interchangeable, and each points at a different layer.

CodeMeaningWhat it implies
500Unhandled application errorA bug in their code, possibly triggered by your input
501Not implementedThe method is not supported at all; retrying is pointless
502Bad gatewayA proxy reached the app and got garbage back
503Service unavailableDeliberate: overloaded, rate limited or in maintenance
504Gateway timeoutA proxy gave up waiting for the app

503 is the most informative. It is usually generated deliberately and often carries a Retry-After header, which makes it the one 5xx that tells you exactly how long to wait.

502 and 504 point at infrastructure. The application tier is unreachable or too slow, which typically means a deploy in progress, a crash loop, or an overloaded backend. These are often brief.

500 is ambiguous. It can mean their database is down, or it can mean your input hit a code path they never handled.

Confirm it is theirs

Three checks, each taking under a minute.

Does a minimal request work?

curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com/v1/health
curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com/v1/items?limit=1

If the simplest possible call succeeds and yours fails, your request is triggering it. Bisect your parameters until you find which one.

Does it fail from elsewhere?

Run the same call from your laptop, from a different network, and from a server in another region. A failure everywhere is an outage. A failure only from your infrastructure suggests your egress IP is blocked, your DNS is resolving somewhere stale, or a proxy is interfering.

What do they say?

Check the provider's status page and their public issue tracker or social account. Many outages are acknowledged within minutes, which saves you from debugging something already known.

Retry, but bound it

Retries are correct here, within limits. A 5xx is exactly the transient class that backoff exists for.

const RETRYABLE = new Set([500, 502, 503, 504]);
 
async function withRetry(url: string, init?: RequestInit, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const response = await fetch(url, {
      ...init,
      signal: AbortSignal.timeout(8000),
    });
 
    if (!RETRYABLE.has(response.status)) return response;
    if (attempt === maxAttempts - 1) return response;
 
    // Honour Retry-After when a 503 provides it.
    const retryAfter = Number(response.headers.get('retry-after'));
    const delay = Number.isFinite(retryAfter) && retryAfter > 0
      ? Math.min(retryAfter * 1000, 30_000)
      : Math.random() * Math.min(500 * 2 ** attempt, 15_000);
 
    await new Promise((r) => setTimeout(r, delay));
  }
 
  throw new Error('unreachable');
}

Two boundaries matter. Cap attempts at three or four, because a service that has failed four times over thirty seconds is having a real outage rather than a blip. And never retry 501, which is a permanent statement that the method does not exist.

There is an ethical dimension too. A 503 often means overload. Aggressive retries from every client simultaneously are precisely what keeps an overloaded service down, turning a brief degradation into a sustained outage.

Design so an outage is survivable

This is the part actually within your control, and it matters more than the retry logic.

Serve stale data rather than nothing

For anything that is not real-time, a cached copy from an hour ago is far better than an error.

app/api/rates/route.ts
export async function GET() {
  try {
    const response = await fetch('https://api.frankfurter.app/latest', {
      next: { revalidate: 3600 },
      signal: AbortSignal.timeout(5000),
    });
 
    if (!response.ok) throw new Error(`upstream ${response.status}`);
 
    const data = await response.json();
    await cache.set('rates', data);
    return Response.json({ data, stale: false });
  } catch {
    // Upstream is down. Serve the last good copy and say so.
    const stale = await cache.get('rates');
    if (stale) {
      return Response.json({ data: stale, stale: true }, { status: 200 });
    }
    return Response.json({ error: 'rates unavailable' }, { status: 503 });
  }
}

The stale flag lets the UI show "rates as of 14:20" instead of pretending the number is current. Users tolerate slightly old data. They do not tolerate a blank screen.

Fail one feature, not the page

If a weather widget cannot load, the weather widget should say so while the rest of the page works normally. In React, that is an error boundary around the component rather than around the route.

Name what broke

"Something went wrong" tells a user nothing and makes your product look unreliable. "Live exchange rates are temporarily unavailable, showing yesterday's figures" tells them what happened, that you know, and that the rest of the product is fine.

Knowing before your users do

Two practical conclusions follow. First, monitor your own dependencies rather than waiting for user reports, because a provider's status page often lags the actual outage. Second, prefer APIs with a track record: every listing in our catalogue shows current status and measured reliability, and the status page lists what is failing right now.

Telling a real outage from your own bad request

Before assuming the provider is broken, it is worth spending two minutes establishing which side the problem is on, because a 500 does not guarantee it is theirs.

The most informative test is whether it reproduces. A genuine infrastructure failure is usually intermittent and affects every request equally, so a 500 that appears for all inputs and then clears is almost certainly theirs. A 500 that occurs reliably for one specific input and never for others points at their unhandled exception triggered by your payload — which is still their bug, and one you can work around by changing what you send.

Narrowing the input is the next step. Strip the request to the minimum that should be valid and add fields back one at a time. When the failure appears, you have found the field their validation does not handle, and that is the detail that makes a bug report actionable rather than a complaint.

Check a different endpoint on the same host. If a simple read works while your specific call fails, the service is up and your request is the variable. If everything fails, it is theirs.

Then look outward. A provider status page, their incident feed, or a quick search for other people reporting the same thing in the same hour usually settles it. The absence of an acknowledgement means little in the first few minutes, since status pages are updated by humans who are busy at exactly that moment.

Finally, check whether it is regional. A request from a different network or region succeeding while yours fails points at their edge or CDN rather than the application, which changes both the expected duration and who at their end can fix it.

Writing a report that gets a reply

If it is their problem, the quality of what you send largely determines how quickly it is addressed.

Include the exact request, with any credential redacted. A curl command is ideal, because it removes all ambiguity about what was sent and lets someone reproduce it in one paste. Reconstructing it in prose invites a round trip asking what you actually sent.

Include the full response, not just the status. Headers frequently carry a request identifier that maps directly to a trace in their logs, and quoting it can turn an investigation into a lookup.

Include timestamps with the timezone, and the frequency. "Every request since 14:20 UTC" and "roughly one in twenty since Tuesday" describe very different problems and lead to different places in their monitoring.

State what you have already ruled out. Saying that the same request succeeded an hour earlier, or that a simpler variant works, saves them repeating your work and signals that the report is worth reading carefully.

When it never recovers

Sometimes a 5xx is not transient. Free APIs get shut down, domains lapse, and side projects stop being maintained without announcement. The signals that an outage is permanent:

  • Failures for days rather than minutes
  • The documentation URL now redirects to a parked domain or a marketing page
  • The status page is also gone
  • No response to support contact

At that point the engineering problem becomes a sourcing problem. Our catalogue records these transitions, including the redirect chain, which is what distinguishes a service having a bad afternoon from a domain that has quietly been sold.

Common questions

Does a 500 error mean I did something wrong?

Usually not. The 5xx class means the server failed to fulfil an apparently valid request. The exception is that some APIs incorrectly return 500 for malformed input that should have been a 400, so it is worth testing a known-good request before concluding it is entirely their problem.

What is the difference between 500, 502, 503 and 504?

500 is an unhandled error inside the application. 502 means a gateway got an invalid response from upstream. 503 means the service is deliberately unavailable, often overloaded or in maintenance. 504 means a gateway waited for upstream and gave up.

Should I retry a 500?

Yes for idempotent requests, with exponential backoff and a small attempt cap. If every attempt fails over several minutes, the outage is real and retrying only adds load to a struggling service.

How do I know whether it is the API or my network?

Call the same endpoint from a different network and from a service like a status page. If it fails everywhere, it is the provider. If it fails only from your infrastructure, suspect your egress IP, DNS or a proxy.

What should my app show users during a third-party outage?

Stale cached data with a visible timestamp, or a clear message naming what is unavailable and what still works. A generic error page that hides which dependency failed makes your product look broken rather than partially degraded.

Sources

Written by

Sandy

I build and run this site on my own: the crawler that assembles the catalogue, the checker that probes every listing, and the writing. Before this I built SaveFromInternet and GrabReels, which meant living with other people’s APIs full time — parsers breaking when a platform shipped a change, rate limits arriving without warning, endpoints disappearing overnight. This directory exists because I got tired of free API lists that had never been checked.

APIs mentioned in this article

Frankfurter

Currency Exchange

Exchange rates, currency conversion and time series

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

US Weather

Weather

Weather forecasts and alerts from the US National Weather Service

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

Read next