Patterns
Five kinds of API product you can sell, each priced its own way, and each one file of the same five blocks.
Open in
The grid
from is your Customer's.| Pattern | Nounthe shape you promise | Verbwhat can be done to it | Sourcewhere its data comes from | Triggerwhat reacts to a change | Offerthe price |
|---|---|---|---|---|---|
| Dataset APISell the facts about a thing, priced per answer.Sources write it | Company, keyed by its domain | get by key, search by a field | the API you resell, mapped into yours | a refresh on a schedule | a price per answer, from credits; a miss is free |
| Headless SaaSSell a home for your Customers' records, priced by plan and seat.Your Customer writes it | 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 |
| Function APISell an answer, priced per call.A Function writes it | the Noun it acts on | a Function: code, a model, an agent or a person | none | none | a price per call, by kind; a failure is free |
| Agent APISell a worker who does the job, billed by consumption.An agent writes it | your Customer's records, in their space | the job, done by an agent for them | your Customer's inbox and CRM | a schedule: three meetings a week | by consumption: tokens and minutes |
| Service APISell an outcome, charged once when it is done: the only pattern priced by outcome.A Workflow writes it | a work Thing the buyer watches | order, then one Verb per move | the filing office's receipt | Filing.ordered starts the draft | 25 when accepted, under an Outcome contract |
The five blocks
import { define, workflow } from 'apis.dev'
/** Deals for each Customer, kicked off when won. */
export default define({
name: 'pipeline', domain: 'pipeline.apis.dev',
sources: { firms: { origin: 'https: //firms.example.com' } }, // Source: where its data comes from
Deal: { // Noun: the shape you promise
title: 'string', amount: 'decimal(12, 2)',
stage: { // Verb: what can be done to it
open: [], qualify: 'opened', win: 'qualified',
},
kickoff: workflow(async ($, deal) => { … }),
},
on: { 'Deal.won': 'Deal.kickoff' }, // Trigger: what reacts to a change
offer: { Team: { month: 49, seats: 5 }, 'Deal.open': 0.001 }, // Offer: the price
})Five blocks, four keys: Verbs live inside their Noun. The Dataset API and the Headless SaaS have pages of their own, and each starts a Get started track.
Answers, workers and outcomes
The other three are each one file here.
Function API
| Kind | What it does | How long | Verb | Price |
|---|---|---|---|---|
| Code | computes | milliseconds | Company.score | 0.001 |
| Generative | writes | seconds | Company.summarize | 0.02 |
| Agentic | researches, with tools | minutes | Company.research | 0.5 |
| Human | decides | hours or days | Company.verify | 5 |
A Verb whose value is a Function sells an answer, and its kind sets how long it takes. returns types the output on every Interface: the Action, the MCP outputSchema, OpenAPI and the SDK. In a prompt, {domain} reads a field and {company.name} follows a Relationship.
import { define, code, generative, agentic, human } from 'apis.dev'
/** Answers about any company by its domain, from a score in milliseconds to a person's check in hours. */
export default define({
name: 'summaries', domain: 'summaries.apis.dev',
Company: { // Noun: the Noun it acts on
domain: 'url!#',
/** A score from 0 to 100, from the domain alone. */
score: code((c) => ({ score: c.domain.endsWith('.ai') ? 80 : 50 })), // Verb: a Function
summarize: generative('Two sentences on what {domain} sells',
{ returns: { summary: 'string' } }),
research: agentic('Find the head of sales at {domain}',
{ returns: { headOfSales: 'string' } }),
verify: human('Call and confirm the head of sales',
{ returns: { confirmed: 'boolean' }, role: 'researcher' }),
},
Text: { // a Function on no stored Noun
translate: generative('Translate {text} into {to}',
{ input: { text: 'string', to: 'string' }, returns: { text: 'string' } }),
},
offer: { // Offer: per call
'Company.score': 0.001, 'Company.summarize': 0.02,
'Company.research': 0.5, 'Company.verify': 5,
'Text.translate': 0.01,
},
})A fast answer comes back done. A slow one answers 202 with where to poll, and the caller polls its Action until it is done. A Function on a Noun with no records, such as Text.translate, takes its input in the body.
curl -X POST https: //summaries.apis.dev/ companies/ acme.com/ summarize \
-H "Authorization: Bearer $SUMMARIES_KEY"HTTP/1.1 200 OK
X-Cost: price= 0.02, currency= "USD"
X-Request-Id: req_fa5075382229d67f1ce9
{
"id": "act_1",
"verb": "Company.summarize",
"status": "done",
"output": { "summary": "Acme sells pipeline software to B2B sales teams." }
}curl -X POST https: //summaries.apis.dev/ companies/ acme.com/ research \
-H "Authorization: Bearer $SUMMARIES_KEY"HTTP/1.1 202 Accepted
Location: /actions/ act_2
Retry-After: 60
X-Cost: price= 0.5, currency= "USD"
X-Request-Id: req_0a6b1dbf299819cd7076
{
"id": "act_2",
"verb": "Company.research",
"status": "pending",
"reason": "Running",
"poll": "/actions/ act_2"
}curl https: //summaries.apis.dev/ actions/ act_2 \
-H "Authorization: Bearer $SUMMARIES_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_6fcd6e915495e75ebee0
{
"id": "act_2",
"verb": "Company.research",
"status": "done",
"output": { "headOfSales": "Dana Ito" },
"provenance": "Inferred"
}curl https: //summaries.apis.dev/ texts/ translate \
-H "Authorization: Bearer $SUMMARIES_KEY" \
--json '{"text": "Two sentences on Acme.", "to": "fr"}'HTTP/1.1 200 OK
X-Cost: price= 0.01, currency= "USD"
X-Request-Id: req_0ab351527f78099b8341
{
"id": "act_3",
"verb": "Text.translate",
"status": "done",
"output": { "text": "Deux phrases sur Acme." }
}Each kind has its price: 0.001 to compute, 0.02 to write, 0.5 to research, 5 for a person. A failed or cancelled Action costs nothing.
$ npx apis.dev init function-api
$ npx apis.dev deploy
✓ published v1 to https: //summaries.apis.dev
$ curl -X POST https: //summaries.apis.dev/ companies/ acme.com/ summarize \
-H "Authorization: Bearer $SUMMARIES_KEY"Agent API
| Meter | Usage × rate | Amount |
|---|---|---|
| Tokens | 18,400 × 0.01 / 1,000 | 0.184 |
| Minutes | 6 × 0.05 | 0.3 |
| Reads | get and list | 0 |
| The run | Meeting.book | 0.484 |
An agent does the job for each Customer, in their own space, with their own inbox and CRM as Sources. Its schedule runs once per Customer, in that Customer's timezone, and a Max-Cost on the run stops it at the ceiling.
import { define, agentic } from 'apis.dev'
/** An agent that books three meetings a week with each Customer's leads, in their own inbox and CRM. */
export default define({
name: 'meetings', domain: 'meetings.apis.dev',
sources: { inbox: { system: 'email', scope: 'customer' }, // Source: each Customer's own
crm: { system: 'crm', scope: 'customer' } },
Meeting: { // Noun: their records, in their space
with: 'string', company: 'string', at: 'date',
book: agentic('Book a meeting with the next lead in {crm}', // Verb: the job
{ tools: ['inbox', 'crm'], returns: { meeting: 'string' } }),
},
on: { 'every Mon, Wed, Fri 09: 00': 'Meeting.book' }, // Trigger: in each Customer's timezone
offer: { // Offer: by use
'Meeting.book': { per1000Tokens: 0.01, perMinute: 0.05 },
'Meeting.get': 0, 'Meeting.list': 0,
},
})Each run is an Action that carries what it spent.
curl https: //meetings.apis.dev/ actions/ act_31 \
-H "Authorization: Bearer $MEETINGS_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_24fbbad973aa8085d69b
{
"id": "act_31",
"verb": "Meeting.book",
"status": "done",
"output": { "meeting": "mtg_1" },
"usage": { "tokens": 18400, "minutes": 6 },
"cost": { "price": 0.484, "currency": "USD" }
}import { Meetings } from 'meetings'
const api = new Meetings({ key: process.env.MEETINGS_KEY })
const { data, meta } = await api.getAction('act_31')
// data: { 'id': 'act_31', 'verb': 'Meeting.book', 'status': 'done', … }
// meta.cost: { price: 0, currency: 'USD' }HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_24fbbad973aa8085d69b
{
"id": "act_31",
"verb": "Meeting.book",
"status": "done",
"output": { "meeting": "mtg_1" },
"usage": { "tokens": 18400, "minutes": 6 },
"cost": { "price": 0.484, "currency": "USD" }
}import os
from meetings import Meetings
api = Meetings(key=os.environ["MEETINGS_KEY"])
data, meta = api.get_action("act_31")
# data: { "id": "act_31", "verb": "Meeting.book", "status": "done", … }
# meta.cost: { "price": 0, "currency": "USD" }HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_24fbbad973aa8085d69b
{
"id": "act_31",
"verb": "Meeting.book",
"status": "done",
"output": { "meeting": "mtg_1" },
"usage": { "tokens": 18400, "minutes": 6 },
"cost": { "price": 0.484, "currency": "USD" }
}$ npx meetings login --key $MEETINGS_KEY
$ npx meetings actions get act_31
{ "id": "act_31", "verb": "Meeting.book", "status": "done", … }
price 0 USD · req_24fbbad973aa8085d69bHTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_24fbbad973aa8085d69b
{
"id": "act_31",
"verb": "Meeting.book",
"status": "done",
"output": { "meeting": "mtg_1" },
"usage": { "tokens": 18400, "minutes": 6 },
"cost": { "price": 0.484, "currency": "USD" }
}curl https: //meetings.apis.dev/ mcp \
-H "Authorization: Bearer $MEETINGS_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "getAction", "arguments": { "id": "act_31" } }
}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_ed960bb1c8504fb2745b
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [ … ],
"structuredContent": { "id": "act_31" },
"_meta": {
"cost": { "price": 0, "currency": "USD" },
"requestId": "req_ed960bb1c8504fb2745b"
}
}
}Each run is billed by consumption: 0.01 per 1,000 tokens and 0.05 a minute, metered once. Reads cost nothing.
{
"currency": "USD",
"rates": [
{ "name": "Meeting.get",
"price": 0, "unit": "call" },
{ "name": "Meeting.list",
"price": 0, "unit": "call" },
{ "name": "Meeting.book",
"price": 0.01,
"unit": "1000 tokens" },
{ "name": "Meeting.book",
"price": 0.05, "unit": "minute" }
]
}$ npx apis.dev init agent-api
$ npx apis.dev deploy
✓ published v1 to https: //meetings.apis.dev
$ curl https: //meetings.apis.dev/ meetings? week= 2026-W44 \
-H "Authorization: Bearer $MEETINGS_KEY"Service API
A Service API sells an outcome: a work Thing the buyer watches move through its lifecycle, under an Outcome contract. It is charged once, on the move the contract names, and a rejected one gets its remedy.
import { define, workflow, agentic } from 'apis.dev'
/** Each Customer's annual report, drafted, signed and filed, charged once when the Customer accepts it. */
export default define({
name: 'filings', domain: 'filings.apis.dev',
Filing: { // Noun: the work your buyer watches
company: 'string', year: 'number',
status: { // Verbs: one per move
order: [],
draft: { in: 'ordered', run: workflow(async ($, filing) => {
const { form } = await $.generative('Draft the annual report for {company}', filing,
{ returns: { form: 'string' } })
await $.human('Check the draft', { form }, { role: 'paralegal' }) // unseen: a step
return { form }
}) },
sign: 'drafted', // seen: the Customer's turn
file: { in: 'signed', run: agentic('File the signed report with the state') },
accept: 'filed', reject: 'filed', // the Customer accepts, or it is redone
},
},
on: { 'Filing.ordered': 'Filing.draft', 'Filing.signed': 'Filing.file' },
offer: {
'Filing.order': { price: 25, when: 'accepted', accept: 'customer',
acceptWithin: '7d', remedy: 'redo' },
},
})The paralegal's check is a step inside draft, so no one outside sees it. The signature is a state: the Customer acts, and file becomes allowed.
curl -X POST https: //filings.apis.dev/ filings/ filing_1/ sign \
-H "Authorization: Bearer $FILINGS_KEY"HTTP/1.1 200 OK
X-Action-Id: act_3
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_47d0a7b035cb42c3265d
{
"id": "filing_1",
"company": "acme.com",
"year": 2026,
"status": "signed",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-25T16: 40: 00Z"
}import { Filings } from 'filings'
const api = new Filings({ key: process.env.FILINGS_KEY })
const { data, meta } = await api.signFiling('filing_1')
// data: { 'id': 'filing_1', 'company': 'acme.com', 'year': 2026, … }
// meta.cost: { price: 0, currency: 'USD' }HTTP/1.1 200 OK
X-Action-Id: act_3
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_47d0a7b035cb42c3265d
{
"id": "filing_1",
"company": "acme.com",
"year": 2026,
"status": "signed",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-25T16: 40: 00Z"
}import os
from filings import Filings
api = Filings(key=os.environ["FILINGS_KEY"])
data, meta = api.sign_filing("filing_1")
# data: { "id": "filing_1", "company": "acme.com", "year": 2026, … }
# meta.cost: { "price": 0, "currency": "USD" }HTTP/1.1 200 OK
X-Action-Id: act_3
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_47d0a7b035cb42c3265d
{
"id": "filing_1",
"company": "acme.com",
"year": 2026,
"status": "signed",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-25T16: 40: 00Z"
}$ npx filings login --key $FILINGS_KEY
$ npx filings filings sign filing_1
{ "id": "filing_1", "company": "acme.com", "year": 2026, … }
price 0 USD · req_47d0a7b035cb42c3265dHTTP/1.1 200 OK
X-Action-Id: act_3
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_47d0a7b035cb42c3265d
{
"id": "filing_1",
"company": "acme.com",
"year": 2026,
"status": "signed",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-25T16: 40: 00Z"
}curl https: //filings.apis.dev/ mcp \
-H "Authorization: Bearer $FILINGS_KEY" \
--json '{
"jsonrpc": "2.0", "id": 1, "method": "tools/ call",
"params": { "name": "signFiling", "arguments": { "id": "filing_1" } }
}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_724d327d688bd322a86d
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [ … ],
"structuredContent": { "id": "filing_1" },
"_meta": {
"cost": { "price": 0, "currency": "USD" },
"requestId": "req_724d327d688bd322a86d"
}
}
}When the Customer accepts, the fee is charged, and the delivery says so.
POST /hooks/ filings HTTP/1.1
Host: hooks.acme.com
X-Webhook-Attempt: 1
X-Webhook-Signature: t= 1793175300, v1= ac668e6901ac0de4e796256b7d3c62e3ac59d85a0230fea22ab0c430b952eab7
{
"id": "evt_11",
"type": "Filing.accepted",
"at": "2026-10-28T08: 15: 00Z",
"seq": 11,
"data": { "id": "filing_1", "status": "accepted" },
"charged": { "price": 25, "currency": "USD" }
}It costs 25 when accepted, the only pattern priced by outcome; a rejected filing is redone at no charge.
{
"currency": "USD",
"rates": [
{ "name": "Filing.order",
"price": 25, "unit": "outcome",
"when": "accepted" }
]
}$ npx apis.dev init service-api
$ npx apis.dev deploy
✓ published v1 to https: //filings.apis.dev
$ curl https: //filings.apis.dev/ filings \
-H "Authorization: Bearer $FILINGS_KEY" \
--json '{"company": "acme.com", "year": 2026}'Built on each other
Every API product is a Source for the next one. Name it by its api, and say what each of its calls costs you.
import { define } from 'apis.dev'
/** Sales leads: a company and its size, priced per lead. */
export default define({
name: 'leads', domain: 'leads.apis.dev',
sources: {
firms: {
api: 'companyfacts.apis.dev', // another API product
cost: { 'Company.get': 0.002, 'Company.search': 0.005 },
},
},
Lead: {
domain: { type: 'url!#', from: 'firms', map: 'domain' },
employees: { type: 'number? ', from: 'firms', map: 'employees' },
},
offer: { 'Lead.get': 0.005, 'Lead.search': 0.0125 },
})Deploy prints your margin on each Verb, to you alone, and refuses a price below its Sources' cost with below_cost.
$ npx apis.dev deploy
✓ Lead.get 0.005 · Source 0.002 · margin 60%
✓ Lead.search 0.0125 · Source 0.005 · margin 60%
✓ published to https: //leads.apis.devYour cost never reaches a Customer: X-Cost carries the price and its currency, and nothing else.