Every bank card deal in Pakistan, kept fresh every day
The card offers that power CardWise and CardIntel change constantly — new merchants appear, discounts shift, deals expire. This is the engine that keeps up: an incremental sync that reverse-engineers the deals platform seven banks embed, and lands thousands of clean, current offers in a Supabase warehouse without re-fetching the whole country every night.
Arham Mirkar
DataLayer — Enterprise Data Infrastructure
Fresh data is the whole product
A deals app is only as good as its last sync. Show a discount that expired last week and you've broken trust; miss a merchant a competitor already lists and you look incomplete. Across seven banks and thirty-three cities, that's tens of thousands of offers that each have their own lifecycle.
The naive fix — re-download everything, every day — is slow, wasteful, and hammers the source. The real problem was building a sync that's incremental: fetch only what changed, update in place, expire what vanished, and never lose the thread if a long run gets interrupted halfway through.
An earlier version of this project scraped bank pages with an LLM-powered browser extension. It was replaced: reading the structured API the portals already speak is faster, cheaper, and far more reliable than asking a model to read a webpage.
Six stages, run every day
The design principle is simple: do the least work that keeps the data correct. Almost every stage exists to avoid re-doing work that hasn't changed.
Discover
SDK reverse-engineeringEach bank embeds Peekaboo Guru's deals portal in an iframe. The pipeline talks to that same private SDK API directly — carrying the per-bank owner key and IFRAME headers so every request looks exactly like the bank's own embed.
Fetch
Paginated JSONFor every bank × city, it pages through the merchant list 100 at a time from one obfuscated endpoint, then pulls each merchant's full deal set from a second — a clean JSON tree instead of scraped HTML.
Detect changes
Signature cacheA local cache fingerprints each merchant (logo, cover, gallery, deal count). If nothing moved since yesterday, the deal fetch is skipped entirely — so a daily run only touches what actually changed.
Normalize
StructureRoughly a hundred Peekaboo tags collapse into 9 clean categories; merchants and cards are de-duplicated, terms are split out, and branches flattened — turning one vendor blob into related rows.
Load
Upsert on deal URLEvery offer's deal URL is its natural key. New offers insert, changed ones patch in place, and offers that disappeared from the source get expired rather than deleted — so history stays intact.
Refresh intelligence
CardIntel RPCsOnce the raw data lands, the sync fires the CardIntel refresh chain — rankings, merchant intelligence, and dashboard snapshots — so the analytics layer is current the moment the sync finishes.
Speaking the portal's own language
The banks don't publish an API. They embed a third-party deals portal in an iframe, and that portal talks to a private SDK endpoint. The sync becomes a well-behaved client of that same endpoint — carrying each bank's owner key and the exact headers its iframe sends.
This is a reverse-engineered client of a private SDK, not an official partner feed — the same discipline used elsewhere in the portfolio, applied to keep a consumer product's core data fresh.
Only sync what actually moved
The difference between a five-minute sync and a five-hour one is knowing what to skip. Three mechanisms keep the daily run cheap and safe to interrupt.
The payoff: a daily run re-fetches only the merchants that changed, typically a few hundred out of well over a thousand — the rest are served from cache in seconds.
One JSON blob, ten related tables
The source returns a nested blob per merchant. That gets decomposed into a relational schema so the app can filter offers by bank, city, category, and card — and so CardIntel can rank them.
What this pipeline feeds
This sync is the quiet layer underneath two products. Everything they show a user traces back to a row this pipeline kept fresh.
Arham Mirkar
Founder & Data Engineer, DataLayer
“My first version of this had an LLM read each bank's webpage and guess the structure. It worked in a demo and broke in production. The rewrite does the opposite — it speaks the structured API the portal already uses, and spends all its cleverness on notre-doing work. Boring, incremental, and it just keeps running.”
Technology
| Layer | Approach |
|---|---|
| Source | Reverse-engineered Peekaboo Guru bank SDK API, per-bank owner-key auth, IFRAME-medium headers |
| Ingestion | Python 3, requests, city-paginated entity + deal fetch, 3× retry with exponential backoff |
| Change detection | File-based signature cache, resumable run state, full-scan stale-offer expiry |
| Normalization | ~100-tag → 9-category map, merchant + card de-duplication, terms dedup, branch flattening |
| Storage | Supabase (PostgreSQL) via PostgREST, unique-URL upserts, many-to-many junction tables |
| Analytics refresh | Post-sync CardIntel RPCs — rankings, merchant intel, dashboard snapshots |
Need data that stays current on its own?
A dataset is a liability the day it goes stale. We build the incremental syncs that keep it alive — fetch what changed, update in place, and never fall over on a re-run.