Random trivia is one of those weird primitives that ends up in more software than you would expect. Daily newsletters that open with a curious fact to lift the rest of the email. Trivia apps that need an endless supply of fresh material so the same player does not see the same card twice in a week. Discord and Slack bots that drop a fact-of-the-day at 9am to nudge a quiet channel back to life. Chatbots that fall back to a friendly aside when the user asks something they cannot answer. Educational platforms that intersperse facts between lessons to keep attention. None of these products are about trivia, but every one of them needs a steady supply of short, well-formed, original facts on tap.
This post walks through why programmatic fun-fact data is its own surprisingly fiddly little problem domain, what a clean fact feed actually looks like, and what the record shape and request patterns should be for random facts, fact-of-the-day, and lookups by ID.
The Useless Facts API is a generous public good, but building production fact features directly on top of any third-party feed has a familiar set of failure modes. The endpoint can be rate-limited, the JSON shape can shift over time, the source domain can move, and the language coverage can change. Beyond plain availability, the consumer side has its own quirks: facts contain quotation marks and special characters that need careful escaping, repeated random calls can return the same fact twice in a row, and the fact-of-the-day endpoint changes once per UTC day — not once per local day — which surprises users in distant timezones.
Random does not mean uniform: A naive integration that fires “give me one random fact” on every page load will, sooner or later, hand the same fact to the same user twice in a session. A production trivia feature wants either a batch-and-shuffle pattern, a per-user seen-set with deduplication, or both. The data layer is one decision; the “do not show this user the same fact twice” layer is a separate one and worth thinking about up front.
Whichever endpoint a fact came from, normalise it to one compact shape so downstream code never has to branch on its origin:
{
"id": "5f048654915ace42c3ab2c246fdc2457",
"text": "US gold coins used to say “In Gold We Trust”.",
"source": "djtech.net",
"source_url": "https://www.djtech.net/humor/shorty_useless_facts.htm",
"language": "en",
"permalink": "https://uselessfacts.jsph.pl/api/v2/facts/5f048654915ace42c3ab2c246fdc2457",
"scraped_at": "2026-05-03T12:39:29.238Z"
}
Keep the id and the permalink even if you never display them — the id is what your per-user seen-set is keyed on, and the permalink is how you re-fetch a curated fact later without storing its text as the only copy. Keep language too, because caching a German fact under an English key is the bug you will otherwise ship.
Whatever you build on, a fact feature only ever asks for four things, and building your data layer around exactly these keeps it small:
| Action | What you get | Inputs that matter |
|---|---|---|
random | N random facts | count, language |
today | The fact of the day (changes once per UTC day) | language |
byId | One specific fact by ID | id, language |
batch | Multiple specific facts by ID | ids, language |
The batch case is the one people leave out and then need: it is how you re-fetch a curated set after an editor picks favourites, and how you rehydrate a cache without a hundred single calls.
The natural pattern is a small scheduled job — a cron, a serverless function, or a daily Apify run — that requests one fact (or a small batch), stores it in your own database keyed by date, and renders it from there. You get caching, replay, and editorial override for free, and the third-party API only sees one call per day per surface instead of one call per page view. For chatbots and Discord/Slack integrations, fetching a small batch of 50 facts in the morning and serving them locally over the rest of the day is almost always the right shape: it stays well inside everyone’s rate limits and keeps response time inside the bot loop fast.
If you wire this up directly, the part that gets people in trouble is not the first request — it is everything around it. Rate limits, retries on transient failures, IP rotation when a single endpoint pushes back, language-aware caching, deduplication for “random” results, and consistent UTF-8 handling for non-ASCII characters in fact text. For anything where the data layer is incidental to the actual product (a newsletter, a bot, an app), a proxy provider like Oxylabs handles the “do not get blocked” layer, and a managed scraper handles the “do not babysit a scraper at 3am” layer.