Back to Blog

Building SaaS with AI Agents: Architecture Guide

Ultrion TeamJuly 18, 202614 min read

Building SaaS with AI Agents: Architecture Guide

How to build a SaaS product powered by AI agents β€” from architecture to launch.


AI agents are transforming SaaS products. Instead of static features, your SaaS can have intelligent agents that automate tasks, answer questions, and provide personalized experiences. This guide covers the complete architecture for building AI-powered SaaS.


SaaS + AI Agent Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Frontend (React/Next.js)          β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚    β”‚       User Interface + Chat         β”‚     β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                   β–Ό                            β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚    β”‚        API Gateway (Next.js API)    β”‚     β”‚
β”‚    β”‚   Auth β€’ Rate Limit β€’ Validation    β”‚     β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                   β–Ό                            β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚    β”‚      Agent Orchestration Layer      β”‚     β”‚
β”‚    β”‚   Route β†’ Plan β†’ Execute β†’ Respond  β”‚     β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚           β–Ό          β–Ό          β–Ό              β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚    β”‚   LLM    β”‚β”‚   MCP    β”‚β”‚  Memory  β”‚      β”‚
β”‚    β”‚  Router  β”‚β”‚  Tools   β”‚β”‚  Store   β”‚      β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    PostgreSQL β€’ Redis β€’ Pinecone β€’ S3         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Components

1. Frontend (Next.js)

// app/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

export async function POST(request: Request) {
  const { messages, userId } = await request.json();

  const result = streamText({
    model: openai("gpt-4o"),
    messages,
    tools: await getUserTools(userId),
    system: await getSystemPrompt(userId),
    onFinish: async (result) => {
      await saveConversation(userId, messages, result);
    },
  });

  return result.toDataStreamResponse();
}

2. Agent Orchestration

class SaaSAgent {
  constructor(private userId: string, private tier: string) {}

  async process(message: string): AsyncGenerator<string> {
    // 1. Check permissions
    const perms = await this.getPermissions(this.userId, this.tier);

    // 2. Get user context
    const context = await this.getUserContext(this.userId);

    // 3. Select appropriate tools
    const tools = await this.selectTools(message, perms);

    // 4. Execute with streaming
    const stream = await this.llm.stream({
      model: this.selectModel(this.tier),
      messages: [
        { role: "system", content: this.systemPrompt(context) },
        ...context.history,
        { role: "user", content: message },
      ],
      tools,
    });

    // 5. Handle tool calls
    for await (const part of stream) {
      if (part.type === "tool_call") {
        yield* this.handleToolCall(part, perms);
      } else {
        yield part.text;
      }
    }
  }
}

3. Multi-Tenancy

class MultiTenantAgent:
    async def get_user_context(self, user_id):
        """Load user-specific data and configuration."""
        user = await self.db.get_user(user_id)
        
        return {
            "profile": user.profile,
            "preferences": user.preferences,
            "organization": user.org,
            "tier": user.subscription_tier,
            "usage": await self.get_usage(user_id),
            "limits": self.get_limits(user.subscription_tier),
            "history": await self.load_conversation_history(user_id),
            "custom_tools": await self.get_user_tools(user_id),
        }

    def get_limits(self, tier):
        limits = {
            "free": {"messages_per_day": 20, "tools": ["basic"], "model": "gpt-4o-mini"},
            "pro": {"messages_per_day": 500, "tools": ["basic", "advanced"], "model": "gpt-4o"},
            "enterprise": {"messages_per_day": float("inf"), "tools": "all", "model": "gpt-4o"},
        }
        return limits.get(tier, limits["free"])

4. Usage Tracking and Billing

class UsageTracker {
  async record(userId: string, usage: TokenUsage) {
    await this.db.insert("usage", {
      userId,
      date: new Date(),
      inputTokens: usage.input,
      outputTokens: usage.output,
      toolCalls: usage.toolCalls,
      cost: this.calculateCost(usage),
    });

    // Check limits
    const dailyUsage = await this.getDailyUsage(userId);
    const limits = await this.getUserLimits(userId);

    if (dailyUsage.messages >= limits.messagesPerDay) {
      throw new UsageLimitExceeded("Daily message limit reached");
    }

    // Stripe integration for overages
    if (dailyUsage.cost > limits.budgetAmount) {
      await this.chargeOverage(userId, dailyUsage.cost - limits.budgetAmount);
    }
  }
}

Database Schema

-- Users and organizations
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT UNIQUE NOT NULL,
    org_id UUID REFERENCES organizations(id),
    subscription_tier TEXT DEFAULT 'free',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Conversations
