DEV Community

Padmaraj Nidagundi
Padmaraj Nidagundi

Posted on

What Happens When 15+ Public APIs Keep Breaking? I Built a Fault-Tolerant Data Platform for India

I started IndiaRealTime with a fairly simple idea:

What if people could check useful, frequently changing information about India without opening five or ten different websites?

Mandi prices. Fuel prices. Gold and silver rates. AQI. Weather alerts. Earthquakes. Bank holidays. Currency conversion. Cricket scores. And a growing list of other data.

Most of this information already exists.

The problem isn't necessarily finding the data.

The problem is making all of those different sources work together reliably.

That turned out to be a much more interesting engineering problem than I expected.

Today, IndiaRealTime is built around roughly 20 independent WordPress plugins that consume around 15 public data sources and feed a single site.

The most important design decision wasn't how to fetch the data.

It was deciding what the website should do when the data source doesn't work.

Live data is easy when every API is healthy.

The interesting engineering starts when it isn't.

The problem: public data doesn't mean reliable data

India has a huge amount of publicly available information.

Agricultural commodity prices are available through public systems.

Air-quality information is published by government sources.

Fuel prices are published by oil marketing companies.

Weather and seismic information comes from different feeds.

Financial information comes from exchanges and other providers.

But these sources don't behave like one clean API.

They have different:

  • response formats
  • update schedules
  • authentication requirements
  • rate limits
  • naming conventions
  • failure modes
  • geographic coverage
  • data freshness
  • maintenance schedules

One source might return JSON.

Another might change a field name.

Another might temporarily return an empty response.

Another might be unavailable for several hours.

Another might return HTTP 200 while the actual data is unusable.

So the problem became:

How do you build a website that depends on many external data sources without allowing one broken source to break the entire site?

That question shaped most of IndiaRealTime's architecture.


Why I chose one plugin per data source

Instead of building one giant ingestion pipeline, I chose a deliberately modular architecture.

Each major data source gets its own WordPress plugin.

Conceptually:

Public data sources
        │
        ├── Source A
        ├── Source B
        ├── Source C
        ├── Source D
        └── ...
             │
             ▼
      Independent plugins
             │
      fetch → validate
             │
          normalize
             │
             ▼
        local cache
             │
             ▼
       WordPress theme
             │
             ▼
       IndiaRealTime
Enter fullscreen mode Exit fullscreen mode

Each plugin owns its own:

  • fetching logic
  • validation
  • normalization
  • schedule
  • cache
  • failure handling

That means a failure in one source doesn't have to become a failure for the entire site.

If a commodity API changes its response format, I want one component to have a problem.

I don't want the weather pages, fuel pages, AQI pages and everything else to become collateral damage.

This is essentially fault isolation at the application architecture level.


But then I discovered the real problem

Suppose a visitor opens a fuel-price page.

The site has a cached value from the previous successful fetch.

Then the upstream source goes down.

What should happen?

Option 1: Show an error

Unable to fetch current fuel price.
Enter fullscreen mode Exit fullscreen mode

Technically honest.

Terrible user experience.

Option 2: Show nothing

Even worse.

Option 3: Keep showing the last successful value

Now the user gets useful information, but it might be stale.

For this project, I chose option 3.

That led to one of the most important patterns in the system:

stale-while-revalidate style fallback.


When the API fails, keep the last good value

The basic flow is:

Fetch new data
     │
     ▼
Is response valid?
   /       \
 yes       no
  │         │
  ▼         ▼
Normalize   Keep previous
  │         cached value
  ▼
Update cache
Enter fullscreen mode Exit fullscreen mode

The important part is that a temporary upstream failure does not immediately become a broken page.

If yesterday's fuel price was successfully retrieved and today's request fails, the system can continue serving the last known good value rather than replacing it with an error.

There is also a short-lived failure flag so the application doesn't repeatedly hammer an upstream source that is already failing.

This sounds simple.

But it changes the reliability model considerably.

The site doesn't have to choose between:

"live but broken"

and

"working but empty."

It can instead say:

"Here is the last known good data while we try again."

Of course, that means freshness becomes part of the data model.

A cached value isn't magically current.

So the system needs to be honest about what it knows and when it last successfully obtained it.

That distinction is important for any application that claims to provide "real-time" information.


Real-time is really a freshness problem

I think the phrase "real-time data" can be misleading.

Fetching something every few minutes doesn't automatically make it real-time.

