What every product gets
Everything around your API, live on your own domain from one deploy: CLI, MCP, SDKs, docs, a landing site, a portal and Stripe billing.
Open in
| What you get | Where | What it does |
|---|---|---|
| Build | ||
| The API | each Noun's path | REST at the paths of your Nouns and Verbs, with one error shape, Batch, and the price and a request id on every reply. |
| Records | POST, GET, PATCH and DELETE /{nouns} | Create, get, list, update and delete on every Noun your Customers write, each record in its Customer's own space. |
| Lifecycle moves | POST /{nouns}/{id}/{verb} | One Verb per move, refused with 409 from the wrong state and with 403 for the wrong Role. |
| The change feed | /events | Every change as an Event in seq order, read back from any point with after. |
| Outbound webhooks | POST /watches | A Watch per URL your Customer picks: each change signed, retried until it lands, each attempt kept, and replayable. |
| Inbound webhooks | a Source's deliveries | Each delivery verified, then written as an Event before anything runs. |
| Durable runs | /actions/{id} | A Workflow's run as an Action: Pending with its reason, then done, across restarts and long waits. |
| Launch | ||
| CLI | a command per Verb | A command per Verb under your API's own name, generated from the same file. |
| MCP | /mcp | One tool per Verb with its price, then search, fetch and do, each answering the same body and price as REST; a Key or OAuth 2.1 signs in. |
| SDK | /sdk | TypeScript and Python clients under your API's name, generated from the file and published with it. |
| Docs | /docs | HTML and Markdown, a page per Noun, Verb and Event, with OpenAPI, llms.txt and agents.json. |
| Landing site | / | The pitch, Try it with a Test key, the prices and Get started, on your own domain. |
| Developer portal | /dashboard | Each Customer's keys, usage and balance, plan, invoices, spend ceiling, webhook deliveries with Replay, and why a run waits. |
| Monetize | ||
| The price on every reply | X-Cost and /pricing | The posted price of each Verb, on /pricing and on every reply, with a miss free. |
| Keys | Test and Live | Keys your Customers sign up for, each Live key with a Test twin whose calls never post. |
| Free calls | the Offer's free calls | The Offer's free calls come off each month before any credit is drawn. |
| Prepaid credits | each Customer's balance | Each priced call draws once from the Customer's credit balance, at the price it was served at, even when retried; a plan bills a month ahead. |
| Stripe billing | your own Stripe account | Each Customer buys credits through Stripe Checkout and plans through a Stripe subscription, with Stripe invoices and receipts, into your own Stripe account. |
| Your Admin | your account | Your revenue by Verb and by Customer, each payment beside the calls it paid for, and your margin per Verb. |
Build
- The API
- each Noun's path
- Records
POST, GET, PATCH and DELETE /{nouns}- Lifecycle moves
POST /{nouns}/{id}/{verb}- The change feed
/events- Outbound webhooks
POST /watches- Inbound webhooks
- a Source's deliveries
- Durable runs
/actions/{id}
Every change writes one Event: create, update, delete and restore give created, updated, deleted and restored, and each move its own past form.
A Watch sends each change your Customer picks to their URL, signed, and retries a failed delivery until it lands. Each attempt is kept, and any delivery replays from the developer portal.
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"
}
}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 })
},
}Launch
One call answers the same body and price on every Interface. The CLI and both SDKs carry your API's own name, generated from the file and published with it.
curl https: //companyfacts.apis.dev/ companies/ acme.com \
-H "Authorization: Bearer $COMPANYFACTS_KEY"HTTP/1.1 200 OK
X-Cost: price= 0.002, currency= "USD"
X-Request-Id: req_3cd59a9a1ede10c5bf21
{ "domain": "acme.com", "employees": 240, "sector": "Software" }curl https: //companyfacts.apis.dev/ mcp \
-H "Authorization: Bearer $COMPANYFACTS_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "getCompany", "arguments": { "domain": "acme.com" } }
}'HTTP/1.1 200 OK
X-Cost: price= 0.002, currency= "USD"
X-Request-Id: req_f9cf5fc65654d907192f
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [ … ],
"structuredContent": {
"domain": "acme.com",
"employees": 240,
"sector": "Software"
},
"_meta": {
"cost": { "price": 0.002, "currency": "USD" },
"requestId": "req_f9cf5fc65654d907192f"
}
}
}import { Companyfacts } from 'companyfacts'
const api = new Companyfacts({ key: process.env.COMPANYFACTS_KEY })
const { data, meta } = await api.getCompany('acme.com')
// data: { 'domain': 'acme.com', 'employees': 240, 'sector': 'Software' }
// meta: { status: 200, cost: { 'price': 0.002, 'currency': 'USD' },
// requestId: 'req_9fd1df32ff7df11f695a' }import os
from companyfacts import Companyfacts
api = Companyfacts(key=os.environ["COMPANYFACTS_KEY"])
data, meta = api.get_company("acme.com")
# data: { "domain": "acme.com", "employees": 240, "sector": "Software" }
# meta.cost: { "price": 0.002, "currency": "USD" }$ npx companyfacts login --key $COMPANYFACTS_KEY
$ npx companyfacts companies get acme.com
{ "domain": "acme.com", "employees": 240, "sector": "Software" }
price 0.002 USD · req_3cd59a9a1ede10c5bf21Your landing site and developer portal come from the same file. The pitch is the comment above it, and the prices are its Offer.
| Part | What it shows |
|---|---|
| The hero | The file's name and its pitch, the comment above the file, with Get started. |
| Try it | Each Verb with its example, called live with a Test key the page mints. |
| Pricing | A card per plan from /pricing, then each price per call, the free calls and the credit packs. |
| Get started | Sign-up, Stripe Checkout for a plan or credits, then a Live key. |
| Docs and MCP | The docs, llms.txt and the MCP server, for people and for agents. |
| Part | What it shows |
|---|---|
| Keys | Each Live key and its Test twin: made, rolled and revoked. |
| Usage and balance | Usage by Verb and day, the credit balance, and a top-up through Checkout. |
| Plan | The plan and its seats, and a switch to another plan. |
| Invoices | Each Stripe invoice and receipt. |
| Spend ceiling | A ceiling on each Key; past it, a call asks for Approval. |
| Webhook deliveries | Each Watch, its deliveries and their attempts, with Replay. |
| Runs | Each Action that waits, and its Pending reason. |
What an agent sees
An agent reads /.well-known/ first, then the product's own llms.txt, then tools/. It lists one tool per Verb, with its schemas, hints from its kind and its price.
{
"tools": [
{
"name": "getCompany",
"description": "Answers one company by its domain, with its headcount and sector.",
"inputSchema": {
"type": "object",
"properties": { "domain": { "type": "string" } },
"required": ["domain"]
},
"outputSchema": { … },
"annotations": { "readOnlyHint": true },
"_meta": { "price": 0.002, "currency": "USD" }
},
{
"name": "searchCompanies",
"description": "Lists the companies in one sector, each with its domain.",
"inputSchema": {
"type": "object",
"properties": { "sector": { "type": "string" } },
"required": ["sector"]
},
"outputSchema": { … },
"annotations": { "readOnlyHint": true },
"_meta": { "price": 0.005, "currency": "USD" }
},
{ "name": "search" },
{ "name": "fetch" },
{ "name": "do" }
]
}Monetize
{
"currency": "USD",
"rates": [
{ "name": "Company.get",
"price": 0.002, "unit": "call" },
{ "name": "Company.search",
"price": 0.005, "unit": "call" },
{ "name": "Company.watch",
"price": 0.1, "unit": "month" }
],
"free": { "Company.get": 100 },
"credits": [
10,
50,
{ "pay": 200,
"get": 220 }
]
}Each priced call draws once from prepaid credits, at the price it was served at.
Each Customer pays through Stripe, into your account.
When a Customer needs more
Every refusal sells. A 402 or a 403 carries offers: credit packs, a plan or a Verb, each with its Stripe Checkout link. The call after payment goes through.
curl https: //companyfacts.apis.dev/ companies/ acme.com \
-H "Authorization: Bearer $COMPANYFACTS_KEY"HTTP/1.1 402 Payment Required
Content-Type: application/ problem+json
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_71f702a3d4250104f0bf
{
"type": "https: //companyfacts.apis.dev/ docs/ errors#payment_required",
"title": "Payment Required",
"status": 402,
"detail": "The credit balance is 0; Company.get draws 0.002 from it.",
"code": "payment_required",
"instance": "req_71f702a3d4250104f0bf",
"offers": [
{
"kind": "credits",
"price": 10,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-10"
},
{
"kind": "credits",
"price": 50,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-50"
},
{
"kind": "credits",
"price": 200,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-200"
}
]
}import { Companyfacts } from 'companyfacts'
const api = new Companyfacts({ key: process.env.COMPANYFACTS_KEY })
const { error, meta } = await api.getCompany('acme.com')
// error: { status: 402, code: 'payment_required' }
// meta.cost: { price: 0, currency: 'USD' }HTTP/1.1 402 Payment Required
Content-Type: application/ problem+json
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_71f702a3d4250104f0bf
{
"type": "https: //companyfacts.apis.dev/ docs/ errors#payment_required",
"title": "Payment Required",
"status": 402,
"detail": "The credit balance is 0; Company.get draws 0.002 from it.",
"code": "payment_required",
"instance": "req_71f702a3d4250104f0bf",
"offers": [
{
"kind": "credits",
"price": 10,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-10"
},
{
"kind": "credits",
"price": 50,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-50"
},
{
"kind": "credits",
"price": 200,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-200"
}
]
}import os
from companyfacts import Companyfacts
api = Companyfacts(key=os.environ["COMPANYFACTS_KEY"])
error, meta = api.get_company("acme.com")
# error: { "status": 402, "code": "payment_required" }
# meta.cost: { "price": 0, "currency": "USD" }HTTP/1.1 402 Payment Required
Content-Type: application/ problem+json
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_71f702a3d4250104f0bf
{
"type": "https: //companyfacts.apis.dev/ docs/ errors#payment_required",
"title": "Payment Required",
"status": 402,
"detail": "The credit balance is 0; Company.get draws 0.002 from it.",
"code": "payment_required",
"instance": "req_71f702a3d4250104f0bf",
"offers": [
{
"kind": "credits",
"price": 10,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-10"
},
{
"kind": "credits",
"price": 50,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-50"
},
{
"kind": "credits",
"price": 200,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-200"
}
]
}$ npx companyfacts login --key $COMPANYFACTS_KEY
$ npx companyfacts companies get acme.com
error 402 payment_required
price 0 USD · req_71f702a3d4250104f0bfHTTP/1.1 402 Payment Required
Content-Type: application/ problem+json
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_71f702a3d4250104f0bf
{
"type": "https: //companyfacts.apis.dev/ docs/ errors#payment_required",
"title": "Payment Required",
"status": 402,
"detail": "The credit balance is 0; Company.get draws 0.002 from it.",
"code": "payment_required",
"instance": "req_71f702a3d4250104f0bf",
"offers": [
{
"kind": "credits",
"price": 10,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-10"
},
{
"kind": "credits",
"price": 50,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-50"
},
{
"kind": "credits",
"price": 200,
"checkout": "https: //companyfacts.apis.dev/ checkout/ credits-200"
}
]
}curl https: //companyfacts.apis.dev/ mcp \
-H "Authorization: Bearer $COMPANYFACTS_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "getCompany", "arguments": { "domain": "acme.com" } }
}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_fe35bd47ea83662091f1
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isError": true,
"content": [ … ],
"structuredContent": { "status": 402, "code": "payment_required" },
"_meta": {
"cost": { "price": 0, "currency": "USD" },
"requestId": "req_fe35bd47ea83662091f1"
}
}
}An agent sets Max-Cost, and a call above it is refused before it runs, at no charge. A spend ceiling the owner sets on a Key answers 403 with an Approval URL.
curl https: //companyfacts.apis.dev/ mcp \
-H "Authorization: Bearer $COMPANYFACTS_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "getCompany", "arguments": { "domain": "acme.com" },
"_meta": { "maxCost": 0.001 } }
}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_30d98961416616e608d0
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isError": true,
"content": [ … ],
"structuredContent": {
"type": "https: //companyfacts.apis.dev/ docs/ errors#over_max_cost",
"title": "Payment Required",
"status": 402,
"detail": "Company.get costs 0.002; the call allows 0.001.",
"code": "over_max_cost",
"instance": "req_30d98961416616e608d0"
},
"_meta": {
"cost": { "price": 0, "currency": "USD" },
"requestId": "req_30d98961416616e608d0"
}
}
}Your revenue
| Part | What it shows |
|---|---|
| Revenue | Revenue by Verb and by Customer, each payment beside the calls it paid for. |
| Customers | Each Customer with their plan, balance and usage. |
| Margin | Your margin per Verb, from your Sources' cost. |