Architecture
Repository layout
apps/api Fastify service: REST (/v1), OAuth (/oauth), MCP (/mcp), admin API (/admin/api), webhooks, tasks, docs + admin static
apps/admin React admin portal (built into apps/api/public/admin)
apps/docs This documentation (built into apps/api/public/docs)
packages/core Domain: catalog, pricing, quotes/checkouts/orders state machine, OAuth server, messaging, payments, tasks, admin
packages/db Drizzle schema + migrations (Postgres in the cloud, PGlite in tests/dev)
packages/snappy-client Typed Snappy Public API v3 client, fixtures, contract test against the vendored OpenAPI spec
infra/terraform bootstrap (projects, state, WIF) · modules/platform · envs/sandbox · envs/prod
.github/workflows ci (typecheck, tests, build, tofu validate) · deploy (sandbox on main, prod on approval) · infra (plan on PR, apply on merge)
Runtime (per environment)
┌────────────────────────────── GCP project snappy-agents-<env> ──────────────────────────────┐
Internet ─▶ Global HTTPS LB (managed cert, Cloud Armor) ─┬─▶ Cloud Run `agents-api` │
/admin/* ─▶ Identity-Aware Proxy ─┘ (same service; IAP JWT verified in-app) │
│ Direct VPC egress │
┌──────────────┼───────────────┐ │
▼ ▼ ▼ │
Cloud SQL Postgres Cloud Tasks Secret Manager │
(private IP, VPC) (push → /internal/tasks/*, OIDC) │
▲ │
Cloud Scheduler ─▶ /internal/tasks/expire-checkouts (5 min) │
Cloud NAT (static IP) ◀─ outbound to api.snappy.com, api.stripe.com, sendgrid, twilio │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
- One container image serves everything;
/adminand/docsare static files baked in at build time. - The database is reachable only through the VPC; the API is the only path in, as required.
- Background work (placing the order after payment, notifications, callbacks, syncs) goes through Cloud Tasks so a crash mid-task is retried with the same idempotency keys. Locally and in tests an in-process queue runs the same handlers.
- GitHub Actions deploys with Workload Identity Federation (no service-account keys). Terraform plan uses a read-only identity on pull requests; apply runs only from
main(sandbox) or an approvedprodenvironment.
Why these choices
| Choice | Reason |
|---|---|
| Opaque tokens instead of JWT | One resource server; instant revocation; nothing to leak from a decoded token |
| Stripe Checkout (hosted) | No PCI scope, no card data in the agent or the platform, Muse's "payment link" model |
| Quote → checkout split | The user sees and approves the exact amount; the price cannot move between consent and charge |
Snappy idempotencyKey = checkout id + item index |
Payment webhooks and task retries can never double-order, even for carts |
| Credentials via the admin portal into Secret Manager | Operators rotate keys without a deploy; nothing secret in Terraform, CI or the database |
| IAP in front of /admin | Google-managed sign-in restricted to the organisation; static admin assets never reach anonymous users |
| PGlite in tests | Full Postgres semantics (unique indexes, JSON) in CI without a service container |
| Vendored Snappy OpenAPI + contract test | Fixture mode stays honest when Snappy's contract changes |
Snappy Agents · agents.snappy.com · support@snappy.com