Skip to content

Architecture

Last updated: 2026-06-01

A high-level view of how Nomatica AI is built. Internal names and hostnames are simplified for the public site; this is not a deployment guide.

At a glance

Nomatica AI is a multi-tenant SaaS application with three planes:

  • Edge plane — Cloudflare's global anycast network terminates TLS, runs Workers, and provides caching, KV, and D1.
  • Application plane — Python (FastAPI) and Node (Vite + React SSR) services run on containerized compute behind the edge.
  • Data plane — Managed Postgres, Redis, and object storage hold tenant content, audit logs, and user uploads.

The three planes are connected by signed JSON Web Tokens (JWTs) and, for service-to-service calls, mTLS.

High-level diagram

Edge plane

ComponentPurpose
Frontend (Pages)React 19 + Vite 6 SPA, served from Cloudflare Pages. Static assets are versioned and immutable.
Gateway WorkerFirst point of contact for all API traffic. Performs auth, rate limiting, request size cap, and routing to the correct origin.
Auth WorkerIssues and verifies JWTs. Stores refresh-token state in KV so a single login works across all subdomains.
Workers KVRead-heavy cache: feature flags, public rate-limit counters, public config.
D1 (SQLite at the edge)Small relational datasets: feature flags history, public leaderboards, search autocomplete.

The edge plane is the only plane that touches the public internet.

Application plane

The application plane runs on containerized compute (ECS on Fargate). Each service is a stateless replica behind an internal load balancer.

  • FastAPI Hub (port 3040) — the primary API. Owns user accounts, billing, tenant management, and the public REST surface.
  • Inference Worker — talks to model providers. Routes prompts to the cheapest provider that meets the latency budget.
  • Search Worker — runs the search index, manages the honeypot, and surfaces contract-hunter results.
  • Chat Worker — long-running WebSocket connections for the chat UI; multiplexes many concurrent conversations onto a single inference session where possible.

All application-plane services are written in Python 3.14 or TypeScript / Node 22.

Data plane

StoreDataNotes
PostgresTenants, users, prompts, generated outputs, billingMulti-AZ. Daily snapshot + PITR. Encrypted at rest.
RedisSessions, rate-limit counters, ephemeral cache1-day TTL on counters. No customer content.
Object storageUser uploads, model artifacts, exported reportsPer-tenant prefix. Lifecycle policy deletes tmp/ after 7 days.

Cross-cutting concerns

Authentication and authorization

  • Users authenticate with email + password (mandatory MFA), OAuth 2.0, or WebAuthn passkeys.
  • A successful login returns a short-lived (15-minute) access JWT and a long-lived (7-day) refresh JWT.
  • The refresh JWT is held in an HttpOnly cookie scoped to the apex domain, so a single login persists across *.colorfulmoves.com.
  • The access JWT carries the user's scopes (read, write, admin, agent, inference, payments).
  • Every API request goes through a centralized authorization layer. Application code never rolls its own permission checks.

Multi-tenancy

  • Every row in every multi-tenant table carries a tenant_id.
  • Postgres Row-Level Security is enabled; queries without an explicit tenant_id filter cannot return rows for other tenants.
  • A "canary" tenant exists in production with synthetic, decoy data; any cross-tenant read trips an alert within 60 seconds.

Observability

  • Structured JSON logs ship to a managed log sink.
  • Metrics (latency, error rate, queue depth, per-tenant spend) are scraped by Prometheus and visualized in Grafana.
  • Distributed tracing (OpenTelemetry) follows a request from the edge through the application and into the model provider.
  • Errors are captured by Sentry, with PII scrubbed at the source.

Deployment

  • Edge workers and Pages are deployed on every push to main (with a hold for review on Friday).
  • Application-plane services are deployed via a CI pipeline that builds the container, scans it with Trivy, signs it with cosign, and rolls it out behind a feature flag.
  • Database migrations are versioned and run in a transaction; a failed migration is automatically rolled back.

Why these choices?

Why Cloudflare for the edge?

  • Global anycast network — sub-50ms TTFB from anywhere
  • Workers KV and D1 are eventually consistent, which is fine for the cache-y workloads we put on them
  • Per-request pricing keeps our cost structure aligned with revenue (we don't pay for idle capacity)
  • The Workers runtime has a small enough surface that we can reason about its security model

Why Python for the API?

  • The team has deep Python expertise
  • The data ecosystem (pandas, polars, sqlite) is unmatched
  • The async story (asyncio + uvicorn) is finally good enough for our latency targets
  • Type-checking with mypy gives us a level of confidence comparable to TypeScript

Why a separate Chat Worker?

  • Long-lived WebSocket connections don't belong in a request/ response API
  • We can scale the chat plane independently of the REST plane
  • We can apply different rate limits and cost controls

What we don't do (and why)

  • We don't run our own data centers. The total addressable market doesn't justify the capex.
  • We don't operate a private LLM. Frontier model providers update faster than we can train; we route to them.
  • We don't store raw payment card data. Tokenization via Stripe / Square is the right tradeoff between compliance burden and integration cost.
  • We don't run our own email deliverability infrastructure. Resend does it better and cheaper than we could.

Last updated:

Released under the Apache 2.0 License.