---
title: "Why we open-sourced our AI SaaS template"
excerpt: "Most AI side-projects die in week three when the founder realizes they need to wire up Stripe, sessions, and a dozen other unsexy things. So we built the template we wished we had."
date: "2026-05-08"
author: "Kacper Włodarczyk"
authorRole: "Maintainer"
tags: ["product", "open source"]
---

The first AI app I shipped took eleven days to build. The next eleven days were spent on auth, billing,
and the magical dance between session cookies and a websocket I couldn't stop disconnecting.

That ratio — eleven days of *product* to eleven days of *plumbing* — is the average AI side-project's
cause of death. By week three, you're knee-deep in webhook signatures and you've forgotten why you
were excited.

So we built a template.

<Callout variant="tip">
  TL;DR: a `cookiecutter` project that gives you a FastAPI + Next.js app with auth, billing,
  RAG, AI agents, organizations, and observability already wired. MIT, runs on your infra,
  zero markup.
</Callout>

## What's actually in it

The hard parts come included:

- **Auth** — JWT access + refresh tokens, password hashing, optional OAuth, magic links.
- **Billing** — Stripe checkout, portal, subscriptions, credits, usage metering.
- **Teams** — organizations, invitations, role-based access control.
- **AI agents** — pick your poison (PydanticAI, LangChain, LangGraph, CrewAI, DeepAgents).
- **RAG** — Milvus / Qdrant / Chroma / pgvector, with sync sources for Drive and S3.
- **Observability** — structured logging, request IDs, Logfire / LangSmith depending on agent.

What's *not* included is opinion-shopping. We picked the boring choices. You can swap them out,
but the defaults work.

## The dial we kept turning

The hardest single decision was where to draw the abstraction line. Too thin and you're shipping
a starter without value; too thick and the template traps you in our taste.

We landed on the **repository → service → route** layering, with thin domains as flat modules and
thick ones (billing, RAG, channels) as sub-packages. Routes never know about repos. Services raise
domain exceptions, never return error codes. It's not novel — but it's consistent.

```python
# Routes never call repos directly.
@router.get("/{user_id}", response_model=UserRead)
async def get_user(user_id: UUID, service: UserSvc, user: CurrentUser) -> Any:
    return await service.get_by_id(user_id)
```

## What happens next

The template ships every two weeks. Each release: a small, real upgrade — not a marketing burst.
We dogfood it on every project we run, which keeps the surface area honest.

If you're building an AI SaaS, fork it. If you find a wart, send a PR. If you ship something cool
on top of it, we'll boost it.
