# API

URL: /code/api · Id: `page:code/api`

> How api.sb works, traced through one data business.

The [StartupBuilder](/code/builders/startup) API serves every [Startup](/business/nouns/startup) at [api.sb](/code/platform/api-sb) as plain HTTPS and JSON. Every route is compiled from a [Verb](/business/nouns/verb), so learning one route teaches them all.

## One namespace per Startup
A Startup's namespace is its [Domain](/business/nouns/domain). `openhours.dev` sells verified opening hours for four million storefronts to developers and the coding agents that work for them, at $0.002 per lookup. Every record it owns lives under `https://api.sb/openhours.dev`.

The namespace holds the platform's [Nouns](/business/nouns/noun), such as [Offers](/business/nouns/offer) and [Orders](/business/nouns/order), and the Startup's own. `openhours.dev` declares a `Storefront` Noun, so `/openhours.dev/storefronts` serves its data. `https://api.sb/openhours.dev/openapi.json` describes both, and its [Customers](/business/nouns/customer) generate clients from it.

## Routes are compiled from Verbs
Nobody writes a route by hand. Each Noun gets a collection named by its plural, and each Verb gets a `POST` route named by itself:

| Call | Route | Name |
| --- | --- | --- |
| List storefronts | `GET /openhours.dev/storefronts` | `Storefront.list` |
| Read one Offer | `GET /openhours.dev/offers/offer_1` | `Offer.get` |
| Place an Order | `POST /openhours.dev/orders/place` | `Order.place` |
| Subscribe to an Offer | `POST /openhours.dev/offers/offer_1/subscribe` | `Offer.subscribe` |

A Verb that acts on an existing record takes its id in the path. A Verb that brings a record into being sits on the collection. `GET` on any Verb route previews it and stores nothing.

The operation name is the contract. `Offer.subscribe` is the OpenAPI operationId, the SDK method, the CLI command, the MCP call, and the rate-limit key. Adding a Verb to a Noun adds all five, its [Event](/business/nouns/event), and its docs row in one write, so no [Interface](/business/nouns/interface) lags another.

## Keys
Every request carries a key as a bearer token. The key names the namespace, and the [Permissions](/business/nouns/permission) of its [Role](/business/nouns/role) decide which Verbs it may run. [Limits](/business/nouns/limit) bound how far each one goes.

```bash
curl "https://api.sb/openhours.dev/storefronts?city=Lisbon&limit=20" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
```

A key that starts `sk_test_` reaches the test environment, where no money moves and no message leaves. A `sk_live_` key reaches live. Code moves from one to the other unchanged, as [authentication](/code/api/guides/authentication) and [environments](/code/cli/guides/environments) explain.

## Reads return an envelope
Every response is one JSON object with the same keys. Reading an Offer returns the record under its Noun's name, plus links to read and Verbs to run:

```json
{
  "api": "https://api.sb/openhours.dev",
  "$type": "Offer",
  "$id": "https://api.sb/openhours.dev/offers/offer_1",
  "offer": {
    "$id": "https://api.sb/openhours.dev/offers/offer_1",
    "$type": "Offer",
    "price": { "amount": 0.002, "currency": "USD" },
    "interval": "month",
    "terms": "Verified opening hours for any storefront, billed per lookup."
  },
  "links": {
    "self": "https://api.sb/openhours.dev/offers/offer_1",
    "up": "https://api.sb/openhours.dev/offers"
  },
  "actions": { "subscribe": "https://api.sb/openhours.dev/offers/offer_1/subscribe" }
}
```

`actions` lists only the Verbs this caller may run on this record in its current state. A client that follows `actions` never calls a Verb the record refuses, and an agent never needs the state rules in its prompt. The [envelope](/code/api/guides/envelope) covers lists, cursors, and every key.

## Writes return an Action
Every write is an [Action](/business/nouns/action): a durable record of one Verb, its input, its status, and who ran it for whom. The response carries the Action beside the record it changed:

```bash
curl -X POST https://api.sb/openhours.dev/offers/offer_1/subscribe \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Idempotency-Key: signup-91c2" \
  -H "Content-Type: application/json" \
  -d '{"quantity":1}'
```

```json
{
  "api": "https://api.sb/openhours.dev",
  "action": {
    "$id": "https://api.sb/openhours.dev/actions/action_1",
    "operation": "Offer.subscribe",
    "status": "done"
  },
  "subscription": {
    "$id": "https://api.sb/openhours.dev/subscriptions/subscription_1",
    "$type": "Subscription",
    "status": "active"
  },
  "links": { "self": "https://api.sb/openhours.dev/actions/action_1" }
}
```

The [Mode](/business/nouns/mode) decides when the caller hears back. `try` runs and stores nothing, `send` returns a `pending` Action at once, and `do` waits for the finish. A `pending` Action names its reason, such as `awaitingInput` or `awaitingTime`, so the caller knows whether to wait or act.

An Action over a Limit stays `pending` until its [Approval](/business/nouns/approval) is decided. A retry with the same `Idempotency-Key` returns the first Action and runs nothing. [Modes](/code/api/guides/modes) covers each case.

## Errors name the fix
Every error is an RFC 9457 problem whose `type` is a page on this site. Its `fix` and `next` fields carry the remedy, so an agent recovers without a search. A test subscription to an Offer that is still a draft is refused:

```json
{
  "type": "https://startupbuilder.dev/code/api/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "Offer offer_2 is a draft, so nobody can subscribe to it.",
  "fix": "Publish the Offer, then subscribe again.",
  "next": "https://api.sb/openhours.dev/offers/offer_2/publish",
  "instance": "https://api.sb/openhours.dev/actions/action_2"
}
```

A refused Action keeps its record forever. A retry runs a new Action linked to the old one, and history is never rewritten. [Errors and testing](/code/sdk/guides/errors-and-testing) lists every problem type and how to reproduce it in test.

## Events
Every write emits one Event, named by its Noun and the past tense of its Verb: `Offer.subscribed`, `Order.paid`. A [Watch](/business/nouns/watch) delivers the Events a selector matches to a URL:

```bash
curl -X POST https://api.sb/openhours.dev/watches \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selector":"Offer.subscribed","url":"https://openhours.dev/hooks/signups"}'
```

Each delivery is a signed `POST`, retried until acknowledged and replayable from any point. [Webhooks](/code/api/guides/webhooks) shows how to verify the signature.

The [SDK](/code/sdk) makes the same calls as typed TypeScript.