-
-
One click, and the tab is free to close. bite fetches the article and writes it up while you get on with something else.
-
Twelve tabs, one click. The ones behind a subscription are read from the browser, so an article you pay for isn't locked.
-
A German newspaper, read in English. Translation belongs to the reader, not the save — the whole stack changes with it.
-
Prose, never bullet points: the one idea first, then enough context to understand it. This is what the other three are for.
-
App Icon 1024x1024
-
Impact
-
Inbox
-
Bite reader
-
Briefing
Inspiration
I come across a lot I want to read and watch: articles, YouTube videos, podcasts. So the tabs stay open. Some days it feels like too much to start, most days there simply isn't time, and closing one without having looked at it feels like giving up on something I meant to do. That small guilt, thirty times over, is why bite exists. What I wanted was permission to close the tab, and that only works if what comes back is worth the trade.
So the whole product fits in the line at the top of its website: Save it. Close the tab. Without the guilt.
What it does
You save an article, a YouTube video or a podcast episode, and close the tab. A few minutes later it comes back as a briefing you can finish: written prose, never bullet points, the one idea first and then enough context to understand it. What you have read leaves the stack, so an empty inbox is the goal rather than an accusation. Nothing counts how far behind you are.
Three things fall out of that. Duplicate coverage of one story arrives as one bite instead of three. Videos and podcasts carry jump marks back into the moment that matters, because a summary of an hour of audio is useless without a way in. And a bite can be read in your own language, including things saved months ago.
It lives where the reading happens: an iPhone app, a web app at bite.fyi, and a Chrome extension that saves a whole window of tabs at once. One account, one library and one subscription across all three.
How we built it
Three clients on one backend: a SwiftUI app, a Next.js web app and a Chrome MV3 extension, all talking to a Next.js backend on Supabase. The extension connects by scanning a QR code with the phone, or by signing in on bite.fyi for anyone without an iPhone. Either way, no password is ever typed into it.
Subscriptions run on RevenueCat in both stores. On iPhone it is purchases-ios behind a custom paywall; on the web it is RevenueCat Web Billing, so somebody who never touches the App Store can still buy Pro in the browser. Both land on the same entitlement through one webhook to my own backend, and that webhook also unlocks everything saved while the account was over its limit. Buying is not a switch that starts working tomorrow; the backlog opens at once.
Challenges we ran into
Paid articles. A server fetching a page a subscriber can read sees a preview and nothing else. So detection moved into the browser tab the reader already has open, but "the page has a paywall element" is not the same as "the article is truncated". Two publishers taught me that separately: one ships its paywall markup on free articles too, the other declares a word count that only matches if you measure the whole article rather than the fragment. It ended up as an ordered set of named rules, testable one at a time.
Podcast transcription kept dying inside the request lifecycle, always partway through a long episode. Moving it to a detached bare-Node worker took the same episode from failing to finishing in three and a half minutes.
The cost was wrong, and I only found out by measuring. Benchmarking five models on two real articles turned up a tokenizer gap I had not accounted for: the same German article was 19,553 tokens for one vendor and 9,895 for another. Same text, same job, double the bill. Switching models cut the cost per summary to a quarter at identical measured quality.
Accomplishments that we're proud of
It shipped, all three pieces: the app on the App Store, the extension in the Chrome Web Store, the web app at bite.fyi. A read-later tool that works on only one of the screens you read on is half a product, and the missing half is always the one where the tabs pile up.
The prose. Every summarising tool defaults to bullet points because they are easy to generate and easy to skim past. Writing full sentences is a harder prompt, a longer output and a bigger bill, and it is the only reason the result is worth reading.
And the allowance is honest about what things cost. An hour of podcast has to be transcribed before anyone can read it, which is about thirty times what summarising an article costs, so fair use is a weighted budget rather than a flat count of saves, and the same show on YouTube costs almost nothing because its captions already exist. It would have been easier to hide that behind a round number and a silent wall.
What we learned
RevenueCat, which is the piece I will be reusing in my other apps. What earned it is the webhook: a purchase arrives at my own backend as an event, so entitlement lives in my database rather than in whatever a client claims about itself. That is what let a second store join without a second system, and what lets a purchase reach backwards and unlock the backlog.
Two stores also means two offers, which I learned the embarrassing way. The seven free days are Apple's introductory offer and exist only on iPhone; the web checkout charges from day one. My web pricing page said "7 days free" as fixed text, so it was true in one store and false in the other. Both paywalls now read the offer off the product instead of writing it down. The same page also marked Pro as "your plan" on the monthly AND the yearly column, because my backend only knew "pro" and not which one. RevenueCat knew; I had not asked it.
One gotcha worth writing down: packageType is only .annual or .monthly when a package uses one of RevenueCat's own $rc_ identifiers. Give your packages your own names and you get .custom, so filtering purely by type renders an empty paywall against a dashboard that is configured perfectly.
How to build a Chrome extension, which I had never done. Manifest V3 has two rules that are only obvious once they have cost you an evening. A service worker is stopped whenever it goes idle, so anything on a timer belongs in chrome.alarms rather than in a variable. And executeScript sends only the BODY of the function you hand it; everything that function closed over is gone by the time it runs inside the page. My paywall rules were a module the injected function imported: correct in every test, silently dead in a browser. Now the page gathers signals and the extension decides.
That a test can pass without testing anything. A retry I had described in a commit message was never actually written: the error class was imported and nothing ever threw it, and the tests still passed, because they checked how that error is classified rather than whether anything produced one. A linter found it weeks later, via an unused import. Tests now stub the network and watch the wiring.
That "unlimited" needs its small print in the same line. Once audio is involved nobody can keep that promise literally, so Pro says "with fair use" right next to it, and the app explains in plain words why an hour of audio weighs more than an article. Saying so up front costs less than a refund.
What's next for bite: Read Later, Summarized
Something shared. Summaries are already shared underneath: everybody who saves the same article gets the same bite, which is why the second person to save it gets it instantly and for free. Making that visible (a bite you can hand to somebody, a stack worth reading in public) is the obvious next move, and the interesting question is whether it can stay quiet enough to still be bite.
Speaking more languages. bite already reads in yours: a German article comes back in English if that is what you want, and everything already saved changes with it. What it does not do is talk to you in anything but English. German first, since that is where it is written.
Safari on the desktop. The extension is Chrome-only, which leaves out everybody who reads on a Mac in Safari. Paywall detection is the one genuinely browser-specific part, and it is already an ordered set of named rules rather than a pile of special cases, which is what makes this a port rather than a rewrite.
Built With
- ios
- next.js
- railway
- revenuecat
- supabase
- swiftui
Log in or sign up for Devpost to join the conversation.