DEV Community

Cover image for Six apps we worked on vanished from the stores. Nobody told us.
Ahmed ElFirgany
Ahmed ElFirgany

Posted on

Six apps we worked on vanished from the stores. Nobody told us.

A few weeks ago I opened every store link in our portfolio. 39 links, one by one.

Six apps were gone. Not on Google Play, not on the App Store.

One of those links had already gone to a client. It was sent as proof of our work.

Nobody told us. When an app is removed, the store does not email the people who built it.

Six removed apps next to the ones you can still open today

The code was fine. The app was never ours.

Those apps made it to the stores. Shipping was never the problem.

An app belongs to the business behind it. The owner decides to renew the developer account. The owner pays for the server. The owner decides to shut it down.

So a portfolio link can die on a random Tuesday. And your portfolio keeps showing it.

Why "just check the page" failed

The obvious fix is to open each link and look.

We tried that with a page-reading tool first. It said one iOS app was still live on the US storefront. We fixed a link based on that answer.

A few hours later a real browser opened the same link. It showed "The page you're looking for can't be found."

Whatever the tool read, it was not the live page. A page reader is not proof that an app exists.

What we check instead

Apple: ask the lookup API, not the web page.

Apple has a public lookup endpoint. You pass app IDs and get back the apps that exist.

ids = ",".join(APPLE_IDS + [CONTROL])
_, body = get(f"https://itunes.apple.com/lookup?id={ids}&limit=200")
found = {str(r.get("trackId")) for r in json.loads(body).get("results", [])}
Enter fullscreen mode Exit fullscreen mode

Any ID missing from found is an app that no longer exists.

The control is the part that matters.

CONTROL is an app that is certainly alive. We use WhatsApp.

If the control is missing from the response, the request failed. Maybe the network, maybe Apple. Either way, nothing gets removed based on that run.

if CONTROL not in found:
    warn("control missing: the query failed, not the apps")
    return
for app_id in APPLE_IDS:
    if app_id not in found:
        error(f"Apple app gone: id{app_id}")
Enter fullscreen mode Exit fullscreen mode

That one check stops the worst mistake. You never delete a live app because of a bad connection.

For the iOS app from earlier, the lookup returned nothing in 16 storefronts. In the same run, the control came back in 13 of them. Only then did we call it dead.

Google Play: open the details page and read the status code.

url = f"https://play.google.com/store/apps/details?id={package}"
Enter fullscreen mode Exit fullscreen mode

A removed app returns 404. When we confirmed one, we opened a known live package at the same moment.

Link Apple apps by ID, not by name.

apps.apple.com/app/id123 keeps working when the app is renamed. The version with the name in it can break.

It runs every week without us

The check runs in a GitHub Actions job every Saturday morning.

on:
  schedule:
    - cron: '0 6 * * 6'
Enter fullscreen mode Exit fullscreen mode

It opens every store link in the portfolio. Today that is 9 Apple apps and 10 Google Play apps.

If something fails, the job opens a GitHub issue with the output. If an issue is already open, it adds a comment. So a dead link becomes a task, not a surprise from a client.

What changed in the portfolio

Two rules came out of this.

  • The portfolio only shows apps you can open today. Removed apps are not listed as proof.
  • The public app count stays below the real count. If one more app disappears, the number is still true.

The part that is not about code

A portfolio is a claim. Every link in it says "this exists, go look."

Checking it once is not enough. The apps belong to other people, and they change without telling you.

When did you last open the links to your old work?


The portfolio, with every link checked weekly: https://saqrelfirgany.github.io/apps/

Top comments (1)

Collapse
 
launchgatecheck profile image
Launch Gate •

The control app is the best part of this. Most link checkers skip it and then "fix" live links after one flaky run.

Two small things that bit similar checks I've seen:

  • Google Play doesn't always return a hard 404 for a removed app. Depending on region and the reason for removal, you can get a 200 with an "item not found" page, or a redirect. Checking for the app's package name or title in the body, alongside the status, removes the ambiguity.
  • For the web links in a portfolio (client sites, demos), the same control trick works. Check a known-good URL on the same host type in the same run. A lot of "dead" results come from a preview deploy that expired (Vercel, Netlify, Figma Sites) rather than the client's real domain, so it helps to record which host each link was on.

Does the weekly job open an issue when something disappears, or just fail the run?