bazito: how an aggregator of Telegram classifieds is built
bazito.app collects listings for housing, transport, goods, jobs and services from open Telegram channels and shows them on one board. There are currently 26,762 listings across 11 countries and 22 cities, from Belgrade and Yerevan to Phuket, Bali and Da Nang.
What follows is about where the money goes in a system like this, and where the decisions are made.
Recall is the expensive part
The pipeline is arranged around one question: which post is worth paying for. In a typical rental channel ten to twenty percent of messages are listings, the rest being questions, thank-yous and “rented it, thanks everyone”, and running that through a model burns money on small talk.
So a cheap prefilter sits before parsing, scoring weighted signals: price and deal type carry one and a half each, a mention of the object one, details such as floor area, deposit, storey and pool half a point up to three of them, and negative markers minus two. Below the threshold a message never reaches a model.
The part that matters is what happens to the rejects. They are not discarded but stored along with their score. Otherwise the threshold gets moved blind; this way it is visible line by line what the filter is eating, and raising or lowering the bar is a decision taken against a list of losses rather than a hunch.
Deduplication sits in the same place, before parsing. One post blasted by an agent across ten chats is normalised and hashed: case dropped, links removed, everything that is not a letter or a space stripped, whitespace collapsed. The markup and emoji that decorate the same post differently in different channels do not survive to the hash, so ten copies become one parse and one model call.
Where the line runs between rules and a model
The first version parsed posts with rules alone, and for housing that was enough. For jobs and services it was not, and a measurement over the corpus showed why: rules give around 92 percent precision at roughly 20 percent recall.
Recall collapses not through sloppy regexes but because a service almost never calls itself a service. “Flying Moscow to Dubai on the 19th, can take parcels”, “we drive for visa runs daily”, “PhD, language coach, I get you talking in a month” are delivery, a visa run and a tutor, and none of them contains a word a regex would have caught them by.
Hence the division of labour I consider the main decision in the project. Meaning, that is intent and vertical, is decided by a model. Money stays with the rules: periods, ranges, net and gross, the channel’s currency are parsed deterministically, because rules are strong exactly there, and a model is unpredictable exactly where the cost of an error is visible to the user.
Safety is not subject to the model’s verdict at all. The check for recruitment schemes runs before it and independently: a blocked post stays blocked whatever the model answers. Otherwise recruitment gets smuggled through by rephrasing.
What it cost to admit the measurement was wrong
The model is not shown the whole corpus. A wide net of words narrows the selection: out of 42,660 unique posts it takes 17,339, and once confidently parsed housing and transport are removed, 8,526 remain, a fifth of the corpus. Half the calls saved.
Then came the part this section exists for. The net was widened with new words, its recall checked against three hundred random rejected posts. The gain was then verified on fresh data, and verification cut it noticeably: on the sample the words had been tuned against it came to 41.5 percent verticals, and without that sample 38.3.
The honest bottom line at that point: the net was losing around eight hundred verticals, the expansion returns around eighty, recall moves from 62 percent to 66. That is not at all the same as “caught half of what we were missing”, and resisting the second phrasing took effort.
Then everything rejected was annotated, all 10,894 posts, and 397 verticals turned up in there. So the net’s real recall was not 62 percent but 78, and the earlier estimate understated it because it had been computed on a sample taken before the expansion. From that day the net stopped being a corpus filter: everything meeting the stage conditions gets annotated.
Two things went from here into other projects. An estimate from a sample of three hundred lies confidently in both directions. And a number obtained on the same data the method was tuned on has to be re-measured on fresh data, or it stays pretty and wrong.
A median instead of a feeling
An extracted price on its own says little: whether 350 euro for a flat is a lot depends on the city, the floor area and the month. So a median is calculated for each selection, and the card shows how far the listing sits from it.
A median rather than a mean, because one villa at ten thousand should not move the reference point for the whole selection. And it is computed over the selection in front of the person, so the number changes when the filters change.
What never reaches the board
Contacts. No phone numbers, no handles, no links to personal profiles: the redaction step sits in the pipeline before the board, and the board states it in plain words, with no intermediary and no commission.
That is why the button on a card leads to the source channel. It costs one extra click, and the author gets the reply where they published the listing themselves.
There is a complaints page and a report button on every card, with the listing it refers to attached automatically. The analytics counter does not load until the visitor has answered the cookie question.
Geography
Countries and cities are not written into the code, they follow from which channels are read: the list sits in configuration, Bali channels were added and Bali appeared. Which is why the current map looks the way it does: UAE, Armenia, Bulgaria, Cyprus, Georgia, Indonesia, Montenegro, Serbia, Thailand, Turkey, Vietnam. It grew from where live channels turned up, not from a roadmap.
The project as a whole, with its architecture, decisions and figures, is on the bazito case page.
Board figures and screenshots taken on 22 August 2026; the recall measurements date from late August and are named in the text. The board updates daily.