---
title: "Patterns"
url: /patterns
---

# Patterns

> Five kinds of API product you can sell, each priced its own way, and each one file of the same five blocks.

## The grid

| Pattern | Noun: the shape you promise | Verb: what can be done to it | Source: where its data comes from | Trigger: what reacts to a change | Offer: the price |
| --- | --- | --- | --- | --- | --- |
| [Dataset API](/patterns/dataset-api.md): Sell 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 SaaS](/patterns/headless-saas.md): Sell 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 API](/patterns.md#function-api): Sell an answer, priced per call. A Function writes it. | the Noun it acts on | a Function: code, a model, an agent or a person | — | — | a price per call, by kind; a failure is free |
| [Agent API](/patterns.md#agent-api): Sell 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 API](/patterns.md#service-api): Sell an outcome, charged once, when the work is accepted. 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
```ts title="definition.ts"
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](/patterns/dataset-api.md) and the [Headless SaaS](/patterns/headless-saas.md) have pages of their own, and each has a Get started page.

## Answers, workers and outcomes
The other three are each one file here.

### Function API
*The four Function kinds*

| 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](/reference.md#function) sells an answer, and its kind sets how long it takes. `returns` types the output on every [Interface](/every-product.md): the [Action](/workflow.md#the-lifecycle), the MCP `outputSchema`, OpenAPI and the SDK. In a prompt, `{domain}` reads a field and `{company.name}` follows a [Relationship](/patterns/headless-saas.md).

```ts title="definition.ts"
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.

**summarize**

```sh title="Request"
curl -X POST https://summaries.apis.dev/companies/acme.com/summarize \
  -H "Authorization: Bearer $SUMMARIES_KEY"
```

```http title="Response"
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." }
}
```

**research**

```sh title="Request"
curl -X POST https://summaries.apis.dev/companies/acme.com/research \
  -H "Authorization: Bearer $SUMMARIES_KEY"
```

```http title="Response"
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"
}
```

**get**

```sh title="Request"
curl https://summaries.apis.dev/actions/act_2 \
  -H "Authorization: Bearer $SUMMARIES_KEY"
```

```http title="Response"
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"
}
```

**translate**

```sh title="Request"
curl https://summaries.apis.dev/texts/translate \
  -H "Authorization: Bearer $SUMMARIES_KEY" \
  --json '{"text":"Two sentences on Acme.","to":"fr"}'
```

```http title="Response"
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.

```sh title="Terminal"
$ 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
*One run, metered once · USD*

| 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](/patterns/dataset-api.md#how-a-field-is-filled). Its schedule runs once per Customer, in that Customer's timezone, and a `Max-Cost` on the run stops it at the ceiling.

```ts title="definition.ts"
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**

```sh title="Request"
curl https://meetings.apis.dev/actions/act_31 \
  -H "Authorization: Bearer $MEETINGS_KEY"
```

**TypeScript**

```ts title="TypeScript"
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' }
```

**Python**

```python title="Python"
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" }
```

**CLI**

```sh title="Terminal"
$ npx meetings login --key $MEETINGS_KEY
$ npx meetings actions get act_31
{ "id": "act_31", "verb": "Meeting.book", "status": "done", … }
price 0 USD · req_24fbbad973aa8085d69b
```

**MCP**

```sh title="Request"
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 title="Response"
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"
    }
  }
}
```


```http title="Response"
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" }
}
```

Each run is billed by consumption: 0.01 per 1,000 tokens and 0.05 a minute, metered once. Reads cost nothing.

```json title="/pricing"
{
  "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" }
  ]
}
```


```sh title="Terminal"
$ 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
```mermaid title="Filing: its lifecycle"
stateDiagram-v2
  [*] --> ordered: order
  ordered --> drafting: draft
  drafting --> drafted: done
  drafted --> signed: sign
  signed --> filing: file
  filing --> filed: done
  filed --> accepted: accept · 25
  filed --> rejected: reject
  rejected --> ordered: redo, free
```

A Service API sells an outcome: a work Thing the buyer watches move through its lifecycle, under an [Outcome contract](/reference.md#outcome-contract). It is charged once, on the move the contract names, and a rejected one gets its remedy.

```ts title="definition.ts"
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**

```sh title="Request"
curl -X POST https://filings.apis.dev/filings/filing_1/sign \
  -H "Authorization: Bearer $FILINGS_KEY"
```

**TypeScript**

```ts title="TypeScript"
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' }
```

**Python**

```python title="Python"
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" }
```

**CLI**

```sh title="Terminal"
$ npx filings login --key $FILINGS_KEY
$ npx filings filings sign filing_1
{ "id": "filing_1", "company": "acme.com", "year": 2026, … }
price 0 USD · req_47d0a7b035cb42c3265d
```

**MCP**

```sh title="Request"
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 title="Response"
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"
    }
  }
}
```


```http title="Response"
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"
}
```

When the Customer accepts, the fee is charged, and the delivery says so.

```http title="Delivery"
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.

```json title="/pricing"
{
  "currency": "USD",
  "rates": [
    { "name": "Filing.order",
      "price": 25, "unit": "outcome",
      "when": "accepted" }
  ]
}
```


```sh title="Terminal"
$ 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.

```ts title="definition.ts"
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`](/reference.md#below_cost).

```sh title="Terminal"
$ 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.dev
```

Your cost never reaches a Customer: `X-Cost` carries the price and its currency, and nothing else.