Multi-Company Architecture

Running multiple independent companies in one Paperclip instance: company_id isolation guarantees, shared infrastructure costs, per-company billing separation, and UI multi-tenancy.

Multi-Company and SaaS Patterns

What you'll learn

Run multiple independent companies in one Paperclip installation, implement tenant isolation, and offer Paperclip-as-a-service to multiple customers.

Why Multi-Company?

Paperclip is designed for multi-tenancy. A single installation can host:
- Multiple internal departments (Engineering, Marketing, Support)
- Multiple client projects (agency model)
- Multiple end-customers (SaaS product)

The Company Isolation Guarantee

As covered in pcl-11, every table has a company_id column and every query filters by it. This is architectural, not configurable — you cannot accidentally leak data between companies.

-- Verify isolation: this should NEVER return rows from another company
EXPLAIN SELECT * FROM issues WHERE company_id = 'company-a-id';
-- → should see "company_id = 'company-a-id'" in the Filter node

Creating Multiple Companies Programmatically

// packages/server/src/scripts/create-company.ts
async function createCompanyWithDefaults(opts: {
  name: string;
  slug: string;
  budget: number;
  template?: 'content' | 'dev-team' | 'support';
}) {
  const company = await db.companies.create({
    name: opts.name,
    slug: opts.slug,
    monthly_budget_usd: opts.budget,
  });

  if (opts.template === 'content') {
    await seedContentTeamAgents(company.id);
  } else if (opts.template === 'support') {
    await seedSupportTeamAgents(company.id);
  }

  return company;
}

Per-Company Heartbeat Schedules

Each company can have its own heartbeat schedule:

-- High-priority company: every 1 minute
UPDATE companies SET heartbeat_cron = '* * * * *' WHERE slug = 'priority-client';

-- Low-priority: every 30 minutes
UPDATE companies SET heartbeat_cron = '*/30 * * * *' WHERE slug = 'low-tier-client';

Billing by Company (SaaS Model)

Track costs per company for billing:

-- Monthly invoice data per company
SELECT
  c.name                   AS company,
  c.slug,
  SUM(ce.cost_usd)         AS llm_cost_usd,
  COUNT(DISTINCT ce.id)    AS llm_calls,
  COUNT(DISTINCT i.id)     AS issues_resolved
FROM companies c
LEFT JOIN cost_events ce
  ON ce.company_id = c.id
  AND date_trunc('month', ce.created_at) = date_trunc('month', now())
LEFT JOIN issues i
  ON i.company_id = c.id
  AND date_trunc('month', i.resolved_at) = date_trunc('month', now())
GROUP BY c.id, c.name, c.slug
ORDER BY llm_cost_usd DESC NULLS LAST;

Add your SaaS markup (e.g. 3× LLM cost) in the billing layer.

Admin API for Company Management

The Paperclip admin API (requires admin JWT) supports:

GET  /api/admin/companies          → list all companies
POST /api/admin/companies          → create company
PATCH /api/admin/companies/:id     → update (budget, pause, etc.)
DELETE /api/admin/companies/:id    → soft-delete
GET  /api/admin/companies/:id/stats → usage and cost summary

Checkpoint ✓

Sign in for progress tracking & Q&A →