Architecture
How the Swayzio API is actually served. You don’t need this to call the API — but
it explains the x-swayzio-core-runtime header, the native→proxy notes, and
where the asynchronous work happens.
Two implementations, one contract
The /api/swayzio/v1 surface has two implementations behind a single
contract:
- Native (Next.js route handlers on Vercel) — the forward path. Reads/writes Neon Postgres and signs GCS URLs directly. This is what serves production today.
- Fastify Core API (
services/api) — the compatibility/fallback service. Any/v1path not yet ported natively is transparently proxied here.
A generic catch‑all (/api/swayzio/[...path]) forwards anything without a
more‑specific native handler, so every Fastify route is reachable. The decision
is made per request:
SWAYZIO_CORE_API_RUNTIME === "vercel"→ serve natively.- A native handler that returns “no match” → proxy fallback (when
SWAYZIO_PROXY_FALLBACK_ENABLED, default on) → else501. - A
*_database_unavailableerror → proxy fallback → else503.
The response header x-swayzio-core-runtime (vercel | proxy) tells you which
path served a given call, and x-swayzio-proxy-fallback: 1 flags a fallback.
Native‑only surfaces never proxy: /v1/analytics*, /v1/catalog-analysis,
/v1/webhooks/*, /v1/media-intelligence/callback, and all /v1/internal/*.
The Stripe webhook returns 503 native_runtime_required if not running on Vercel.
Both implementations share the same auth model (requireSwayzioActor /
requireActor), the same @swayzio/domain Zod schemas, and the same owner‑scoping
discipline. Parity is intentionally incomplete — the proxy covers the remainder.
The platform around the API
| System | Role |
|---|---|
| Vercel / Next.js | Hosts the web app + native Core API route handlers |
Fastify Core API (services/api) | Compatibility/fallback implementation of /v1 |
| Clerk | Authentication; issues the session JWTs the API verifies |
| Neon (Postgres) | Primary datastore (owner‑scoped rows, pgvector search) |
| Google Cloud Storage | Audio + media objects (served via short‑lived signed URLs) |
| Modal | GPU media intelligence — embeddings, tags, stems, lyrics |
Worker (services/worker, Cloud Run) | Upload finalize → readiness → drains; dispatches to Modal |
| Trigger.dev | Async workflows (upload processing, catalog drains) |
| Stripe | Billing & subscriptions |
| Dub | Link shortening + click/conversion analytics |
| Resend | Transactional email (pack invites) |
| Sentry | Errors + tracing |
Request lifecycle (an authenticated read)
Client ──Bearer JWT──▶ Vercel route handler
│ requireSwayzioActor → { ownerId, … }
│ validate query (Zod)
├─ native: repository (WHERE owner_id = $ownerId) → Neon
│ → redact DTO (strip ownerId / storageUri)
└─ proxy: forward to Fastify Core API (same auth/headers)
◀── JSON + x-swayzio-core-runtime headerMedia processing (asynchronous)
Uploads don’t block on analysis. The flow:
POST /v1/uploads ─▶ signed PUT ─▶ POST /finalize
│ │
▼ ▼
GCS object worker: probe + waveform (fast readiness)
│ │
└─ GCS finalize push ─▶ worker ─▶ Modal (tags / stems / lyrics, GPU)
│
POST /v1/media-intelligence/callback (signed)
▼
persist lanes; queue embedding refreshEmbeddings (MuQ / MuLan) power audio search; they’re computed by a separate Modal app and drained by the worker. See Internal & Services.