BidTender — multi-tenant SaaS
A whole AI product, built and run alone
A multi-tenant SaaS for bidding on Moroccan public tenders: the daily scraping that fetches the files, the bilingual OCR that reads them, citable retrieval, structured extraction, decision engines, generation of the documents to submit, the sales funnel and billing. 84 API routers and 300 business-logic modules, written and operated by one person.
- Role
- Co-founder · AI and full-stack engineer
- Period
- Since September 2025
Stack
The problem
Before bidding, a company has to know whether it qualifies, which certification is required, what bid bond to post, how it will be scored, and which clauses will prove expensive. That reading takes hours, it is done by the most expensive people in the building, and most files end up discarded anyway.
Pointing an LLM at it is the obvious move. It is also where things break: a tender file is not a document, it is a contractual instrument. An approximate answer is not a slightly worse answer — it is a bid rejected for non-compliance, or a commitment made by mistake.
“I think the bid bond is 50,000 dirhams” is unusable. The same figure with the exact sentence from the specification that states it is usable, because a human verifies it in three seconds. The constraint is not answer quality. It is traceability.
Constraints
Two technical founders: anything one person cannot maintain does not get built.
Multi-tenant from day one, with competing companies on the same database. A cross-tenant leak is not a bug, it is the end of the product.
No control over input format: native PDFs, scans, Word documents, spreadsheets, AutoCAD drawings, and two languages — French and Arabic, including scanned Arabic script.
A wrong answer costs more than no answer. “That information is not in the file” had to be a first-class result.
What I built
Provenance carried end to end. Citations are not lost at generation, they are lost at chunking. Every extracted value keeps the verbatim passage that produced it: 274,376 citations across 58 distinct fields, 99.95 % of them with the source text. A field that cannot be sourced is flagged as uncertain rather than returned with confidence.
Clause-based chunking, not token windows. Splitting a contractual specification every 800 tokens produces chunks that start mid-article. On legal text that is destructive: the unit of meaning is the numbered article, and an obligation cut in half becomes a false obligation. Chunking follows the document hierarchy; the token window is only a safety net.
Hybrid pgvector + BM25 retrieval — out of necessity, not elegance. Procurement language is formulaic: two passages saying opposite things have near-identical embeddings. Conversely, what users actually search for — an article number, a lot reference, a date, an amount — are rare tokens, which is BM25’s territory. Neither approach alone reached a usable level. 151,661 chunks, 113,244 vectors, HNSW index.
Page-level OCR routing, bilingual. Pages with no usable text layer and pages in Arabic script go to a dedicated OCR engine; the rest do not, because blanket OCR is expensive and degrades a native PDF. On the documents that need it, every word keeps its bounding box and confidence score — 1.1 million words located on their page.
Tenant isolation inside retrieval, not after it. The naive implementation searches the whole index then filters by tenant: secure, and broken. If the global top results belong to other organisations, the user gets zero results on documents they own. Recall collapses as the corpus grows, and the bug never shows up in a single-customer demo.
Structured extraction, tables included. 58 fields per file, but also 132,492 documentary requirements, 29,096 lots, 12,521 award criteria, and 228,771 price-schedule rows — the hardest part, because in a pricing grid the table structure *is* the information.
Asynchronous processing with resume. The real engineering is not the queue: it is that a document failing at page 180 restarts there instead of paying for 180 pages of OCR again.
Beyond reading — what the platform actually does
Citable retrieval is the visible part. It accounts for around ten modules out of 300. The rest is what separates a demo from a product you can invoice.
Filling official forms without templates. The documents to submit are administrative forms, and every buyer has their own variant. Rather than maintaining one template per form, the system detects the fillable areas geometrically, infers which field belongs in which area, renders the completed document, then verifies the render pixel by pixel before sending it for review. It is the one place in the product where image analysis, geometry and validation matter more than the language model.
Inferring the rule from the outcome. Award minutes publish the winner but not always the attribution method that was applied. An engine infers it: eight signals computed over admissible bids only, a decision tree, and an honest confidence score that would rather answer “undetermined” than guess. Validated on 100 real contracts: the first version cleared the confidence threshold on only 4 cases, the second on 38, with no false positives — and it surfaced 7 labelling errors in the collector that nobody had spotted.
A known, steerable cost per request. Not every question deserves the same model. An intent classifier costing a few dozen tokens, cached by question fingerprint, routes factual requests to a far cheaper tier — roughly fifty to one. Behind a switch, measured per customer, and switchable off without a redeploy if quality moves.
Guardrails, not just prompts. Retrieved passages are inspected before they reach the model: text that tries to hijack the assistant is filtered out and logged, which also reveals which uploaded documents attempt it. Alongside it, a rigour mode re-reads the answer sentence by sentence against its sources and visually flags whatever is unsupported.
Generating documents, not only reading them. Technical memoranda, brand-consistent decks with a quality score, PDF and Word exports, and the assembly of every required document into a submission-ready envelope.
The sales funnel, built inside. Prospect discovery and enrichment, a pipeline state machine that forbids illegal transitions, scoring, follow-up cadences — and role-based visibility filtering applied in the database: a rep sees only their own accounts, a manager sees their scope.
The foundation
Isolation. Row-level security in the database with its helper functions, a (feature, role) matrix in the application, and the tenant filter pushed inside the retrieval query rather than applied after it.
The hot path. Token verification goes through four cache levels — request, process, shared Redis, database as a last resort — because you cannot pay for an authentication query on every API call.
Long jobs. A queue with resume, progress pushed live to the interface, and nothing to redo when a document fails midway.
What gets invoiced. Plans, subscriptions, usage counters, quotas, credits, invoices and payments — a product that cannot count what it consumes does not know what it is worth.
What can be proven. An action audit log, application traces and logs, overrun alerts, and an internal dashboard that correlates signals from the code repository, monitoring and the database to track what is actually moving.
Results
- 84 API routers and 300 business-logic modules in production, written and operated alone — counted in the repository on 5 September 2026
- Attribution-method inference engine validated on 100 real contracts: 38 classified at high confidence after refinement, 9 of 9 “discount” cases correctly identified, no false positives, and 7 collector labelling errors surfaced
- 45,000 files processed in production, 40,800 distinct documents, 56 GB — PDF, Word, Excel, AutoCAD drawings
- Over 250,000 pages counted · median file 58 pages · largest encountered 3,592 pages across 32 documents
- 274,376 provenance citations across 58 fields, 99.95 % carrying the verbatim excerpt
- 14 % of indexed documents required OCR — 1.1 million words kept with position and confidence score
- 228,771 price-schedule rows extracted across 2,950 files
- Vector search in 1 ms server-side over 113,000 vectors, HNSW index
- A complete tender file analysed end to end in a median 2 min 11 s, for 0.22 USD
- 4 active client organisations, 3 of them on a paid plan
- Triaging a tender file: from 4 h down to 40 min — and the number of files actually reviewed went from 10 to 30 a month at constant headcount (two client interviews)
- Extraction accuracy measured against a hand-annotated reference set — 30 stratified tender files × 8 critical fields, 240 cells: 90.4% overall, 86.2% on scanned files, and 75% on the subset where the source document could be re-read, which is where every error sits. Deterministic sample, reproducible protocol and scoring code.
What I’d do differently
Build the evaluation set before the pipeline. For months, “is this better?” was judged by eye on a handful of familiar queries. The reference set exists today — 30 files stratified across native, scanned and hundred-page-plus, drawn deterministically so anyone can replay exactly the same sample. Two days of work that would have made every decision measurable instead of arguable. I’d do it first.
I underestimated tables. Flowing text settles quickly; price schedules, where the grid structure is the information, needed an entirely separate processing chain. On an unfamiliar corpus my first question is now: how much of your information lives in tables? The answer doubles or halves the estimate.
Silence should have been a product goal from the start. A system that always answers is a system that sometimes invents — the only truly serious failure mode on a contractual document. Making “not in this file” a correct, deliberate answer is as much interface work as model work, and I got to it too late.
I measured cost per request far too late. For a long time the model spend was a single line at the end of the month: no way to say which feature cost what, or which customer was profitable. Intent routing and per-request tracking were bolted on afterwards, when they should have been there from the first release — they change product decisions, not just the invoice.