# Define an API product

URL: /quickstart · Id: `page:/quickstart`

> Define an API product from the records of a source or from a schema of Nouns, check it, and see every Interface the platform derives from it.

To define an API [Product](/define/product), write its [Definition](/reference/concepts#definition) as data, from the records of a source or from a schema of Nouns. The platform checks it and derives every [Interface](/interfaces) from it.

## Prerequisites
None: the steps need no account and no install.

## Steps
### A lookup over the records of a source
1. To start, describe where the records come from as one [Wrapped Source](/define/sources) descriptor. Countrylookup reads one dataset file, and its transform shapes each record as data: `select` picks and renames a field, and `coerce` sets its type.

   {/* registry:view sample-source countries */}

   ```yaml title="countrylookup.example: the descriptor"
   slug: countries
   title: Countrylookup
   description: Countries by ISO code, read from one public dataset file.
   origin: https://atlas.example
   resources:
     countries:
       list:
         path: /countries.json
         method: GET
         extract:
           itemId: cca2
         transform:
           - { op: select, path: cca2, as: code }
           - { op: select, path: name.common, as: name }
           - { op: select, path: capital.0, as: capital }
           - { op: select, path: subregion }
           - { op: select, path: area }
           - { op: coerce, field: area, to: number }
   schedule:
     cron: 0 6 * * 1
   tenancy:
     owner: countrylookup.example
     visibility: third-party
   discovery:
     home: https://countrylookup.example
   ```

   {/* registry:end */}

2. To serve lookups over the records, write the Definition. Each [Operation](/define/operations) reads the `countries` collection and posts its rate: `getCountry` by code, `listCountries` by subregion.

   {/* registry:view sample-definition countrylookup */}

   ```json title="countrylookup.example: the Definition"
   {
     "startup": "countrylookup.example",
     "product": "countrylookup",
     "name": "Countrylookup",
     "description": "Countries by ISO code or subregion, with name, capital and area.",
     "version": "1.0.0",
     "currency": "USD",
     "free": 1000,
     "operations": [
       {
         "verb": "getCountry",
         "method": "GET",
         "path": "/countries/{code}",
         "summary": "One country",
         "description": "Answers one country by its ISO 3166-1 alpha-2 code.",
         "params": [
           {
             "name": "code",
             "in": "path",
             "type": "string",
             "required": true,
             "description": "The upper-case alpha-2 code",
             "example": "FR"
           }
         ],
         "returns": {
           "type": "object",
           "properties": {
             "code": { "type": "string" },
             "name": { "type": "string" },
             "capital": { "type": "string" },
             "subregion": { "type": "string" },
             "area": { "type": "number" }
           }
         },
         "example": {
           "input": { "code": "FR" },
           "output": {
             "code": "FR",
             "name": "France",
             "capital": "Paris",
             "subregion": "Western Europe",
             "area": 551695
           }
         },
         "price": 0.001,
         "run": { "kind": "get", "collection": "countries", "key": "code" }
       },
       {
         "verb": "listCountries",
         "method": "GET",
         "path": "/countries",
         "summary": "Countries in one subregion",
         "description": "Lists every country in one subregion, each with its code.",
         "params": [
           {
             "name": "subregion",
             "in": "query",
             "type": "string",
             "required": true,
             "description": "A subregion as the records name it",
             "example": "Western Europe"
           }
         ],
         "returns": {
           "type": "array",
           "items": {
             "type": "object",
             "properties": { "code": { "type": "string" }, "name": { "type": "string" } }
           }
         },
         "example": {
           "input": { "subregion": "Western Europe" },
           "output": [{ "code": "FR", "name": "France" }, { "code": "NL", "name": "Netherlands" }]
         },
         "price": 0.005,
         "run": { "kind": "list", "collection": "countries", "filters": ["subregion"] }
       }
     ]
   }
   ```

   {/* registry:end */}

3. To see what the platform derives, read where the Definition answers on its own host.

   {/* registry:view sample-interfaces countrylookup */}

   | Derived | At |
   | --- | --- |
   | API: `getCountry` | `https://countrylookup.example/countries/{code}` |
   | API: `listCountries` | `https://countrylookup.example/countries` |
   | MCP | `https://countrylookup.example/mcp` |
   | Docs | `https://countrylookup.example/docs` |
   | SDK | `https://countrylookup.example/sdk` |
   | App | `https://countrylookup.example/dashboard` |
   | OpenAPI 3.1 | `https://countrylookup.example/openapi.json` |
   | llms.txt | `https://countrylookup.example/llms.txt` |
   | Markdown twin | `https://countrylookup.example/docs/{verb}.md` |
   | agents.json | `https://countrylookup.example/.well-known/agents.json` |
   | Pricing document | `https://countrylookup.example/pricing` |

   {/* registry:end */}

### An API from a schema of Nouns
1. To start from a schema, write the Nouns, their fields and the Verbs on them. A field is a type, `!` marks a required field, and `->` links another Noun.

   {/* registry:view sample-schema openhours */}

   ```yaml title="openhours.example: the Nouns and Verbs"
   nouns:
     Place:
       placeId: string!
       name: string
       city: string
     Hours:
       placeId: ->Place
       timezone: string
       open: "[Span]"
   verbs:
     Place.find:
       answers: list
       by: city
       path: /places
     Hours.get:
       answers: one
       by: placeId
       path: /places/{placeId}/hours
   ```

   {/* registry:end */}

2. To derive the Definition, give each Verb its rate in the [Offer](/define/offers). Each Verb derives one operation, named by its Noun and Verb: `Place.find` answers a list, so it is `findPlaces`.

   {/* registry:view sample-definition openhours */}

   ```json title="openhours.example: the Definition"
   {
     "startup": "openhours.example",
     "product": "openhours",
     "name": "Openhours",
     "description": "Opening hours for places, by place id.",
     "version": "1.0.0",
     "currency": "USD",
     "free": 1000,
     "requiresKey": true,
     "operations": [
       {
         "verb": "findPlaces",
         "method": "GET",
         "path": "/places",
         "summary": "Places in one city",
         "description": "Lists the places Openhours holds in one city, by the city name, each with its place id.",
         "params": [
           {
             "name": "city",
             "in": "query",
             "type": "string",
             "required": true,
             "description": "The city name",
             "example": "Lisbon"
           }
         ],
         "returns": {
           "type": "array",
           "items": {
             "type": "object",
             "properties": { "placeId": { "type": "string" }, "name": { "type": "string" } }
           }
         },
         "example": {
           "input": { "city": "Lisbon" },
           "output": [{ "placeId": "pl_4821", "name": "Cafe Alfama" }]
         },
         "price": 0.002,
         "run": { "kind": "list", "collection": "places", "filters": ["city"] }
       },
       {
         "verb": "getHours",
         "method": "GET",
         "path": "/places/{placeId}/hours",
         "summary": "The opening hours of one place",
         "description": "Answers the weekly opening hours of one place, in the place's own timezone.",
         "params": [
           {
             "name": "placeId",
             "in": "path",
             "type": "string",
             "required": true,
             "description": "The place id",
             "example": "pl_4821"
           }
         ],
         "returns": {
           "type": "object",
           "properties": {
             "placeId": { "type": "string" },
             "timezone": { "type": "string" },
             "open": { "type": "array" }
           }
         },
         "example": {
           "input": { "placeId": "pl_4821" },
           "output": {
             "placeId": "pl_4821",
             "timezone": "Europe/Lisbon",
             "open": [{ "day": "mon", "from": "09:00", "to": "18:00" }]
           }
         },
         "price": 0.002,
         "run": { "kind": "get", "collection": "hours", "key": "placeId" }
       }
     ]
   }
   ```

   {/* registry:end */}

3. To check it, hold it to the [rules every Definition passes](/define/operations#checks). The platform runs the same check when a Definition is written and again when it is read, so a Definition with a problem never serves.

4. To see what the check answers, break two rules: capitalize the first verb and move its path under `/docs`. The check names each mistake, one line per problem.

   {/* registry:view sample-problems openhours */}

   ```json title="The problems, one per mistake"
   [
     "FindPlaces: a verb is lowerCamelCase",
     "FindPlaces: /docs/places is one of the generated doors"
   ]
   ```

   {/* registry:end */}

5. To see what the platform derives, read where Openhours answers on its own host.

   {/* registry:view sample-interfaces openhours */}

   | Derived | At |
   | --- | --- |
   | API: `findPlaces` | `https://openhours.example/places` |
   | API: `getHours` | `https://openhours.example/places/{placeId}/hours` |
   | MCP | `https://openhours.example/mcp` |
   | Docs | `https://openhours.example/docs` |
   | SDK | `https://openhours.example/sdk` |
   | App | `https://openhours.example/dashboard` |
   | OpenAPI 3.1 | `https://openhours.example/openapi.json` |
   | llms.txt | `https://openhours.example/llms.txt` |
   | Markdown twin | `https://openhours.example/docs/{verb}.md` |
   | agents.json | `https://openhours.example/.well-known/agents.json` |
   | Pricing document | `https://openhours.example/pricing` |

   {/* registry:end */}

## Result
Countrylookup and Openhours are each one Definition that passes every check. Every Interface reads that one Definition, so no two of them can disagree, and a call that misses or fails costs nothing.

## Next steps
- [Wrapped Source](/define/sources): every member of a descriptor.
- [Product](/define/product): every member of a Definition.
- [Offers](/define/offers): the posted rates and the free calls.
- [Your API on your own domain](/publish/hosting): where every Interface answers.