Check page backlinks through an API and cache it honestly
Our index moves once a month, so most lookups can be served from memory. A recipe for caches that save credits without showing users stale lies.
AlfaLinks editorial team ·
The rule
Cache until the next index refresh, not for a fixed hour
Because the data changes roughly monthly, an hourly TTL buys you nothing but cost. A month-scoped key gives the same answer all month and invalidates in one move.
Key: action + domain + params + index month
TTL: until the month ends, plus a day of grace
Never cache an error response
Why a short TTL is just a donation to your credit balance
Teams new to link data copy habits from rank trackers. Habit one: they set a one-hour cache because positions move. Link graphs do not move like that.
A 2-credit backlinks call costs $0.0002. That sounds like nothing until a list view fires 50 of them per page load across 400 active workspaces, repeating every hour for identical answers.
The worked numbers below are an example, not a measurement of anyone's traffic. Assume 400 workspaces, 20 domain views each per day, 2 credits per view, and a 1-hour cache that hits maybe a fifth of the time. Credits burn roughly 5x faster than with a month key.
Keyword research tools need fresher data, and for those the big suites remain the better choice. For link context, monthly is the product.
TTL by action
Suggested, not mandated. All actions read the same monthly index.
Action
Credits
Suggested TTL
Why
overview, stats
0
Until month end
Free, but your server still saves latency
bulk_dr
1
Until month end
Whole customer table in one call
refdomains, backlinks
2
Until month end, per page param
Include page, per, sort in the key
anchors, toppages
2
Until month end
Stable between refreshes
search, keyword
3
7 days
Rarely repeated, cheap to skip caching
Request example: warm the cache for a customer table
Call bulk_dr once per customer, not once per row. Up to 2,000 domains fit in one POST body and cost one credit.
bash
curl -X POST "https://alfalinks.net/api.php?action=bulk_dr&key=sk_live_..." \
-d "domains=stripe.com,notion.so,linear.app,figma.com,vercel.com"
# billed once, whatever the row count
Stampede protection and other small things
Single-flight: if ten users open the same domain, make one upstream call and share the promise.
Warm on the first of the month with a background job, not at 9am when users arrive.
Store the response _credits.remaining and alert below a threshold you choose.
Cache the negative result 'domain not found' for a day, not a month.
Expose the cache age in the UI so support can answer 'when was this fetched?'.
Where this connects
The cache is the middle layer in the counter setup. Once it works, the cost per seat becomes predictable and you can price a plan around it with the per-seat comparison. Short pages deserve a clear end. This one has it.
Cache questions we see in code review
Should the cache live in the browser too?
A short browser cache helps back-button navigation, but the shared cache on your server does the real saving. Keep client-side storage to minutes, since users on different machines would otherwise see different numbers for the same domain on the same day.
What if a customer says a new link is missing?
Check the index month shown in your UI. If the link was added after the last refresh, it will appear after the next one. Offer a note, not a refund, because nothing is broken.
Can I bypass the cache on demand?
You can, but each bypass costs credits for an answer that is likely identical. Offer it to admins only, and rate-limit it per workspace to avoid accidental loops.
Pagination and the cache key
Pagination bites everyone once. Include it in the key.
The backlinks and refdomains actions return up to 1,000 rows per call, with page and per between 10 and 5000, and deep paging is capped at 50,000 rows, so a key without those parameters will serve page one for every request.
Sorting belongs in the key as well. A user who flips from DR descending to alphabetical expects a different list, and the cache has to oblige without a new upstream call when the full set is already stored.
Plan the export button separately: one background job, one stored file, and one notification when it is ready.