---
title: "UI contracts and the headless-parity test"
url: /interfaces/ui-contracts
---

# UI contracts and the headless-parity test

> What any UI renders, generated from the file, and a test that an agent finishes every path a UI offers with no UI.

The UI contracts are what any screen renders: six answers for every principal and record, generated from the file. The headless-parity test has an agent finish every path a UI offers with no UI.

**One contract, two readers.**

- `definition.ts` compiles to six answers for one principal and one record.
- They are the state, what they may see, allowed moves with schemas, refused moves with cures, who else can act, and a live subscription.
- Any UI and an agent with no UI both read them, and both reach the record through the same Verbs and one gate.
- The parity test runs one path through both readers. The UI contracts add the app map, views, forms, settings, onboarding, brand and empty reasons.

## Capability families
APIs.Dev specifies no App, client portal or delivery screen. It specifies what each one reads, so any UI, from any builder, renders the product without a private API. The landing site, the developer portal and the docs are [specified as products](/interfaces/site-portal-docs.md); every other UI gets these contracts.

| Family | What it gives every product |
| --- | --- |
| The affordance contract | Six answers for any principal and record: the state; what they may see; the moves allowed, each with its input schema and price; the moves refused, each with its cure; who else can act; and a live subscription |
| The app map | What a member can reach, in order: the Nouns, views and settings their Role opens, generated from the file |
| Views | Saved filters, sorts and groupings as data, each answered over every [Interface](/every-product.md) with its count |
| Forms | Per Verb: fields, order, defaults, validation and help, from its input schema. A form never asks for what the Verb does not take |
| Settings | The account's own settings as Verbs: members, Roles, plan, [Integrations](/patterns/headless-saas.md#integrations), preferences |
| Onboarding as data | The first steps as a checklist of Verbs, each ticked when its Event arrives, the same for a person and an agent |
| Branding tokens | Name, logo, colours and type from `brand`, for any UI, email and receipt |
| A reason for every empty state | Every empty list or view says why (none yet, filtered out, hidden by Role, not in the plan) and what fills it |
| Contracts per pattern | Member contracts ([Headless SaaS](/patterns/headless-saas.md), Content), client and delivery contracts ([Service API](/patterns/service-api.md)), storefront and supplier contracts (Marketplace): the six answers, plus the pattern's records |
| The headless-parity test | Every path a UI offers, finished by an agent through the API or MCP and kept in the proof record. A UI that has to invent a state, a move or a reason fails it, and marks a capability the product lacks |

