Skip to content

Define an API product

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, write its Definition as data, from the records of a source or from a schema of Nouns. The platform checks it and derives every Interface from it.

Prerequisites

None: the steps need no account and no install.

Steps

A lookup over the records of a source

Terminal3 steps
  1. To start, describe where the records come from as one Wrapped Source 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.

    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
  2. To serve lookups over the records, write the Definition. Each Operation reads the countries collection and posts its rate: getCountry by code, listCountries by subregion.

    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"] }
        }
      ]
    }
  3. To see what the platform derives, read where the Definition answers on its own host.

    DerivedAt
    API: getCountryhttps://countrylookup.example/countries/{code}
    API: listCountrieshttps://countrylookup.example/countries
    MCPhttps://countrylookup.example/mcp
    Docshttps://countrylookup.example/docs
    SDKhttps://countrylookup.example/sdk
    Apphttps://countrylookup.example/dashboard
    OpenAPI 3.1https://countrylookup.example/openapi.json
    llms.txthttps://countrylookup.example/llms.txt
    Markdown twinhttps://countrylookup.example/docs/{verb}.md
    agents.jsonhttps://countrylookup.example/.well-known/agents.json
    Pricing documenthttps://countrylookup.example/pricing

An API from a schema of Nouns

Terminal5 steps
  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.

    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
  2. To derive the Definition, give each Verb its rate in the Offer. Each Verb derives one operation, named by its Noun and Verb: Place.find answers a list, so it is findPlaces.

    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" }
        }
      ]
    }
  3. To check it, hold it to the rules every Definition passes. 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.

    The problems, one per mistake
    [
      "FindPlaces: a verb is lowerCamelCase",
      "FindPlaces: /docs/places is one of the generated doors"
    ]
  5. To see what the platform derives, read where Openhours answers on its own host.

    DerivedAt
    API: findPlaceshttps://openhours.example/places
    API: getHourshttps://openhours.example/places/{placeId}/hours
    MCPhttps://openhours.example/mcp
    Docshttps://openhours.example/docs
    SDKhttps://openhours.example/sdk
    Apphttps://openhours.example/dashboard
    OpenAPI 3.1https://openhours.example/openapi.json
    llms.txthttps://openhours.example/llms.txt
    Markdown twinhttps://openhours.example/docs/{verb}.md
    agents.jsonhttps://openhours.example/.well-known/agents.json
    Pricing documenthttps://openhours.example/pricing

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