What matters is:

  1. When was the source updated?
  2. When did we retrieve it?
  3. Did the retrieval succeed?
  4. Is the value valid?
  5. When should we try again?
  6. What do we display if the source fails?

Once you have multiple external sources, "real-time" becomes less about speed and more about freshness management.

For example, different datasets don't need identical schedules.

Some information can be refreshed several times a day.

Some information changes daily.

Some feeds need much more frequent updates.

So each plugin has its own schedule rather than forcing every source into one global polling system.


Why the API health monitor exists

Once you have one plugin, checking whether it works isn't particularly difficult.

With around 20 independent plugins, manually checking logs becomes annoying.

So IndiaRealTime has a separate API health-monitoring component.

Conceptually:

Plugin A ──┐
Plugin B ──┤
Plugin C ──┤
Plugin D ──┤──> API Health Monitor
Plugin E ──┤
Plugin F ──┘
Enter fullscreen mode Exit fullscreen mode

The goal is simple:

One place to see which external integrations are healthy and which ones need attention.

This is one of those engineering decisions that doesn't make the homepage look more impressive.

But it makes operating the system much easier.


Another challenge: PHP and JavaScript don't always agree

One of the more unusual problems appeared in author attribution.

The backend computes a hash in PHP.

The frontend also needs to compute the corresponding value in JavaScript.

That sounds trivial until integer behavior becomes relevant.

PHP and JavaScript don't handle integer arithmetic in exactly the same way.

In particular, reproducing 32-bit wraparound required explicit handling on the JavaScript side.

The solution uses Math.imul() so the JavaScript calculation can reproduce the intended 32-bit multiplication behavior.

Without that, the backend and frontend can silently calculate different hashes.

There isn't necessarily an obvious error on the page.

The problem only appears when you compare the two results.

This was a good reminder that cross-language compatibility isn't only about syntax or APIs.

Sometimes it is about arithmetic semantics.


Location routing was another interesting problem

IndiaRealTime has location-based URLs.

The site needs to represent things like:

/state/city/category/
Enter fullscreen mode Exit fullscreen mode

rather than maintaining a completely separate routing implementation for every state and city.

The application parses location information through shared WordPress query variables such as:

ir_state
ir_sub
ir_sub2
Enter fullscreen mode Exit fullscreen mode

That lets the same architecture handle many different locations.

But flexible routing creates its own problems.

For example:

  • Is this segment a city?
  • Is it a category?
  • Is there a trailing slash?
  • Does this URL represent a national page or a local page?
  • What happens when a location name overlaps with another route?

Those are not glamorous problems.

They're the kind of problems that show up after the architecture looks finished.

So routing edge cases need dedicated tests rather than relying on manually opening a few URLs in a browser.


Accessibility isn't just a CSS detail

Another thing I wanted to take seriously was color.

A data website naturally uses semantic colors.

Green might mean good.

Yellow might mean caution.

Red might mean dangerous.

AQI makes this especially important.

If a page is communicating air-quality severity, color isn't merely decoration.

It carries information.

So the semantic colors are checked for WCAG contrast and considered against color-vision deficiencies.

The AQI bands also follow the relevant CPCB scale rather than using an arbitrary visual gradient.

That creates a useful engineering principle:

If color communicates data, color is part of the data presentation layer.

It deserves testing just like an API response does.


Structured data is part of the architecture too

IndiaRealTime isn't only rendering HTML for humans.

The project also generates machine-readable structured data through Schema.org JSON-LD.

That gives search engines and other systems a more explicit representation of what a page contains.

The architecture therefore looks roughly like:

             External sources
                    │
                    ▼
             Data fetchers
                    │
                    ▼
              Validation
                    │
                    ▼
               Normalize
                    │
                    ▼
              Cache/store
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
     HTML pages          Schema.org JSON-LD
          │
          ▼
      Human users
Enter fullscreen mode Exit fullscreen mode

And when fresh data is successfully updated, the system can use IndexNow to signal that updated content is available for crawling.

Again, the idea isn't to make search engines the architecture.

The goal is to make fresh data discoverable without waiting indefinitely for a crawler to happen to revisit the page.


Why WordPress?

This is probably the question developers will ask.

Why build a data aggregation system with WordPress?

The answer is that WordPress wasn't being used as a page builder.

The project uses:

  • a custom WordPress theme
  • PHP templates
  • vanilla JavaScript/CSS
  • MySQL
  • custom plugins
  • WP-Cron
  • WordPress's existing routing and content infrastructure