CREATE TABLE conversations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    title TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Messages
CREATE TABLE messages (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    conversation_id UUID REFERENCES conversations(id),
    role TEXT NOT NULL,
    content TEXT NOT NULL,
    tool_calls JSONB,
    tokens_used INTEGER,
    cost_eur DECIMAL(10, 4),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Usage tracking
CREATE TABLE usage (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    date DATE NOT NULL,
    message_count INTEGER DEFAULT 0,
    input_tokens INTEGER DEFAULT 0,
    output_tokens INTEGER DEFAULT 0,
    tool_calls INTEGER DEFAULT 0,
    cost_eur DECIMAL(10, 4) DEFAULT 0,
    UNIQUE(user_id, date)
);

-- Custom tools per user
CREATE TABLE user_tools (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    name TEXT NOT NULL,
    config JSONB NOT NULL,
    enabled BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Subscription Integration (Stripe)

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

const PLANS = {
  free: { priceId: null, limits: { messages: 20, model: "gpt-4o-mini" } },
  pro: { priceId: "price_pro123", limits: { messages: 500, model: "gpt-4o" } },
  enterprise: { priceId: "price_ent456", limits: { messages: -1, model: "gpt-4o" } },
};

export async function handleSubscription(user, newTier) {
  const plan = PLANS[newTier];

  if (plan.priceId) {
    const session = await stripe.checkout.sessions.create({
      customer: user.stripeCustomerId,
      payment_method_types: ["card"],
      line_items: [{ price: plan.priceId, quantity: 1 }],
      mode: "subscription",
      success_url: `${process.env.APP_URL}/settings?upgraded=true`,
      cancel_url: `${process.env.APP_URL}/settings`,
    });
    return { checkoutUrl: session.url };
  }

  await db.update("users", user.id, { subscription_tier: newTier });
  return { upgraded: true };
}

Security for Multi-Tenant

class TenantSecurity:
    async def check_access(self, user_id, resource):
        """Ensure user can only access their own data."""
        resource_owner = await self.get_owner(resource)
        if resource_owner != user_id:
            if not await self.same_org(user_id, resource_owner):
                raise PermissionError("Access denied")

    async def get_tools(self, user_id):
        """Get user-specific tools with isolation."""
        user = await self.db.get_user(user_id)
        
        # User can only access their org's tools + marketplace tools
        tools = []
        
        # Org-specific tools (private)
        tools.extend(await self.get_org_tools(user.org_id))
        
        # User's custom tools
        tools.extend(await self.get_user_tools(user_id))
        
        # Marketplace tools (shared, but usage tracked per user)
        tools.extend(await self.get_marketplace_tools(user.tier))
        
        return tools

Launch Checklist

MVP Features

  • User authentication (email, Google)
  • Chat interface with streaming
  • 3-5 core tools/integrations
  • Usage tracking and limits
  • Stripe subscription integration
  • Basic admin dashboard
  • Error handling and fallbacks
  • Mobile-responsive design

Launch Week

  • Product Hunt launch
  • Share in communities (Reddit, Discord, Twitter)
  • Demo video on landing page
  • Early user onboarding flow
  • Feedback collection system
  • Analytics tracking (PostHog, Mixpanel)
  • Error monitoring (Sentry)

Post-Launch

  • Weekly feature releases
  • User feedback β†’ feature prioritization
  • MRR tracking dashboard
  • Churn analysis and prevention
  • Expansion to new use cases

Cost Structure

Per user costs (Pro tier, €29/month):
- LLM API: €2-5/month per active user
- Infrastructure: €1-2/month per user
- Vector DB: €0.50/month per user
- Total: €3.50-7.50/month per user

Gross margin: 75-88%

At 100 Pro users:
- Revenue: €2,900/month
- Costs: €500-750/month
- Profit: €2,150-2,400/month

Conclusion

Building a SaaS with AI agents is one of the highest-margin, fastest-growing business models. With modern tools (Next.js, Mastra, MCP, Stripe), you can launch in weeks, not months.

The key: solve one specific problem exceptionally well with AI, price it as a subscription, and scale through word-of-mouth and content marketing.


Learn More

Launch your AI SaaS with skills from SkillExchange.

Newsletter

Enjoying this article?

Get weekly insights on building and selling AI skills, MCP tools, and creator economics. Join 2,000+ AI builders and creators.

No spam. Unsubscribe anytime.

Related Articles

Ready to try AI skills?

Browse the marketplace and discover skills for your AI agents.

Browse Skills