All Works
Founder and lead engineer: product, design, engineering and sales/2026 – present

Viato

A multi-tenant ERP for travel agencies

Travel agencies run on WhatsApp, spreadsheets and a mid-office tool that talks to neither. Viato gives an agency one system from enquiry to invoice, and gives every agency its own isolated infrastructure, built by a provisioning pipeline instead of by hand.

Live · viato.pro For: Travel agencies (my own product)
18
modules on one record, enquiry to itinerary
8
automated, resumable provisioning steps per agency
314
passing tests across workspace and console
1
database per agency, nothing shared
Viato's home page, showing a quote with supplier cost and client price on every line

// The Problem

Three things break when an agency runs on chat and spreadsheets. Margin is discovered after the trip instead of before the quote goes out. Revenue can't be traced back to the channel that produced the enquiry, so nobody knows which channel pays. And the back office (who worked, who approved what) lives apart from the money.

The customers are small agencies: price-sensitive, working on phones, issuing Indian GST invoices, and nervous about putting their client list in somebody else's shared database.

// How It's Built

  1. Operator console

    Pipeline, agencies, billing and team access at viato.pro/admin

  2. Provisioning job

    8 steps; each checks what exists, so re-running resumes

    • Google Cloud project per agency

      Firestore in Mumbai, email sign-in, security rules, two narrow service accounts

    • Vercel project per agency

      Same workspace code, the agency's own config, <agency>.viato.pro over HTTPS

  3. Agency workspace

    18 modules, installable, works offline

A won deal becomes a live, isolated workspace without anyone clicking through a cloud console.

// Decisions & Trade-offs

  • 01

    A project per agency, not tenant IDs in one database

    A mistake in one workspace's security rules can't expose another agency's bookings, and an agency that leaves takes its whole database with it rather than a filtered export. The cost is many deployments to keep in step, so every agency deploys from the same repository and provisioning is safe to re-run.

  • 02

    Provisioning as code that is safe to re-run

    Every step checks what already exists and only does what is missing: it never creates a second project or mints a second key. The decisions (names, rules, roles, environment) live in a side-effect-free module with unit tests; the script itself is only the sequence of API calls.

  • 03

    Least privilege for every service account

    The console and each workspace server get exactly two roles (auth admin and datastore user), never Owner or Editor. That is enough to issue logins and read roles, and nothing more.

  • 04

    The quote is where margin gets decided

    Every quote line carries cost and selling price side by side, so profit is checked before the quote is sent. Bookings keep the quote and lead they came from, which is what lets revenue be traced back to its channel.

  • 05

    Storage behind an interface

    Modules talk to a typed Collection interface with Firestore and local-storage adapters. The same workspace runs against a real database in production and with no backend at all as a demo.

  • 06

    Permissions below the module level

    A role can be granted a whole module, one desk inside it, or a single permission such as overriding attendance. The UI hides what a person can't use; the database rules decide what they can write.

// Problems Worth Telling

  • 01

    Production builds that silently didn't happen

    A build-skip rule meant to ignore preview branches also skipped real production builds from main, leaving an agency on an old version with no error anywhere. Provisioning now clears that setting on every run, and the reason is written next to the code.

  • 02

    An LLM that ran out of room before answering

    The daily prospect-scoring job asks for JSON. The model sometimes spent its token budget reasoning and returned nothing, flagged only by finish_reason "length". The job now retries once at a lower temperature, caps model calls per run and stops after consecutive failures instead of burning through them.

// Quality

  • 220 tests on the workspace and 94 on the console, run with node:test: money math, invoice and ticket PDFs, geofencing, grants, CSV and e-ticket import, provisioning decisions, billing and pipeline ranking.
  • Pure functions for anything that decides money or access, so they can be tested without a network.
  • The console's "Today" screen is a pure ranking function (warnings first, then people waiting, then routine), tested like the rest.

// Where It Stands

  • Live at viato.pro and sold to travel agencies. Each agency runs on infrastructure provisioned for it alone.
  • The operator console runs the whole business: pipeline, onboarding links, billing and per-person access codes.

NextLetting an AI assistant work inside the ERP under the same permissions and approval rules as staff, with an evaluation suite to prove it behaves.

// Stack

Next.jsReact 19TypeScriptFirebase Auth and FirestoreGoogle Cloud APIsVercel APIPWAnode:test
Next case studyTravel group platform

Building something similar?

Let's talk