Headless SaaS
Sell a home for your Customers' records, priced by plan and seat.
Open in
- Free
- 0 a month · 1 seat · 25 Deal.open included
- Team
- 49 a month · 5 seats, 9 each past them · 1,000 Deal.open included
- Deal.open
- 0.001 a call past a plan
Records
Nouns and their Relationships, CRUD and lifecycles, Roles and seats, and Workflows on their Events: CRUD is the floor. With no from, your Customer writes the field, and the Dataset API file becomes a Headless SaaS when its sources and every from go.
import { define, secret } from 'apis.dev'
import { define } from 'apis.dev'
/** Any company by its domain, with its headcount and sector. */
export default define({
name: 'companyfacts', domain: 'companyfacts.apis.dev',
sources: {
firms: { origin: 'https: //firms.example.com', key: secret('FIRMS_KEY') },
},
Company: {
domain: { type: 'url!#', from: 'firms', map: 'website' },
domain: 'url!#',
employees: { type: 'number? ', from: 'firms', map: 'headcount' },
employees: 'number? ',
sector: { type: 'string? ', from: 'firms', map: 'industry' },
sector: 'string? ',
},
offer: { 'Company.get': 0.002, 'Company.search': 0.005 },
offer: { Free: { month: 0, seats: 1 } },
})Create, get, list, update, delete and restore answer on each Noun, in each Customer's own space. A record answers at its id and at its !# key, so /companies/ is company_1. A delete keeps the record, and POST /companies/ brings it back.
curl https: //pipeline.apis.dev/ companies \
-H "Authorization: Bearer $PIPELINE_KEY" \
--json '{"name": "Acme", "domain": "acme.com"}'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"
}curl https: //pipeline.apis.dev/ companies/ company_1 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_2dc8be23d78d02570ee3
{
"id": "company_1",
"name": "Acme",
"domain": "acme.com",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 00: 00Z"
}curl https: //pipeline.apis.dev/ companies/ acme.com \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_abaab07988e66cd6576a
{
"id": "company_1",
"name": "Acme",
"domain": "acme.com",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 00: 00Z"
}curl https: //pipeline.apis.dev/ companies? domain= acme.com&limit=50 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_ea1dd29332afc974c134
{
"items": [
{
"id": "company_1",
"name": "Acme",
"domain": "acme.com",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 00: 00Z"
}
],
"total": 1,
"next": null
}curl -X PATCH https: //pipeline.apis.dev/ companies/ company_1 \
-H "Authorization: Bearer $PIPELINE_KEY" \
--json '{"name": "Acme Inc."}'HTTP/1.1 200 OK
X-Action-Id: act_2
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_47bafc37faaf0d937892
{
"id": "company_1",
"name": "Acme Inc.",
"domain": "acme.com",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 05: 00Z"
}curl -X DELETE https: //pipeline.apis.dev/ companies/ company_1 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Action-Id: act_3
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_e0627ba7efa530eb73ac
{
"id": "company_1",
"name": "Acme Inc.",
"domain": "acme.com",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 05: 00Z",
"deletedAt": "2026-10-24T09: 06: 00Z"
}A Relationship reads from either side: the Deals of a Company are one call.
curl https: //pipeline.apis.dev/ companies/ company_1/ deals \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_88eb811626931380df5a
{
"items": [
{
"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"
}
],
"total": 1,
"next": null
}import { Pipeline } from 'pipeline'
const api = new Pipeline({ key: process.env.PIPELINE_KEY })
const { data, meta } = await api.listDeals('company_1')
// data: { 'items': [{ 'id': 'deal_1', 'title': 'Pilot', … }
// meta.cost: { price: 0, currency: 'USD' }HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_88eb811626931380df5a
{
"items": [
{
"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"
}
],
"total": 1,
"next": null
}import os
from pipeline import Pipeline
api = Pipeline(key=os.environ["PIPELINE_KEY"])
data, meta = api.list_deals("company_1")
# data: { "items": [{ "id": "deal_1", "title": "Pilot", … }
# meta.cost: { "price": 0, "currency": "USD" }HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_88eb811626931380df5a
{
"items": [
{
"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"
}
],
"total": 1,
"next": null
}$ npx pipeline login --key $PIPELINE_KEY
$ npx pipeline deals list company_1
{ "items": [{ "id": "deal_1", "title": "Pilot", … }
price 0 USD · req_88eb811626931380df5aHTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_88eb811626931380df5a
{
"items": [
{
"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"
}
],
"total": 1,
"next": null
}curl https: //pipeline.apis.dev/ mcp \
-H "Authorization: Bearer $PIPELINE_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "listDeals", "arguments": { "id": "company_1" } }
}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_55a7c051d846f85cffb3
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [ … ],
"structuredContent": { "items": [ … ] },
"_meta": {
"cost": { "price": 0, "currency": "USD" },
"requestId": "req_55a7c051d846f85cffb3"
}
}
}The lifecycle
You write the moves, never the states: each state is its move's past form, and each move writes that Event. A move may name the Role that makes it, and a member without it gets 403.
open: [], qualify: 'opened', win: 'qualified',
open: [], qualify: 'opened',
win: { in: 'qualified', role: 'manager' },A move the lifecycle does not allow answers 409, with the moves it does allow, as the quickstart's early win shows.
Integrations
Each Customer can keep their records in their own CRM. Your Company is then served from it through their Integration: the paths, Events, SDK types and price stay the same.
sources: { crm: { system: 'crm', scope: 'customer' } },
Company: {
name: 'string', domain: 'url!#',
name: { type: 'string', from: 'crm', map: 'name' },
domain: { type: 'url!#', from: 'crm', map: 'website' },
deals: '<-Deal.company',
},A change in their CRM arrives as Company.updated, Observed, on the change feed and on every Watch that picks it.
curl https: //pipeline.apis.dev/ events? type= Company.updated&after=evt_3 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_9385689ed087052f4167
{
"items": [
{
"id": "evt_4",
"type": "Company.updated",
"at": "2026-10-24T11: 00: 00Z",
"seq": 4,
"via": "crm",
"provenance": "Observed",
"data": { "id": "company_1", "name": "Acme Corporation" }
}
],
"next": null
}Plans and seats
A plan is a price a month for its seats, with the calls it includes. A plan at month 0 stops at them and its refusal offers the next plan; a paid plan draws the price past them from credits. A seat past the plan's own is billed at seat, and keep: keeps current subscribers on a plan's old price.
{
"currency": "USD",
"rates": [
{ "name": "Free",
"price": 0, "unit": "month",
"seats": 1,
"includes": { "Deal.open": 25 } },
{ "name": "Team",
"price": 49, "unit": "month",
"seats": 5, "seat": 9,
"includes": { "Deal.open": 1000 } },
{ "name": "Deal.open",
"price": 0.001, "unit": "call" }
]
}| Nounthe shape you promise | Verbwhat can be done to it | Sourcewhere its data comes from | Triggerwhat reacts to a change | Offerthe price |
|---|---|---|---|---|
| your Customers' records and their Relationships | create, get, list, update and delete, and a lifecycle | each Customer's own CRM, as an Integration | a Workflow on Deal.won | plans with seats that include calls, and a price past them |