### The six paths
The suite runs each path at every deploy, in the [Test twin](/reference/platform.md#test-twin), with an agent and no UI.

| Path | Who | What it covers |
| --- | --- | --- |
| An admin's setup | A Customer's admin | Create the org, invite, set Roles, connect single sign-on and an Integration |
| A member's day | A member | Create, relate, search, move a record, restore one, export |
| A client's matter | A buyer, or its agent | Order, approve the quote, the reserved act by link, accept or contest, pay |
| A worker's shift | A worker | Claim a task, complete it, hand it off, mark a booking attended |
| A supplier's order | A supplier, or its agent | Apply, pass verification, publish an item, claim, fill, read the payout |
| A buyer's purchase | Another product's agent | Find, evaluate, pay, invoke, prove, contest |

## Lifecycles and states
| Thing | States | What moves it |
| --- | --- | --- |
| A contract, per principal and record | `current`, then `changed` and `current` again | The record's state, the principal's Role, entitlement or mandate, or a new [Version](/reference/platform.md#version); each change goes out on the subscription |
| A live subscription | `open`, `behind`, `closed` | The client; one that falls behind resumes from its last `seq` |
| An onboarding checklist | `started`, then `complete` or `dismissed` | Each step's Event; the member |
| A path in the suite | `defined`, `running`, then `passed` or `failed` | Each deploy in the Test twin; a failure names the step and the missing capability |
| A UI's conformance | `checked`, then `conforming` or `inventing` | The check of each screen against the contracts; `inventing` names the screen and what it made up |

A contract is never cached past a change: the move that changes a record's state changes every principal's allowed moves in the same commit.

## What you declare, what the platform does
The six answers need nothing declared: the platform builds them from the Nouns, lifecycles, Roles, Offers and mandates.

`views` answer as data, each with its count and an empty reason. `ui.app` orders the app map, `ui.forms` sets field order and help, `ui.onboarding` lists the checklist's Verbs, and `ui.empty` gives a view its reason.

`brand` styles any UI, email and receipt. `parity` adds paths beyond the six, and a deploy that breaks one is refused.

### Example
`pipeline` names its app map, a form, onboarding and the reason its open view can be empty.

```ts title="definition.ts"
import { define } from 'apis.dev'

/** Deals for each Customer, kicked off when won. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  roles: { rep: { see: { Deal: 'own' } }, manager: { has: 'rep', see: { Deal: 'all' } } },
  Company: { name: 'string', domain: 'url!#', deals: '<-Deal.company' },
  Deal: {
    title: 'string', amount: 'decimal(12,2)', company: '->Company.deals',
    stage: { open: [], qualify: 'opened', lose: ['opened', 'qualified'],
             win: { in: 'qualified', role: 'manager', approve: { above: { amount: 50000 } } } },
  },
  views: { open: { Deal: { stage: ['opened', 'qualified'] }, sort: '-amount' } },
  ui: {
    app: ['views.open', 'Company', 'settings'],           // the app map
    forms: { 'Deal.open': ['title', 'amount', 'company'] },
    onboarding: ['Company.create', 'Deal.open', 'crm.connect'],
    empty: { open: 'No open Deals. Open one, or connect your CRM to bring yours in.' },
  },
  offer: { Team: { month: 49, seats: 5, seat: 9 }, 'Deal.open': 0.001 },
})
```

Dana is a rep. Her contract for a qualified Deal of 64,000: she may lose it but not win it, and a manager's win waits for approval.

```json title="GET /deals/deal_1/affordances · as dana@acme.com"
{
  "record": "deal_1",
  "state": "qualified",
  "see": { "fields": ["title", "amount", "company", "stage"], "hidden": [] },
  "allowed": [
    { "verb": "Deal.lose", "price": 0,
      "input": { "type": "object", "properties": { "reason": { "type": "string" } } } }
  ],
  "refused": [
    { "verb": "Deal.win", "code": "role_required",
      "cure": "A manager wins a Deal: sam@acme.com" }
  ],
  "others": [
    { "principal": "sam@acme.com", "role": "manager", "can": ["Deal.win", "Deal.lose"],
      "pending": { "Deal.win": "approval_required" } }
  ],
  "live": "/deals/deal_1/live",
  "seq": 41
}
```

The suite runs at deploy, and checks a screen built on the product against the same contracts.

```sh title="Terminal"
$ npx apis.dev test --parity
✓ an admin's setup     9 moves over MCP, no UI
✓ a member's day      14 moves over MCP, no UI
✕ screen 'Deals board', column 'stalled': no state, view or metric defines it
```

A `stalled` view in the file fixes it; a column that only the screen knows does not ship.

## Contracts with the other subsystems
The contracts decide nothing. Each answer is a question the other subsystems already answer, asked for one principal and one record.

- **Definition and compile** checks `views`, `ui` and `brand` at deploy, and refuses an app map entry or onboarding step that names no Verb or view.
- **Identity, access and policy** answers the one decision for every move, masks fields and records, and names who else holds a Role or mandate.
- **Model and records** gives the record, its state, history, views and metrics, and never shows what the Role hides.
- **Runtime** feeds Events into the live subscription, with the `seq` a client resumes from.
- **Work** gives tasks, turns and a case's seen states, which the client, delivery and supplier contracts render.
- **Commerce** prices each move, and a refused move's cure names the plan or payment that lifts it.
- **Interfaces and distribution** serves one contract over the API, MCP and the SDK, and renders the in-app [channel](/interfaces/channels.md) from the record's conversation.
- **Trust and operations** keeps each path's result per Version, and each UI's conformance, in the proof record.

## What Customers and their agents meet
- **A screen that cannot lie.** Every button is an allowed move, every greyed one carries its cure, and every empty list says why.
- **No private API.** The App, a client portal and a supplier's screen read the contracts an agent reads. Anything a UI shows, an agent can fetch.
- **An agent that finishes the job.** An agent reads the contract, makes the allowed move, and follows the subscription. It never needs a screen.
- **Onboarding that counts what happened.** A checklist ticks itself when its Event arrives, whether a person or an agent made the move.
- **The same answer everywhere.** A refusal on a screen, over MCP and in a 403 carries the same code and the same cure.

---

Any UI is a view of the contracts, so whatever a person can do on a screen, an agent can do without one.