---
title: "Dataset API"
url: /patterns/dataset-api
---

# Dataset API

> Sell the facts about a thing, priced per answer.

```json title="firms.example.com answers"
{
  "website": "acme.com",
  "headcount": 240,
  "industry": "Software"
}
```

```http title="companyfacts.apis.dev/companies/acme.com?include=provenance"
HTTP/1.1 200 OK
X-Cost: price=0.002, currency="USD"

{
  "domain": "acme.com",
  "employees": 240,
  "sector": "Software",
  "provenance": {
    "domain": {
      "basis": "Observed",
      "writer": "firms",
      "at": "2026-10-19T06:00:00Z"
    },
    "employees": {
      "basis": "Observed",
      "writer": "firms",
      "at": "2026-10-19T06:00:00Z"
    },
    "sector": {
      "basis": "Observed",
      "writer": "firms",
      "at": "2026-10-19T06:00:00Z"
    }
  }
}
```

## How a field is filled
It is not a proxy. A [Source](/reference.md#source) answers in its own shape, a [Mapping](/reference.md#mapping) reads each field into yours, and each value carries its [Provenance](/reference.md#provenance).

```ts title="firms: the Source"
firms: {
  origin: 'https://firms.example.com',
  key: secret('FIRMS_KEY'),           // set with npx apis.dev secrets set
  resources: { companies: { detail: '/companies/{website}' } },
  schedule: 'every Mon 06:00',        // Trigger: read again each Monday
  license: 'CC-BY-4.0',
},
```

The credential is a secret you set once, never a line of the file, and no Customer ever sees it. A value is a kept copy read again on its schedule, or a live answer through a cache. A ranked `from` tries each writer in order until one answers.

```ts title="definition.ts"
    employees: { type: 'number?', from: 'firms', map: 'headcount' },  // [!code --]
    employees: { type: 'number?', map: 'headcount',  // [!code ++]
      from: ['firms', agentic('Find headcount for {domain}')] },  // [!code ++]
```

A Source's write is Observed, an agent's Inferred, and Observed wins for facts. So the agent's guess at headcount stands until `firms` observes the real one.

A Source holds only the members its grammar names: an unknown one is refused on publish.

## What your Customers call
A get by the `#` key, and a search by any field, on every [Interface](/every-product.md).

**curl**

```sh title="Request"
curl https://companyfacts.apis.dev/companies?sector=Software \
  -H "Authorization: Bearer $COMPANYFACTS_KEY"
```

**TypeScript**

```ts title="TypeScript"
import { Companyfacts } from 'companyfacts'

const api = new Companyfacts({ key: process.env.COMPANYFACTS_KEY })
const { data, meta } = await api.searchCompanies({ sector: 'Software' })
// data: { 'items': [{ 'domain': 'acme.com', 'employees': 240, … }
// meta.cost: { price: 0.005, currency: 'USD' }
```

**Python**

```python title="Python"
import os
from companyfacts import Companyfacts

api = Companyfacts(key=os.environ["COMPANYFACTS_KEY"])
data, meta = api.search_companies(sector="Software")
# data: { "items": [{ "domain": "acme.com", "employees": 240, … }
# meta.cost: { "price": 0.005, "currency": "USD" }
```

**CLI**

```sh title="Terminal"
$ npx companyfacts login --key $COMPANYFACTS_KEY
$ npx companyfacts companies search --sector Software
{ "items": [{ "domain": "acme.com", "employees": 240, … }
price 0.005 USD · req_1e9cb0ffe275994adbe7
```

**MCP**

```sh title="Request"
curl https://companyfacts.apis.dev/mcp \
  -H "Authorization: Bearer $COMPANYFACTS_KEY" \
  --json '{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "searchCompanies", "arguments": { "sector": "Software" } }
}'
```

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

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [ … ],
    "structuredContent": {
      "items": [
        { "domain": "acme.com", "employees": 240, "sector": "Software" }
      ],
      "total": 1,
      "next": null
    },
    "_meta": {
      "cost": { "price": 0.005, "currency": "USD" },
      "requestId": "req_3f16f46aec098bc51cd7"
    }
  }
}
```


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

{
  "items": [{ "domain": "acme.com", "employees": 240, "sector": "Software" }],
  "total": 1,
  "next": null
}
```

## The price
A price per answer, a Watch on one Company by the month, free calls each month, and the credit packs Checkout sells.

```ts title="definition.ts · offer"
  offer: { 'Company.get': 0.002, 'Company.search': 0.005 },  // [!code --]
  offer: {  // [!code ++]
    'Company.get': 0.002, 'Company.search': 0.005,  // [!code ++]
    'Company.watch': { month: 0.1 },  // [!code ++]
    free: { 'Company.get': 100 },  // [!code ++]
    credits: [10, 50, { pay: 200, get: 220 }],  // [!code ++]
  },  // [!code ++]
```

A miss costs nothing. Each refresh writes `Company.refreshed`, so a Customer's Watch on `employees` delivers each change it makes.

| 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 |

[Start a Dataset API](/quickstart/dataset-api.md) →