Architecture overview
ContentsVision is a two-tier web application with an asynchronous AI processing pipeline. This page is the map: the stack, the repo layout, the runtime topology, and how requests and jobs flow. The following pages drill into each layer.
Tech stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS, Framer Motion, TanStack Query and Virtual |
| Backend | Python 3.11, FastAPI, SQLAlchemy 2 (async), Pydantic v2 |
| Database | SQLite via aiosqlite (async) |
| Media | FFmpeg (via ffmpeg-python), Pillow, OpenCV (headless), PyMuPDF (reading photos out of PDFs) |
| AI | OpenRouter (Claude vision and analysis), Deepgram Nova-2 (transcription) |
| Auth | AdjustSquare SSO, JSON Web Tokens (python-jose, HS256) |
| Analytics | PostHog (product analytics + session replay), optional, off by default |
| Exports | pandas + openpyxl (Excel), hand-rolled CSV |
| Deployment | Docker Compose, nginx, Cloudflare Tunnel |
Repository layout
contentsvision/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI app, router registration, CORS, /health
│ │ ├── api/routes/ # HTTP endpoints (auth, claims, rooms, upload, jobs, inventory, settings, activity, billing)
│ │ ├── core/ # config, database, security (JWT), auth_deps, actor, observability
│ │ ├── models/ # SQLAlchemy ORM models (claim, room, job, inventory, team, user, activity)
│ │ ├── schemas/ # Pydantic request/response shapes
│ │ └── services/ # pipeline, video_processor, audio_processor, claude_analyzer,
│ │ # numbering, auto_approve, prompt_settings, billing, activity, adjust_square
│ ├── tests/ # pytest suite (offline, in-memory DB)
│ └── requirements.txt
├── frontend/
│ └── src/
│ ├── app/ # Next.js App Router pages
│ ├── components/ # React components
│ ├── lib/ # api client, auth, settings contexts, helpers
│ └── types/ # TypeScript interfaces
├── docs/ # this handbook (Docusaurus)
├── nginx/nginx.conf
├── docker-compose.yml # local
└── docker-compose.prod.yml # production (nginx + Cloudflare Tunnel)
Runtime topology
In production, four containers run behind a Cloudflare Tunnel. No ports are exposed to the public internet; all ingress arrives through the tunnel into nginx.
The same logical split exists in local development, just without nginx or the tunnel: the Next dev server proxies /api to the uvicorn backend. See local development.
Backend layering
The backend follows a conventional layered shape. Requests flow down; the pipeline runs to the side, in the worker container in production.
| Layer | Responsibility | Key files |
|---|---|---|
| Routes | HTTP surface, validation, auth, response shaping | app/api/routes/* |
| Auth | Validate JWT, resolve current user and team, build the Actor | core/auth_deps.py, core/actor.py, core/security.py |
| Services | Business logic: the pipeline, billing, numbering, auto-approve, Adjust Square client | app/services/* |
| Models | SQLAlchemy ORM tables | app/models/* |
| Schemas | Pydantic request/response contracts | app/schemas/* |
| Core | Config, database engine, observability | core/config.py, core/database.py, core/observability.py |
Two request paths: synchronous API vs background pipeline
ContentsVision has two distinct execution paths, and understanding the split explains most of the design.
Synchronous API calls
Standard REST. The route reads or writes the database and returns immediately. Most endpoints (claims, rooms, inventory edits, settings) work this way.
Background pipeline jobs
Uploading media returns right away, but the heavy AI work never runs inside the request. Every pipeline start goes through app/core/queue.enqueue_job: in production (REDIS_URL set) the route pushes the job id onto a Redis queue and a separate worker container (app/worker.py, ARQ) runs the pipeline against its own database session; in local development (no REDIS_URL) the same call schedules a FastAPI background task in the web server instead. The frontend then watches progress over Server-Sent Events, which the backend serves from the job row plus the log lines the worker mirrors through Redis (see observability).
This is why a long video does not block the HTTP request, and why one user's processing never slows another user's pages: the web server only does front-desk work. The Adjust Square submission still hands its slow photo uploads to an in-process background task (network waiting, no CPU; see Adjust Square).
Where to go next
- The AI pipeline: the most important subsystem, in full.
- Data model: the tables and their relationships.
- Backend API: the endpoint reference.
- Frontend: the Next.js app.