---
title: "Start a Headless SaaS"
url: /quickstart/headless-saas
---

# Start a Headless SaaS

> A live API your Customers keep records in, with plans and seats paid to your Stripe, written as one file of Nouns, moves and plans.

You need Node 20 and a GitHub account. The first deploy answers at once on `pipeline.apis.dev`; your own domain is one line.

1. **Start the folder**

   ```sh title="Terminal"
   $ npx apis.dev init headless-saas
   ✓ wrote definition.ts: a Headless SaaS
     next: npx apis.dev dev
   ```

2. **Write the file**

   ```ts title="definition.ts"
   import { define } from 'apis.dev'

   /** A pipeline tracker your Customers log their deals in, each in its own space. */
   export default define({
     name: 'pipeline', domain: 'pipeline.apis.dev',
     Company: {                                    // Noun: your Customer writes it
       name: 'string', domain: 'url!#',
       deals: '<-Deal.company',                    // a Relationship
     },
     Deal: {
       title: 'string', amount: 'decimal(12,2)',
       company: '->Company.deals',
       stage: {                                    // Verbs: the lifecycle
         open: [], qualify: 'opened', win: 'qualified',  // []: from no state, so it creates the Deal
         lose: ['opened', 'qualified'],
       },
     },
     offer: {                                      // Offer: plans, and a price past them
       Free: { month: 0, seats: 1, includes: { 'Deal.open': 25 } },
       Team: { month: 49, seats: 5, seat: 9, includes: { 'Deal.open': 1000 } },
       'Deal.open': 0.001,
     },
   })
   ```

   With no `from`, your Customer writes the field: create, get, list, update, delete and restore. `stage` adds a Verb per move. Free stops at what it `includes`; Team draws the price past it.

3. **Try it locally**

   ```sh title="Terminal"
   $ npx apis.dev dev
   ✓ every Interface on http://localhost:8787, against your Test twin
     reloads on every save
   ```

4. **Publish it**

   ```sh title="Terminal"
   $ npx apis.dev deploy
   ✓ signed in with GitHub
   ✓ definition.ts: pipeline on pipeline.apis.dev
   ✓ Stripe connected: each Customer pays into your own account
   ✓ 2 Nouns, 15 Verbs, 2 plans, 1 price
       Company  create, get, list, update, delete, restore
       Deal     open, get, list, update, delete, restore, qualify, win, lose
       Free            0 a month, 1 seat, 25 Deal.open included
       Team            49 a month, 5 seats, 1,000 Deal.open included
       Deal.open       0.001 a call, past a plan
   ✓ published v1 to https://pipeline.apis.dev: 11 Interfaces
       REST              /companies, /deals
       CLI               npx pipeline
       MCP               /mcp
       SDK               npm i pipeline, pip install pipeline
       Docs              /docs
       OpenAPI           /openapi.json
       llms.txt          /llms.txt
       agents.json       /.well-known/agents.json
       Pricing           /pricing
       Landing site      /
       Developer portal  /dashboard
   ✓ Test key: pipeline_test_PUzMCRBrEcNUxGDRDwFcRU42AR8CCc21e958
   ```

   Deploy counts the Verbs from the file and prints each one by name. Each Customer who signs up gets a space of their own, and every record they write stays in it.

