Adding a new city to SMOVECITY is a process we've now done 123 times. Porto, last week, was the 124th. By the time you read this it's already live on the application — bike-share, scooter-share, the STCP bus network, the Metro do Porto, and door-to-door multi-modal routing across all of them. This is how the eight working days went.
Day one: the discovery call
The first thing we do is not a technical thing. It's a call with whoever in the city is responsible for the modal we want to start with. In Porto's case that meant separate conversations with STCP (the bus operator), Metro do Porto, the dockless bike operator, and the largest scooter operator. We ask three questions:
- Do you publish live data? Where?
- Do you allow third-party aggregators on it?
- Who owns the API contract on your side?
If the answers are yes/yes/here-is-an-email, the rest of the integration is a question of engineering effort. If any of the three are no, you're looking at a two-to-six-month policy conversation before any code runs. Porto was yes/yes/yes across the four operators — which is unusually fast.
Day two: the feeds
STCP publishes a feed that follows the standard transit data spec. So does Metro do Porto. The two shared-mobility operators publish data in a slightly different shape but it's well-known enough that our adapter library has had support for it since 2023.
Where the feeds lie is in the metadata — particularly stop names. "Casa da Música" appears as Casa da Música in the transit feed, "Casa Da Música" in one of the bike-share docks, "casa-da-musica" in the metro polygon. The first day of any integration is mostly normalising names and coordinates so all four data sources point at the same physical place.
Days three and four: routing
Once the live feeds are in, the routing engine has to know how to traverse Porto. We use a graph built from the OpenStreetMap network, augmented with the public transit graph from the feeds, augmented with the bike-share dock positions. Tuning the graph for a new city is mostly about transfer penalties — how much "time" you charge a route for switching modes. Porto's metro stations are deep, so the metro-to-bus transfer penalty is higher than it would be in, say, Brussels.
Day five: the app and the website
Adding a city to the app and the marketing site is almost entirely a data change. The city enters the cities.ts seed (lat/lng, modes available, a friendly name), it gets a pin on the coverage globe, it gets a row in the operators table on the trip detail screen.
The one thing that's not data is the AI's prior over which mode to suggest. We add a per-city hint that tells the model "for trips under 4km in Porto, bikes are the default; over 4km, metro." That hint comes from the local operations lead, not from data.
Days six through eight: the manual QA
This is where almost all the actual work lives. Two people on the team spend three days physically in the city, running the application against reality. They take 30+ multi-modal trips, screenshot anything that's wrong, and we fix in real time. Stop names that don't match. Buses that show up before they actually do. Bike-share docks that the feed says are full but in reality have one bike left. Walking directions that route you through a closed pedestrian alley.
Day eight, end of day, we sign off the city and flip the feature flag. The next morning Porto is live on the live map.
What's hard about doing this every week
The work is repetitive. The hard part is making sure it stays high-quality at the 124th city the same way it was at the third. We've automated the boring 70% (feed adapters, graph builds, city-list updates, screenshot diffing). The remaining 30% — the policy calls, the field QA, the per-city AI hint — is still hand-done. And probably always will be.