
Curio: Taste-sharing social app
A feed of people whose taste you trust, in the order they posted, with no like counts.
- Role
- Designed, built and shipped end to end
- Timeline
- 8 weeks to the App Store, 2026
- Type
- Taste-sharing social app
- Platforms
- iOS and Android
Stack
The problem
Good recommendations die in group chats. Someone sends a song in March and by June nobody can find it, while the apps built for sharing reward whatever collects the most reactions. Curio gives each person a corner: a lasting, browsable library of what they thought was worth passing on, read by people who chose to follow them.
The engineering problem hides behind one gesture. A link shared from Spotify, IMDb, Goodreads or a newspaper has to become a typed, rich card, which means fetching URLs that strangers typed, matching the same song across catalogues that each use their own track ID, and coping with upstream services that are slow, rate-limited or gone. Odesli's public API, the usual way apps matched music links, was switched off two weeks into the project, a closure the plan had already prepared for.
Approach
One link in, a finished card out
The share sheet is the main way in. Any link, from any app, goes through one server-side door that routes it by host: music services, video, IMDb and Letterboxd, book pages, and a generic article reader as the catch-all. TikTok, Instagram and X links are refused at the door. A failed lookup, or one that takes longer than 20 seconds, never blocks the post: a plain card publishes instead, a database trigger queues a job, and a scheduled worker upgrades the card in place, with retries and backoff. Because the server fetches URLs strangers typed, every fetch it makes of a user's URL passes an SSRF guard that resolves DNS right before connecting, blocks private and cloud metadata addresses, re-checks every redirect and caps size and time. A live attack battery against the deployed function found one real leak, a fallback scraper forwarding rejected URLs to a third-party service, and the guard was split so that path checks its target too.

One song, many catalogues
A track shared from Spotify should still offer Apple Music, YouTube Music and Deezer, play a preview and wear the right cover, yet each service has its own track ID. With Odesli's API gone, Curio resolves music itself: it parses the link, finds the recording's ISRC, or the UPC for a single, then asks Apple Music, Deezer and Spotify in parallel, with MusicBrainz and YouTube Music filling gaps afterwards. Results are cached per recording and looked up again once they are a month old. Artwork is ranked by what each source actually matched rather than by a fixed order that always put Apple first, after one post wore the cover of a different record, filling 38% of its frame; with the right cover it fills 99%. Two quieter finds: the iTunes Search API ignores its offset parameter, so page two was page one again, and Spotify's terms forbid mixing its text with other services' audio, so Spotify only reads links and opens them.

Scores you can weigh, streaming where you are
Film and TV posts carry whichever IMDb, Rotten Tomatoes and Metacritic scores OMDb holds for them, plus Letterboxd's average when the post came from Letterboxd, and TMDB's own score as a last resort when nothing else has one. Each score keeps its own scale and names its source, and nothing is averaged, because 8.8/10 and 79% are different crowds on different scales. OMDb's free tier allows 1,000 requests a day, so ratings are fetched once when a post is made and then stored, and posts added through in-app search are filled in by a scheduled sweep. A failed lookup is stored differently from an empty answer, so an outage is never recorded as having no scores. Where to watch works the other way round: it is fetched each time someone opens the post, for the country their phone is set to, because streaming catalogues change weekly and by region. If the phone gives no region, the section stays hidden rather than defaulting to the US, since telling someone in Cairo that a film is on Hulu is worse than saying nothing.

The database is the last line of defence
A phone app can call the database API directly, so any rule enforced only in the app is a suggestion. Curio's rules live in Postgres: row-level security policies, column guards, triggers and locked-down functions, and that is where review found holes and closed them. Early on, the profile update policy had no column restriction, so any signed-in user could turn on, for their own account, the flag that put it in Explore and in every new user's onboarding. Approving a follow request could be abused to forge a follow and skip the message-request gate, fixed with the project's first column-level grant plus a trigger. Notifications now live exactly as long as the thing they describe: unfollow, unlike or unsave, and the notice goes too. And when one deploy took search down because the CLI defaulted a function to require a verified token, every function started declaring that setting with its reason.

Outcome
Curio went live on the App Store on 14 September 2026, 58 days after the first commit, and on Google Play on 3 October, after the 14-day closed test Google requires of a personal developer account. Version 1.0.6 reached Google Play on 21 October and the App Store on 23 October, adding one-to-one messages and a notification outbox written in the same transaction as the action that triggers each notice.
Before that release shipped, a multi-pass adversarial review raised 32 findings; 30 were confirmed and all 30 fixed, including the forged follow above. The product site at curiocc.app carries the Apple and Android app-link files, so from version 1.0.6 a shared profile link can open straight in the app.
Want something like this?
Tell us what you're building and we'll give you an honest assessment of scope, timeline, and cost.