Architecture

How the hard parts actually work.

Four decisions this document exists to defend: native AR over WebAR, two recognition pipelines instead of one, Stripe Connect with Spain's actual payment rails, and an offline-first tour runtime. Each one came out of research, not guesswork.

Stack overview
LayerChoiceWhy
Mobile clientsSwift/SwiftUI (iOS), Kotlin/Jetpack Compose (Android)Native AR needs native frameworks — ARKit and ARCore aren't cross-platform
AR — iOSARKit + RealityKit, geo-anchorsApple's geospatial anchoring for outdoor mural placement
AR — AndroidARCore + Geospatial API (VPS)Google's Visual Positioning Service, same outdoor use case
Image recognitionOn-device Core ML / ML Kit, cloud fallbackIndoor gallery pieces, where GPS/VPS can't help
BackendNode.js / NestJS, PostgreSQL + PostGISPostGIS for geofencing and stop-radius queries
CMS / adminSame backend, role-scoped APIOne source of truth for app and console
PaymentsStripe Connect (destination charges)Multi-party payout without Arteria touching bank data
AnalyticsFirebase Analytics + BigQuery exportInstall/session tracking client asked for, queryable funnel
MapsVector tiles (self-hosted) or MapboxStyleable to the design system, offline-cacheable
AR: two pipelines, one fallback ladder

The brief asks for one feature — "point your camera, reveal extra content." That's actually two different technical problems wearing the same UI.

Research finding

Niantic's 8th Wall — the standard WebAR platform — is winding down. Hosted projects go dark February 2027, and Niantic's VPS was explicitly excluded from the open-source release. Any WebAR-based plan for this product has a two-year shelf life at most. We rule it out and build native from day one.

1
Outdoor — geospatial anchor

ARCore Geospatial API (Android) / ARKit geo-anchors (iOS), backed by Google's VPS where Street View coverage exists. This is how murals and landmarks get placed precisely in the real world.

↓ no VPS coverage on this street
2
Image recognition against the city AR-target index

On-device ML first (fast, offline-capable), cloud match as fallback. This is the only path indoors — GPS and VPS don't work inside a gallery, so every indoor stop resolves this way, the Smartify/Bloomberg Connects model.

↓ recognition fails or lighting is poor
3
Manual lookup code

A short code printed on a small plaque at every stop. Always works, zero dependencies. Demoed in ar-fallback.html.

↓ device has no AR support
4
Full-screen media card

Same content, no overlay. Older devices land here directly — no dead end, ever.

The POC's ar-scan.html simulates this sequence visually — the in-app "how this actually works" panel shows the same ladder to the demo audience.

Offline strategy

Madrid's metro kills signal, and crowded plazas throttle it. Every tour is downloadable as a self-contained pack: stop content, audio, map tiles for the tour's bounding box, and the AR image-target index for that route's stops. Geofence arrival detection runs on cached GPS regardless of connectivity; only marketplace actions (checkout, reviews) require a live connection.

Payments

Stripe Connect, destination charges. The platform account collects the full payment at checkout; Stripe splits and transfers the artist's share automatically once an order is marked delivered. Artists complete Stripe's own KYC and bank-detail onboarding — Arteria never stores payment credentials or bank data, which keeps PCI scope minimal.

Spain-specific
  • 21% IVA calculated and itemized at checkout
  • SCA / PSD2 3-D Secure on card payments
  • Bizum enabled — the instant phone-linked payment method most Spanish shoppers reach for first, easy to miss in a generic proposal
  • SEPA Direct Debit as a lower-fee alternative for larger purchases
Security & data

Role-scoped API tokens for the admin console (see the RBAC matrix in users-roles.html), TLS everywhere, encrypted at rest. Location data used for geofencing is processed on-device where possible; server-side location logging is minimized to what tour-completion analytics actually needs, disclosed in-app during onboarding.