A Viaduct-powered GraphQL graph over a whole city.
One endpoint for maps, dashboards, and AI.
Cities expose data through separate systems.
Try answering: what's happening on this block?
You end up pulling permits, zoning, transit, and 311 from different places.
You stitch it together in the client.
That logic gets copied into every consumer.
Stop stitching endpoints.
Ask for the shape you need.
Resolve it in one place.
GET /parcels/:id
GET /zoning?parcel=...
GET /permits?parcel=...
GET /transit?near=...
GET /311?parcel=...
// client joins everything query BlockContext($id: ID!) {
parcel(id: $id) {
address
zoning { allowedUses }
permits { status issuedAt }
nearbyTransit { stops { name } }
complaints { count openCount }
}
} GraphQL moves the join into the server.
Same data. Same database.
What changes is where the composition happens.
Year one is easy. The schema is small.
By year two the system grows. More types. More teams.
The questions change.
These are coordination problems.
The schema just makes them visible.
One team usually takes it on.
The graph turns into a bottleneck.
The X-UrbanMesh-Profile header controls what resolves.
The query stays the same.
The response changes.
No new endpoints. No versioning.
Zoning compliance, permit history, land-use trends
Nearby trees, transit options, walkability, open 311 cases
Permit timelines, home values, disruption risk
Each domain defines its part of the system.
entities · relationships · boundaries
Relationships are defined once.
Clients do not rebuild them.
GraphQL handles querying.
Viaduct handles composition.
Ownership lives in the modules. Not in a shared layer.
Each domain defines its data and relationships. Viaduct assembles the schema at runtime.
No central team stitching everything together.
Querying was straightforward. Defining ownership was not.
Does a permit belong to :parcels or :permits?
Where is that edge defined?
Who is responsible when it breaks?
This demo uses offline data.
Production adds failure modes:
stale caches · partial failures · rate limits.
Clear ownership improves the system.
It also requires discipline.
A shared schema still creates shared responsibility.
The AI client uses the same GraphQL API as the UI.
No extra layer. No custom endpoints.
Agents use the same structure as humans.
Resolve permits, zoning, transit, 311, and census in one query.
“Five data sources. One query. No client joins.”
Planner sees zoning compliance. Resident sees walkability. Developer sees disruption risk.
Canopy coverage, green space access, commute patterns.
A city behaves like a distributed system.
This is one way to structure it.