There is no page-builder dependency at the center of the architecture.

WordPress provides the application framework, while the custom plugins provide the data-ingestion layer.

That separation works reasonably well for this project.

It also gives me a useful deployment and content-management environment without having to build an entire CMS from scratch.


But ~20 plugins isn't free

The modular architecture has a clear tradeoff.

Fault isolation is good.

Maintainability is harder.

With one plugin per source, shared conventions have to remain consistent.

For example:

  • transient naming
  • cache durations
  • failure flags
  • error handling
  • scheduling
  • validation
  • data normalization

A monolith gives you more opportunities to centralize those conventions.

A collection of independent plugins gives you more isolation.

So the architecture deliberately chooses:

more components + stronger isolation

over:

one central ingestion system + tighter coupling.

I'm still interested in whether that is the correct long-term decision.


What IndiaRealTime currently covers

The project currently brings together data and utilities across several categories:

Prices

  • Mandi/agricultural commodity prices
  • Fuel prices
  • LPG
  • Precious metals
  • Currency conversion
  • Mutual fund NAVs
  • FD rates
  • Toll rates

Environment

  • Air quality
  • Weather alerts
  • Earthquake alerts

Civic and time

  • Bank holidays
  • Pincode lookups
  • Timezone conversion

Culture

  • Panchang
  • Muhurat
  • Vrat calendar
  • Rashifal

Sport

  • Live cricket scores

The exact coverage depends on what the underlying sources make available.

For example, fuel prices can be represented at city level rather than just showing a national average.


The part I like most: the site is actually a test of the architecture

IndiaRealTime started as a website.

But the longer I worked on it, the more it became an experiment in a different question:

How do you build a useful application on top of external systems that you don't control?

That question applies far beyond India.

It applies to:

  • payment providers
  • shipping APIs
  • weather services
  • financial APIs
  • government data
  • mapping services
  • social APIs
  • SaaS integrations
  • public datasets

The upstream provider can change.

The network can fail.

The response can be malformed.

The service can be rate-limited.

The data can become stale.

Your application still has to behave sensibly.

That is why the stale-cache mechanism ended up being more important than some of the visible features.


What I would change if I started again

There are several things I'd approach differently.

1. Historical data earlier

The project currently has historical tracking for some datasets such as fuel and AQI.

Extending that to mandi and commodity prices is one of the next useful steps.

A current value tells you what is happening.

A history tells you why it matters.

2. More automated contract testing

External APIs change.

Ideally, each source should have stronger automated checks that detect:

  • missing fields
  • changed field types
  • unexpected empty responses
  • invalid values
  • schema changes

before those changes reach production pages.

3. More complete accessibility testing

Color contrast is only one part of accessibility.

The next step is broader testing for:

  • keyboard navigation
  • focus states
  • semantic HTML
  • screen readers
  • form controls
  • dynamic content

4. More granular geographic coverage

The architecture can support more city-level data as the underlying sources permit it.

That is especially useful for prices that vary geographically.


What I learned building it

The biggest lesson wasn't about WordPress.

It wasn't about APIs.

It wasn't even about caching.

It was this:

Reliability is often about deciding what to do when something goes wrong.

Anyone can build the happy path:

API works
   ↓
Fetch data
   ↓
Display data
Enter fullscreen mode Exit fullscreen mode

Production systems need the other diagram:

API works
   ↓
Fetch
   ↓
Validate
   ↓
Cache
   ↓
Display


API fails
   ↓
Detect failure
   ↓
Keep last known good value
   ↓
Record failure
   ↓
Retry later
   ↓
Continue serving
Enter fullscreen mode Exit fullscreen mode

That second path is where a lot of the engineering lives.


I'd like other developers to challenge this architecture

IndiaRealTime is a working project, but I don't consider the architecture finished.

I'm particularly interested in how other developers would approach:

  • stale data versus unavailable data
  • API failure detection
  • cache invalidation
  • per-source scheduling
  • contract testing for unstable APIs
  • multi-source data normalization
  • plugin-per-source architecture
  • accessibility for data-heavy interfaces
  • cross-language hashing
  • large-scale geographic routing

If you've built something similar, I'd genuinely like to compare approaches.

The project overview is on GitHub:

GitHub: https://github.com/padmarajnidagundi/indiarealtime-com

And the live application is:

IndiaRealTime: https://www.indiarealtime.com

I'm especially interested in hearing from developers who have had the same experience:

the API works perfectly in development, and then production teaches you otherwise.

Top comments (0)