5. **Add a Company**

   **curl**

   ```sh title="Request"
   curl https://pipeline.apis.dev/companies \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{"name":"Acme","domain":"acme.com"}'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Pipeline } from 'pipeline'

   const api = new Pipeline({ key: process.env.PIPELINE_KEY })
   const { data, meta } = await api.createCompany({ name: 'Acme', domain: 'acme.com' })
   // data: { 'id': 'company_1', 'name': 'Acme', 'domain': 'acme.com', … }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from pipeline import Pipeline

   api = Pipeline(key=os.environ["PIPELINE_KEY"])
   data, meta = api.create_company(name="Acme", domain="acme.com")
   # data: { "id": "company_1", "name": "Acme", "domain": "acme.com", … }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx pipeline login --key $PIPELINE_KEY
   $ npx pipeline companies create --name Acme --domain acme.com
   { "id": "company_1", "name": "Acme", "domain": "acme.com", … }
   price 0 USD · req_df59a11235e22d4bc20f
   ```

   **MCP**

   ```sh title="Request"
   curl https://pipeline.apis.dev/mcp \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "createCompany",
       "arguments": { "name": "Acme", "domain": "acme.com" } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_864ae4b67955f0e11948

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "content": [ … ],
       "structuredContent": { "id": "company_1" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_864ae4b67955f0e11948"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 201 Created
   Location: /companies/company_1
   X-Action-Id: act_1
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_df59a11235e22d4bc20f

   {
     "id": "company_1",
     "name": "Acme",
     "domain": "acme.com",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-24T09:00:00Z"
   }
   ```

   A create answers 201 with the record, where it lives, and the [Action](/workflow.md#the-lifecycle) it ran as.

6. **Open a Deal**

   **curl**

   ```sh title="Request"
   curl https://pipeline.apis.dev/deals \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{"title":"Pilot","amount":"5000.00","company":"company_1"}'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Pipeline } from 'pipeline'

   const api = new Pipeline({ key: process.env.PIPELINE_KEY })
   const { data, meta } = await api.openDeal({ title: 'Pilot', amount: '5000.00', company: 'company_1' })
   // data: { 'id': 'deal_1', 'title': 'Pilot', 'amount': '5000.00', … }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from pipeline import Pipeline

   api = Pipeline(key=os.environ["PIPELINE_KEY"])
   data, meta = api.open_deal(title="Pilot", amount="5000.00", company="company_1")
   # data: { "id": "deal_1", "title": "Pilot", "amount": "5000.00", … }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx pipeline login --key $PIPELINE_KEY
   $ npx pipeline deals open --title Pilot --amount 5000.00 --company company_1
   { "id": "deal_1", "title": "Pilot", "amount": "5000.00", … }
   price 0 USD · req_019e21b5e33a04d5e91e
   ```

   **MCP**

   ```sh title="Request"
   curl https://pipeline.apis.dev/mcp \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "openDeal",
       "arguments": {
         "title": "Pilot",
         "amount": "5000.00",
         "company": "company_1"
       } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_cfa756674d6e49366ba7

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "content": [ … ],
       "structuredContent": { "id": "deal_1" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_cfa756674d6e49366ba7"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 201 Created
   Location: /deals/deal_1
   X-Action-Id: act_5
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_019e21b5e33a04d5e91e

   {
     "id": "deal_1",
     "title": "Pilot",
     "amount": "5000.00",
     "company": "company_1",
     "stage": "opened",
     "createdAt": "2026-10-24T09:10:00Z",
     "updatedAt": "2026-10-24T09:10:00Z"
   }
   ```

   Opening the Deal wrote `Deal.opened`. Free includes it, so its price is 0.

7. **Get the webhook**

   **curl**

   ```sh title="Request"
   curl https://pipeline.apis.dev/watches \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
       "events": ["Deal.opened"],
       "url": "https://hooks.acme.com/hooks/pipeline"
     }'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Pipeline } from 'pipeline'

   const api = new Pipeline({ key: process.env.PIPELINE_KEY })
   const { data, meta } = await api.createWatch({ events: ['Deal.opened'], url: 'https://hooks.acme.com/hooks/pipeline' })
   // data: { 'id': 'watch_1', 'events': ['Deal.opened'], … }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from pipeline import Pipeline

   api = Pipeline(key=os.environ["PIPELINE_KEY"])
   data, meta = api.create_watch(events=["Deal.opened"], url="https://hooks.acme.com/hooks/pipeline")
   # data: { "id": "watch_1", "events": ["Deal.opened"], … }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx pipeline login --key $PIPELINE_KEY
   $ npx pipeline watchs create \
       --events '["Deal.opened"]' \
       --url https://hooks.acme.com/hooks/pipeline
   { "id": "watch_1", "events": ["Deal.opened"], … }
   price 0 USD · req_8db1e5ea781c9709a731
   ```

   **MCP**

   ```sh title="Request"
   curl https://pipeline.apis.dev/mcp \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "createWatch",
       "arguments": {
         "events": ["Deal.opened"],
         "url": "https://hooks.acme.com/hooks/pipeline"
       } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_79bcb44af511cb69425f

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "content": [ … ],
       "structuredContent": { "id": "watch_1" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_79bcb44af511cb69425f"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 201 Created
   Location: /watches/watch_1
   X-Action-Id: act_4
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_8db1e5ea781c9709a731

   {
     "id": "watch_1",
     "events": ["Deal.opened"],
     "url": "https://hooks.acme.com/hooks/pipeline",
     "secret": "whsec_7Kq2vN4xR7mW1pT9",
     "createdAt": "2026-10-24T09:08:00Z",
     "updatedAt": "2026-10-24T09:08:00Z"
   }
   ```


   ```http title="Delivery"
   POST /hooks/pipeline HTTP/1.1
   Host: hooks.acme.com
   X-Webhook-Attempt: 1
   X-Webhook-Signature: t=1792833000,v1=2c067386fcd9006ece95389e2fde364074ad34388decdfc0a31f3edce14d0252

   {
     "id": "evt_2",
     "type": "Deal.opened",
     "at": "2026-10-24T09:10:00Z",
     "seq": 2,
     "data": {
       "id": "deal_1",
       "title": "Pilot",
       "amount": "5000.00",
       "company": "company_1",
       "stage": "opened"
     }
   }
   ```


   ```ts title="Receive it"
   import { createHmac, timingSafeEqual } from 'node:crypto'

   // verify(request, secret): true when the delivery was signed with your secret in the last 5 minutes
   export async function verify(request: Request, secret: string) {
     const header = request.headers.get('X-Webhook-Signature') ?? ''
     const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
     if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
     const want = createHmac('sha256', secret).update(`${t}.${await request.text()}`).digest()
     const got = Buffer.from(v1 ?? '', 'hex')
     return got.length === want.length && timingSafeEqual(got, want)
   }

   // The receiver: verify a copy, then read the Event once by its id.
   const seen = new Set<string>()
   export default {
     async fetch(req: Request) {
       if (!(await verify(req.clone(), process.env.PIPELINE_WEBHOOK_SECRET!))) return new Response(null, { status: 401 })
       const event = await req.json()
       if (!seen.has(event.id)) seen.add(event.id)  // a retry carries the same id
       return new Response(null, { status: 204 })
     },
   }
   ```

   Your Customer makes a [Watch](/reference.md#events), and each `Deal.opened` goes out signed, retried until their URL answers. `npx apis.dev events listen --forward localhost:3000/hooks` tests the receiver first.

8. **Win it too early**

   **curl**

   ```sh title="Request"
   curl -X POST https://pipeline.apis.dev/deals/deal_1/win \
     -H "Authorization: Bearer $PIPELINE_KEY"
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Pipeline } from 'pipeline'

   const api = new Pipeline({ key: process.env.PIPELINE_KEY })
   const { error, meta } = await api.winDeal('deal_1')
   // error: { status: 409, code: 'wrong_state' }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from pipeline import Pipeline

   api = Pipeline(key=os.environ["PIPELINE_KEY"])
   error, meta = api.win_deal("deal_1")
   # error: { "status": 409, "code": "wrong_state" }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx pipeline login --key $PIPELINE_KEY
   $ npx pipeline deals win deal_1
   error 409 wrong_state
   price 0 USD · req_66b1b3cad16f500d936f
   ```

   **MCP**

   ```sh title="Request"
   curl https://pipeline.apis.dev/mcp \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "winDeal", "arguments": { "id": "deal_1" } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_dd2bf757dc2514a44b6c

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "isError": true,
       "content": [ … ],
       "structuredContent": { "status": 409, "code": "wrong_state" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_dd2bf757dc2514a44b6c"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 409 Conflict
   Content-Type: application/problem+json
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_66b1b3cad16f500d936f

   {
     "type": "https://pipeline.apis.dev/docs/errors#wrong_state",
     "title": "Conflict",
     "status": 409,
     "detail": "A Deal in opened cannot win; win moves from qualified.",
     "code": "wrong_state",
     "instance": "req_66b1b3cad16f500d936f",
     "allowed": ["qualify", "lose"]
   }
   ```

   A Deal moves only along its lifecycle: `win` needs `qualified` first, and the refusal lists the moves it allows.

9. **Qualify it, then win it**

   **qualify**

   ```sh title="Request"
   curl -X POST https://pipeline.apis.dev/deals/deal_1/qualify \
     -H "Authorization: Bearer $PIPELINE_KEY"
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Action-Id: act_6
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_b3219e966dabdd964790

   {
     "id": "deal_1",
     "title": "Pilot",
     "amount": "5000.00",
     "company": "company_1",
     "stage": "qualified",
     "createdAt": "2026-10-24T09:10:00Z",
     "updatedAt": "2026-10-24T09:20:00Z"
   }
   ```

   **win**

   ```sh title="Request"
   curl -X POST https://pipeline.apis.dev/deals/deal_1/win \
     -H "Authorization: Bearer $PIPELINE_KEY"
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Action-Id: act_7
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_3baa43ca329668af8de5

   {
     "id": "deal_1",
     "title": "Pilot",
     "amount": "5000.00",
     "company": "company_1",
     "stage": "won",
     "createdAt": "2026-10-24T09:10:00Z",
     "updatedAt": "2026-10-24T09:30:00Z"
   }
   ```

   Winning wrote `Deal.won`.

10. **Kick off when a Deal is won**

   ```ts title="definition.ts"
   import { define, workflow } from 'apis.dev'  // [!code ++]
     Deal: {
       …
       kickoff: workflow(async ($, deal) => {  // [!code ++]
         const { plan } = await $.generative('Draft the kickoff plan for {company.name}', deal,  // [!code ++]
           { returns: { plan: 'string' } })  // [!code ++]
         await $.human('Approve the plan', { plan }, { role: 'manager' })  // [!code ++]
         const at = await $.step('stamp it', () => new Date().toISOString())  // [!code ++]
         return { plan, at }  // [!code ++]
       }),  // [!code ++]
     },
     on: { 'Deal.won': 'Deal.kickoff' },  // [!code ++]
   ```

   **get**

   ```sh title="Request"
   curl https://pipeline.apis.dev/actions/act_8 \
     -H "Authorization: Bearer $PIPELINE_KEY"
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_1154ca02bc01e341ac7b

   {
     "id": "act_8",
     "verb": "Deal.kickoff",
     "status": "pending",
     "reason": "AwaitingWorker",
     "poll": "/actions/act_8"
   }
   ```

   **complete**

   ```sh title="Request"
   curl https://pipeline.apis.dev/actions/act_8/complete \
     -H "Authorization: Bearer $MANAGER_KEY" \
     --json '{"output":{"approved":true}}'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_5881a5df799b8e82a74b

   {
     "id": "act_8",
     "verb": "Deal.kickoff",
     "status": "done",
     "output": {
       "plan": "Week 1: import. Week 2: first review.",
       "at": "2026-10-26T15:00:00Z"
     }
   }
   ```

   `Deal.won` starts the kickoff once. It waits on a manager, who completes the step with their own [Key](/reference.md#key), and the run ends with its output. See [Workflow](/workflow.md).

11. **Outgrow the Free plan**

   **curl**

   ```sh title="Request"
   curl https://pipeline.apis.dev/deals \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{"title":"Renewal","amount":"1200.00","company":"company_1"}'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Pipeline } from 'pipeline'

   const api = new Pipeline({ key: process.env.PIPELINE_KEY })
   const { error, meta } = await api.openDeal({ title: 'Renewal', amount: '1200.00', company: 'company_1' })
   // error: { status: 402, code: 'plan_limit' }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from pipeline import Pipeline

   api = Pipeline(key=os.environ["PIPELINE_KEY"])
   error, meta = api.open_deal(title="Renewal", amount="1200.00", company="company_1")
   # error: { "status": 402, "code": "plan_limit" }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx pipeline login --key $PIPELINE_KEY
   $ npx pipeline deals open --title Renewal --amount 1200.00 --company company_1
   error 402 plan_limit
   price 0 USD · req_02f4f9b0fd2d3af756ba
   ```

   **MCP**

   ```sh title="Request"
   curl https://pipeline.apis.dev/mcp \
     -H "Authorization: Bearer $PIPELINE_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "openDeal",
       "arguments": {
         "title": "Renewal",
         "amount": "1200.00",
         "company": "company_1"
       } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_ac0716f42658ea530abb

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "isError": true,
       "content": [ … ],
       "structuredContent": { "status": 402, "code": "plan_limit" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_ac0716f42658ea530abb"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 402 Payment Required
   Content-Type: application/problem+json
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_02f4f9b0fd2d3af756ba

   {
     "type": "https://pipeline.apis.dev/docs/errors#plan_limit",
     "title": "Payment Required",
     "status": 402,
     "detail": "Free includes 25 Deal.open a month; this is the 26th.",
     "code": "plan_limit",
     "instance": "req_02f4f9b0fd2d3af756ba",
     "offers": [
       {
         "kind": "plan",
         "name": "Team",
         "price": 49,
         "checkout": "https://pipeline.apis.dev/checkout/team"
       }
     ]
   }
   ```

   The call past what Free includes answers 402 with Team in its `offers`; after Checkout, the same call goes through. On Team, a teammate past its seats answers 402 for one more seat.

## Result
Your API answers on https://pipeline.apis.dev: 15 Verbs, a wrong move refused with 409, each change sent on a signed webhook, and a Workflow 8 lines long on `Deal.won`. Free includes 25 `Deal.open` a month; the call past them answers 402 with Team on Stripe Checkout. You wrote no CRUD, no state checks, no Events and no webhook code.

```ts title="definition.ts"
import { define, workflow } from 'apis.dev'

/** A pipeline tracker your Customers log their deals in, each in its own space. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  Company: {                                    // Noun: your Customer writes it
    name: 'string', domain: 'url!#',
    deals: '<-Deal.company',                    // a Relationship
  },
  Deal: {
    title: 'string', amount: 'decimal(12,2)',
    company: '->Company.deals',
    stage: {                                    // Verbs: the lifecycle
      open: [], qualify: 'opened', win: 'qualified',  // []: from no state, so it creates the Deal
      lose: ['opened', 'qualified'],
    },
    kickoff: workflow(async ($, deal) => {
      const { plan } = await $.generative('Draft the kickoff plan for {company.name}', deal,
        { returns: { plan: 'string' } })
      await $.human('Approve the plan', { plan }, { role: 'manager' })
      const at = await $.step('stamp it', () => new Date().toISOString())
      return { plan, at }
    }),
  },
  on: { 'Deal.won': 'Deal.kickoff' },
  offer: {                                      // Offer: plans, and a price past them
    Free: { month: 0, seats: 1, includes: { 'Deal.open': 25 } },
    Team: { month: 49, seats: 5, seat: 9, includes: { 'Deal.open': 1000 } },
    'Deal.open': 0.001,
  },
})
```

## Next steps
- **[Start a Dataset API](/quickstart/dataset-api.md) →** Another API writes each field. You pick the shape and the price.
- [Headless SaaS](/patterns/headless-saas.md): records, the lifecycle, [Integrations](/patterns/headless-saas.md#integrations), plans and seats.
- [Workflow](/workflow.md): what runs when a Deal is won.