Ride price comparison app for Buenos Aires. Compare Uber, Cabify, and DiDi in one place.
In Buenos Aires, ride prices vary wildly between apps depending on demand, time of day, and promotions. Some cities have ride options integrated into Google Maps or third-party aggregators, but that doesn't exist here. So I built one.
FairFare lets you compare Uber, Cabify, and DiDi side by side. Uber is the easiest to scrape since it has a web app—it's stable and serves as the reliable fallback. Cabify and DiDi required a more laborious approach: since they only offer mobile apps, I had to automate an Android emulator via Appium. It's not elegant, but it works and proves the concept.
Warning
This project scrapes third-party services (Uber, Cabify, DiDi) for personal use. The maintainers are not affiliated with these companies and are not responsible for any terms of service violations. Use at your own risk.
- Multi-provider comparison: Uber (web), Cabify/DiDi (Android emulator)
- ARS pricing: normalized to Argentine pesos
- Sorting: cheapest vs fastest
- Deep links: open the provider app (or web fallback)
- Resilience tooling: per-provider status + timeouts + scraper artifacts (for debugging)
- Frontend (
src/): React + Vite app that calls the API and renders results. - Backend (
server/): Express API that:- geocodes addresses server-side (Nominatim + cache + throttle)
- scrapes providers:
- Uber: Puppeteer against
m.uber.com(requires login) - Cabify/DiDi: Appium/WebDriverIO against an Android emulator (requires being logged in on the emulator)
- Uber: Puppeteer against
- Shared types (
shared/): used by both frontend and backend to avoid contract drift.
- Node.js 20+ and npm
- macOS/Linux (Windows is untested)
- Puppeteer downloads Chromium on install (or set
PUPPETEER_SKIP_DOWNLOAD=1and provide a Chrome binary) - For Cabify/DiDi: an Android emulator + Appium running locally (see “Debugging scrapers”)
Before testing the full app, make sure all services are running:
| Service | Command / How to Start | Port | Required For |
|---|---|---|---|
| Backend | npm run dev --prefix server |
3001 | All features |
| Frontend | npm run dev |
5173 | Web UI |
| Android Emulator | Start from Android Studio | — | Cabify, DiDi |
| Appium Server | appium |
4723 | Cabify, DiDi |
Tip
Uber only? You just need Backend + Frontend.
Full comparison (Uber + Cabify + DiDi)? You need all four services running.
- Open
http://localhost:5173— you should see the FairFare UI - Check
http://localhost:3001/api/health— should return{ status: "ok" } - Click "Verificar conexión" (Verify connection) in the UI to confirm provider connections
# Install dependencies
npm install
npm install --prefix server
# Configure environment variables (optional)
cp .env.example .env
cp server/.env.example server/.env
# Run in development (two terminals)
# Terminal 1: API
npm run dev --prefix server
# Terminal 2: Web
npm run dev
# Or run both with one command
npm run dev:all
# Run tests
npm run test # watch mode
npm run test:run # single run
npm run test:coverage
# Build for production
npm run buildBase URL: http://localhost:3001 (default)
- GET
/api/health: health check - GET
/api/geocode?q=...: geocode an address (server cached + throttled) - GET
/api/places?q=...: address autocomplete (server cached + throttled) - GET
/api/auth/status: returns{ isLoggedIn, hasCookies }for Uber - POST
/api/auth/verify-apps: verifies Cabify/DiDi login status on the emulator (Appium; expensive) - POST
/api/auth/uber/login: interactive login to generate cookies (rate-limited) - POST
/api/auth/uber/refresh: refresh Uber session from cookies (rate-limited) - POST
/api/rides: fetch ride options- request body:
{ origin: { address, lat, lng }, destination: { address, lat, lng } } - response:
{ options, meta }wheremetaincludes:needsLoginProviders?: Provider[](e.g.["uber"]or["cabify"])providerStatus?: ProviderStatus[](per-provider outcome, duration, error/reason)
- request body:
- The frontend sends a session header:
x-fairfare-session. - Single-user mode (default): session id is
"default"so Uber cookies are stored at:server/uber_cookies.json(gitignored)
- Multi-user mode (optional): enable
VITE_MULTI_USER_SESSIONS=true(or setVITE_SESSION_ID=...), and Uber cookies are stored under:server/.sessions/<hash>/uber_cookies.json(gitignored)
See:
docs/ENVIRONMENT.mddocs/README.md(docs index)
See:
docs/DEBUGGING.md- API contract:
docs/API.md - Appium setup:
docs/APPIUM_SETUP.md - Troubleshooting:
docs/TROUBLESHOOTING.md
- API contract:
If you plan to run the backend beyond localhost, read:
- React 19 + TypeScript
- Vite 7
- Express + Puppeteer + WebDriverIO (Appium)
- Pino logging
- Leaflet + OpenStreetMap (maps)
Note
Maps use Leaflet/OpenStreetMap to keep things open source and simple. If you prefer Google Maps, swapping it in is straightforward. Just bring your own API key.
- Do not commit:
server/uber_cookies.jsonserver/.sessions/server/.artifacts/- any
.envfiles
See CONTRIBUTING.md.
The UI is in Spanish (for Buenos Aires users). Documentation is in English.
MIT. See LICENSE.





