Idea β Profitable SaaS. Wantrepreneur β Entrepreneur.
import { SaaS } from 'saaskit'
export default () => (
<SaaS name="YourIdea">
{$ => {
$.nouns({ Customer: { name: 'string', email: 'string' } })
$.verbs({ Customer: { notify: $ => $.api.emails.send({ to: $.record.email }) } })
}}
</SaaS>
)
// That's it. You now have:
// β App β yourapp.saas.dev (React admin dashboard)
// β API β api.yourapp.saas.dev (REST + GraphQL + WebSocket)
// β Site β yourapp.io.sb (Landing page)
// β Docs β docs.yourapp.io.sb (API reference)
// β CLI β npx yourapp customers notify <id>
// β MCP β Works with Claude, ChatGPT, Cursor
// β SDK β npm install yourapp (TypeScript client)
// β Zapier β Your app is an integration
// β Stripe β Subscriptions flowingYou have an idea. You've probably had it for months.
But between your idea and a running business stands:
β‘ Authentication β‘ Landing page β‘ Legal entity
β‘ Authorization β‘ Documentation β‘ Business banking
β‘ User management β‘ API reference β‘ Compliance certs
β‘ Billing integration β‘ SDK generation β‘ Marketing
β‘ Subscription logic β‘ CLI tooling β‘ Sales
β‘ Admin dashboard β‘ Webhook system β‘ Support
β‘ CRUD interfaces β‘ Rate limiting β‘ Onboarding
β‘ Audit logging β‘ Error handling β‘ Analytics
That's 6-12 months of work. And you haven't even started on your actual idea.
SaaSkit deletes all of it.
Define your business in Nouns and Verbs. Everything else derives.
$.nouns({
Customer: {
name: 'string',
email: 'string',
plan: '->Plan', // Customer owns link to Plan
orders: ['<-Order'], // Orders link back to Customer
},
Order: {
items: ['->Product'],
total: 'number',
status: 'pending | paid | shipped | delivered',
customer: '->Customer',
},
Product: {
name: 'string',
price: 'number',
inventory: 'number',
},
})$.verbs({
Order: {
create: $ => $.db.Order.create($.input),
pay: async $ => {
await $.api.stripe.charges.create({
amount: $.record.total,
customer: $.record.customer.stripeId,
})
return $.db.Order.update($.id, { status: 'paid' })
},
ship: async $ => {
const label = await $.api.shippo.labels.create({
to: $.record.customer.address,
parcel: $.record.items,
})
await $.api.emails.send({
to: $.record.customer.email,
subject: 'Your order has shipped!',
body: `Track it here: ${label.tracking_url}`,
})
return $.db.Order.update($.id, { status: 'shipped', trackingUrl: label.tracking_url })
},
},
Product: {
restock: $ => $.db.Product.update($.id, {
inventory: $.record.inventory + $.input.quantity
}),
},
})$.on.Order.paid(async (order, $) => {
await $.api.slack.send('#sales', `New order: $${order.total}`)
await $.db.Metric.increment('revenue', order.total)
})
$.on.Product.updated(async (product, $) => {
if (product.inventory < 10) {
await $.api.emails.send({
to: 'inventory@yourcompany.com',
subject: `Low stock: ${product.name}`,
})
}
})$.every.day.at6am(async $ => {
const unpaid = await $.db.Order.find({
status: 'pending',
createdAt: { $lt: $.time.daysAgo(3) },
})
for (const order of unpaid) {
await $.api.emails.send({
to: order.customer.email,
subject: 'Complete your order',
body: await $.ai`Write a friendly reminder about their pending order`,
})
}
})
$.every.Monday.at9am(async $ => {
const revenue = await $.db.Metric.sum('revenue', { period: 'week' })
await $.api.slack.send('#metrics', `Weekly revenue: $${revenue}`)
})The $ is your handle to everything. No configuration. No setup. Just use it.
interface $ {
// Database
db: {
[Noun]: {
create, get, update, delete, list, find,
search, // Full-text search
semanticSearch, // AI-powered semantic search
}
}
// AI
ai`prompt` // AI-generated content
agents: { // Named AI workers
[name]: { run }
}
// Human-in-the-loop
human: {
approve, // Requires human approval
ask, // Ask human a question
review, // Human reviews content
}
// Workflows
send(event, data) // Fire and forget (durable)
do(action, data) // Wait for result (durable)
// 9000+ Integrations
api: {
emails, // Emails.do
texts, // Texts.do
calls, // Calls.do
stripe, // Payments.do
slack, // via APIs.do
hubspot, // via APIs.do
// ...etc
}
// Context
input // Verb input payload
record // Current noun instance
id // Record ID
user // Authenticated user
org // Current organization
env // Environment variables
time // Time helpers
}$.verbs({
BlogPost: {
draft: async $ => {
const content = await $.ai`
Write a blog post about: ${$.input.topic}
Tone: Professional but approachable
Length: 800-1000 words
Include: Introduction, 3 main points, conclusion
`
return $.db.BlogPost.create({ ...$.input, content, status: 'draft' })
},
},
Customer: {
summarize: $ => $.ai`
Summarize this customer's activity:
Orders: ${$.record.orders.length}
Total spent: $${$.record.orders.reduce((s, o) => s + o.total, 0)}
Last order: ${$.record.orders[0]?.createdAt}
Write 2-3 sentences highlighting key insights.
`,
},
})$.agent('support', {
instructions: `
You are a helpful customer support agent.
Be friendly and professional.
If you can't resolve an issue, escalate to a human.
`,
tools: ['getCustomer', 'getOrder', 'refundOrder', 'escalate'],
})
$.verbs({
Ticket: {
resolve: $ => $.agents.support.run({
ticket: $.record,
customer: $.record.customer,
history: $.record.messages,
}),
},
})$.verbs({
Refund: {
process: async $ => {
// Large refunds require approval
if ($.record.amount > 500) {
const approved = await $.human.approve(
`Approve refund of $${$.record.amount} for ${$.record.customer.name}?`
)
if (!approved) return $.db.Refund.update($.id, { status: 'rejected' })
}
await $.api.stripe.refunds.create({ charge: $.record.chargeId })
return $.db.Refund.update($.id, { status: 'processed' })
},
},
})From <SaaS nouns={} verbs={} />, everything derives:
Full React admin dashboard. Automatically.
yourapp.saas.dev
βββ /dashboard β Overview with metrics
βββ /customers β CRUD list/create/edit/show
βββ /orders β CRUD + status workflow + actions
βββ /products β CRUD + inventory management
βββ /settings β Organization settings
βββ /team β User management
βββ /billing β Stripe customer portal
βββ /api-keys β Key management
βββ /webhooks β Webhook configuration
REST + GraphQL + WebSocket. Automatically.
api.yourapp.saas.dev
REST:
GET /customers β list
POST /customers β create
GET /customers/:id β get
PUT /customers/:id β update
DELETE /customers/:id β delete
POST /orders/:id/pay β verb
POST /orders/:id/ship β verb
GraphQL:
query { customers { id name orders { total } } }
mutation { payOrder(id: "...") { status } }
subscription { orderCreated { id total } }
Landing page with your messaging. Automatically.
yourapp.io.sb
βββ Hero section (from StoryBrand)
βββ Features (from your Nouns + Verbs)
βββ Pricing (from your Plans)
βββ Testimonials (when you have them)
βββ CTA β Sign up
API reference + guides. Automatically.
docs.yourapp.io.sb
βββ Getting Started
β βββ Quick Start
β βββ Authentication
β βββ Your First Request
βββ API Reference
β βββ Customers
β βββ Orders
β βββ Products
βββ Webhooks
β βββ Event Reference
βββ SDKs
βββ JavaScript
βββ Python
βββ Go
Command-line interface. Automatically.
$ npm install -g yourapp-cli
$ yourapp login
$ yourapp customers list
$ yourapp orders create --customer cus_123 --items prod_456
$ yourapp orders pay ord_789
$ yourapp orders ship ord_789Your SaaS works with AI tools. Automatically.
// In Claude, ChatGPT, Cursor, etc:
"Use yourapp to list my customers"
"Create a new order for customer John Smith"
"Ship order #789"Type-safe client libraries. Automatically.
import { YourApp } from 'yourapp'
const client = new YourApp({ apiKey: '...' })
// Typed CRUD
const customer = await client.customers.create({
name: 'John Smith',
email: 'john@example.com.ai',
})
// Typed verbs
await client.orders.pay('ord_123')
await client.orders.ship('ord_123')
// Real-time subscriptions
client.orders.on('created', (order) => {
console.log('New order:', order.total)
})Your SaaS is an integration. Automatically.
Your app auto-publishes to Zapier with:
βββ Triggers (every event)
β βββ Order Created
β βββ Order Paid
β βββ Customer Created
β βββ ...
βββ Actions (every verb)
β βββ Create Order
β βββ Pay Order
β βββ Ship Order
β βββ ...
βββ Searches (every noun)
βββ Find Customer
βββ Find Order
βββ ...
A complete SaaS for recruiters, in one file:
import { SaaS } from 'saaskit'
export default () => (
<SaaS name="RecruitKit">
{$ => {
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// NOUNS: Define what exists
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.nouns({
Recruiter: {
name: 'string',
email: 'string',
company: '->Company',
searches: ['<-Search'],
},
Candidate: {
name: 'string',
email: 'string',
skills: ['~>Skill'], // Fuzzy match to skill taxonomy
resume: 'markdown?',
linkedin: 'url?',
matches: ['<-Match'],
},
Search: {
title: 'string',
criteria: 'string',
recruiter: '->Recruiter',
matches: ['->Match'],
status: 'active | paused | closed',
},
Match: {
score: 'number',
status: 'new | shortlisted | contacted | interviewing | rejected | hired',
search: '->Search',
candidate: '->Candidate',
notes: 'markdown?',
},
})
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// VERBS: Define what happens
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.verbs({
Search: {
create: $ => $.db.Search.create({ ...$.input, status: 'active' }),
run: async $ => {
// AI-powered candidate matching
const candidates = await $.db.Candidate.semanticSearch($.record.criteria)
const matches = await Promise.all(
candidates.slice(0, 50).map(async candidate => {
const score = await $.ai`
Score 0-100 how well this candidate matches the search criteria.
Return ONLY a number.
Search: ${$.record.title}
Criteria: ${$.record.criteria}
Candidate: ${candidate.name}
Skills: ${candidate.skills.join(', ')}
Resume: ${candidate.resume || 'Not provided'}
`
return $.db.Match.create({
search: $.record.id,
candidate: candidate.id,
score: parseInt(score),
status: 'new',
})
})
)
await $.send('Search.completed', {
search: $.record,
matchCount: matches.length,
})
return matches
},
pause: $ => $.db.Search.update($.id, { status: 'paused' }),
close: $ => $.db.Search.update($.id, { status: 'closed' }),
},
Match: {
shortlist: $ => $.db.Match.update($.id, { status: 'shortlisted' }),
contact: async $ => {
const candidate = await $.record.candidate
const search = await $.record.search
// AI writes personalized outreach
const email = await $.ai`
Write a recruiting outreach email.
Candidate: ${candidate.name}
Role: ${search.title}
Their skills: ${candidate.skills.join(', ')}
Be warm and professional. Mention specific skills.
Keep it under 200 words.
`
await $.api.emails.send({
to: candidate.email,
subject: `Exciting opportunity: ${search.title}`,
body: email,
})
return $.db.Match.update($.id, { status: 'contacted' })
},
schedule: async $ => {
// Human picks interview time
const time = await $.human.ask(
`When should we schedule the interview with ${$.record.candidate.name}?`
)
await $.api.calendar.create({
title: `Interview: ${$.record.candidate.name}`,
time,
attendees: [$.record.candidate.email, $.record.search.recruiter.email],
})
return $.db.Match.update($.id, { status: 'interviewing' })
},
reject: async $ => {
// Requires confirmation for candidates in interview stage
if ($.record.status === 'interviewing') {
const confirmed = await $.human.approve(
`Reject ${$.record.candidate.name}? They are in the interview stage.`
)
if (!confirmed) return
}
await $.api.emails.send({
to: $.record.candidate.email,
subject: `Update on your application`,
body: await $.ai`
Write a kind rejection email for ${$.record.candidate.name}
who applied for ${$.record.search.title}.
Be respectful and encouraging.
`,
})
return $.db.Match.update($.id, { status: 'rejected' })
},
hire: async $ => {
await $.db.Match.update($.id, { status: 'hired' })
await $.db.Search.update($.record.search.id, { status: 'closed' })
await $.db.Metric.increment('hires')
// Celebrate!
await $.api.slack.send('#wins', `
π ${$.record.candidate.name} hired for ${$.record.search.title}!
`)
},
},
Candidate: {
enrich: async $ => {
// Pull data from Apollo
const data = await $.api.apollo.people.enrich({
email: $.record.email,
})
return $.db.Candidate.update($.id, {
name: data.name || $.record.name,
linkedin: data.linkedin_url || $.record.linkedin,
skills: data.skills || $.record.skills,
})
},
import: async $ => {
// Import from LinkedIn URL
const data = await $.api.proxycurl.linkedin.get({
url: $.input.linkedinUrl,
})
return $.db.Candidate.create({
name: data.full_name,
email: data.email,
linkedin: $.input.linkedinUrl,
skills: data.skills?.map(s => s.name) || [],
resume: data.summary,
})
},
},
})
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// EVENTS: React to changes
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.on.Match.created(async (match, $) => {
if (match.score >= 80) {
await $.api.emails.send({
to: match.search.recruiter.email,
subject: `High-quality match: ${match.candidate.name} (${match.score}/100)`,
body: `${match.candidate.name} scored ${match.score}/100 for "${match.search.title}"`,
})
}
})
$.on.Search.completed(async ({ search, matchCount }, $) => {
await $.api.emails.send({
to: search.recruiter.email,
subject: `Search complete: ${matchCount} candidates found`,
body: `Your search "${search.title}" found ${matchCount} candidates. Review them now.`,
})
})
$.on.Match.hired(async (match, $) => {
// Send offer letter
await $.api.emails.send({
to: match.candidate.email,
subject: `Offer Letter: ${match.search.title}`,
body: await $.ai`
Write a professional offer letter for ${match.candidate.name}
for the role of ${match.search.title}.
`,
})
})
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCHEDULES: Recurring tasks
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.every.day.at9am(async $ => {
// Remind recruiters about stale matches
const stale = await $.db.Match.find({
status: 'new',
createdAt: { $lt: $.time.daysAgo(7) },
})
const byRecruiter = groupBy(stale, m => m.search.recruiter.id)
for (const [recruiterId, matches] of Object.entries(byRecruiter)) {
const recruiter = await $.db.Recruiter.get(recruiterId)
await $.api.emails.send({
to: recruiter.email,
subject: `${matches.length} candidates awaiting review`,
body: `You have ${matches.length} candidates that haven't been reviewed in 7+ days.`,
})
}
})
$.every.Monday.at9am(async $ => {
// Weekly metrics
const hired = await $.db.Match.count({
status: 'hired',
updatedAt: { $gte: $.time.daysAgo(7) },
})
const contacted = await $.db.Match.count({
status: 'contacted',
updatedAt: { $gte: $.time.daysAgo(7) },
})
await $.api.slack.send('#metrics', `
π Weekly Recruiting Report
β’ Hired: ${hired}
β’ Contacted: ${contacted}
β’ Conversion: ${((hired / contacted) * 100).toFixed(1)}%
`)
})
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// AGENTS: AI workers
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.agent('sourcer', {
instructions: `
You are a technical recruiter specialized in sourcing candidates.
Your job:
1. Understand the role requirements
2. Find candidates that match
3. Score candidates based on fit
4. Explain why each candidate is a good match
Be thorough but prioritize quality over quantity.
A great match is better than 10 mediocre ones.
`,
tools: ['searchCandidates', 'enrichCandidate', 'scoreMatch'],
})
$.agent('outreach', {
instructions: `
You are a recruiting outreach specialist.
Your job:
1. Write personalized emails to candidates
2. Reference their specific skills and experience
3. Explain why this role is a good fit for them
4. Be warm but professional
Never be generic. Every email should feel personal.
`,
tools: ['getCandidate', 'getSearch', 'sendEmail'],
})
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// INTEGRATIONS
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$.integrate('apollo', { apiKey: $.env.APOLLO_API_KEY })
$.integrate('proxycurl', { apiKey: $.env.PROXYCURL_API_KEY })
$.integrate('slack', { webhook: $.env.SLACK_WEBHOOK })
$.integrate('calendar', { provider: 'google', oauth: true })
}}
</SaaS>
)That's a complete recruiting SaaS. App, API, landing page, docs, CLI, SDK, webhooks, billing β all derived from 250 lines of business logic.
SaaSkit is part of Startups.Studio:
Startups.Studio
βββ Startups.Studio/StartupBuilder β Build startups in minutes
βββ Startups.Studio/ServiceBuilder β AI-delivered Services-as-Software
βββ Startups.Studio/SalesBuilder β Mark (Marketing) + Sally (Sales)
SaaSkit (npm: saaskit)
βββ SaaS.Dev β SaaSkit-as-a-Service (managed hosting)
βββ SaaS.Studio β Manage your SaaS (MRR, customers, revenue)
Services.Studio β Managed Services-as-Software
Platform.do β The PaaS for Business-as-Code
βββ Agents.do β AI workers with tools
βββ Database.do β Schema-first database
βββ Functions.do β Serverless execution
βββ Workflows.do β Durable state machines
βββ Payments.do β Stripe Connect
βββ Bank.Accounts.do β Business banking + cards
βββ Emails.do β Transactional + marketing
βββ Texts.do β SMS
βββ Calls.do β Voice + AI receptionist
βββ ... β 20+ more primitives
Builder.Domains
βββ *.io.sb β Free app domains
βββ *.app.net.ai β Free app domains
βββ *.api.net.ai β Free API domains
βββ Any .tld β Register any domain
| Where | What |
|---|---|
| Startups.New | "Vibe code" your startup. AI conversation β running business in minutes. |
| Startup.Games | Gamify your journey from idea to one-person unicorn. |
| SaaS.Dev | Deploy your SaaSkit app. Managed hosting. |
| SaaS.Studio | Manage your SaaS. MRR, ARR, churn, customers, revenue. |
| Requirement | Version | Notes |
|---|---|---|
| Node.js | >=18.0.0 |
Required for native fetch, ES modules |
| React | ^18.0.0 or ^19.0.0 |
Peer dependency for UI components |
SaaSkit uses environment variables for authentication and configuration:
| Variable | Required | Description |
|---|---|---|
SAASKIT_API_TOKEN |
Yes (production) | API token for Platform.do services (Emails.do, Texts.do, etc.). Development mode uses a fallback token with a console warning. |
SAAS_API_KEY |
For deployment | Required when running saaskit deploy to authenticate with SaaS.Dev hosting. |
NODE_ENV |
No | Set to test to suppress development warnings. |
Integration-specific variables (optional, used via $.env):
STRIPE_API_KEY/STRIPE_SECRET_KEY- For Stripe paymentsAPOLLO_API_KEY- For Apollo enrichmentSLACK_WEBHOOK- For Slack notifications- Any custom variables your app needs via
$.env.YOUR_VAR
SaaSkit connects to the Platform.do ecosystem for backend services:
| Service | URL | Purpose |
|---|---|---|
| Emails.do | emails.do |
Transactional & marketing email |
| Texts.do | texts.do |
SMS messaging |
| Calls.do | calls.do |
Voice calls & AI receptionist |
| Payments.do | payments.do |
Stripe integration |
| APIs.do | apis.do |
9000+ third-party integrations |
Note: These services require a SAASKIT_API_TOKEN. During development, a fallback token is used automatically.
{
"optionalDependencies": {
"@dotdo/react": "^0.1.0" // Enhanced React integration
}
}-
Create a
.envfile from the example:cp .env.example .env
-
Add your API token:
# .env SAASKIT_API_TOKEN=your-token-here -
For deployment, also add:
SAAS_API_KEY=your-deployment-key
npm install saaskit// app.tsx
import { SaaS } from 'saaskit'
export default () => (
<SaaS name="MyApp">
{$ => {
$.nouns({
Todo: { title: 'string', done: 'boolean' },
})
$.verbs({
Todo: {
create: $ => $.db.Todo.create($.input),
complete: $ => $.db.Todo.update($.id, { done: true }),
},
})
}}
</SaaS>
)npx saaskit deployYour SaaS is live at myapp.saas.dev.
You have an idea. Stop letting infrastructure stand between you and your dream.
Define your business in Nouns and Verbs. We generate everything else.
Idea β Profitable SaaS. Wantrepreneur β Entrepreneur.
SaaSkit sits at the generation layer - it takes DSL definitions (Nouns + Verbs) and generates code that uses mdxui components.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β mdxui (interfaces) β
β SiteComponents, AppComponents, AdminComponents, DataProvider β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β implements
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β @mdxui/beacon + @mdxui/cockpit (templates) β
β Site sections, App dashboards, Auth flows β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β generates to
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
saaskit (generation) β YOU ARE HERE β
β DSL (Nouns + Verbs) β App, API, Site, Docs, CLI, SDK, MCP β
β Generates code that uses @mdxui components β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β optionally uses
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β @dotdo/react (platform) β
β TanStack DB binding for real-time data sync β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Output | Using |
|---|---|
| App | React components (raw + @mdxui/cockpit) |
| Site | @mdxui/beacon components |
| API | Self-contained REST/GraphQL/WebSocket |
| Docs | MDX documentation |
| CLI | Command-line interface |
| SDK | TypeScript/Python/Go clients |
| MCP | Model Context Protocol server |
SaaSkit is generation, not runtime. It produces code that uses mdxui abstractions. The generated code is self-contained and can be ejected.
When @dotdo/react is available, SaaSkit wires generated code to real backends with durable workflows.