{"docsVersion":"0.1","baseUrl":"https://rapidui.dev","rui":{"fileExtension":".rui.json","description":"A RUI is a JSON document describing an app — its screens, blocks, and data bindings. Not React code."},"links":{"llmsTxt":"/llms.txt","schema":"/api/schema","validate":"/api/validate","specs":"/api/specs"},"sections":[{"id":"overview","format":"markdown","content":"# What RapidUI is\n\nRapidUI is an **agent-first platform** for generating, validating, and storing **RUIs** — JSON documents that describe an app's screens, blocks, and data bindings.\n\nA **RUI** (`.rui.json`) is **not** React code, not a component library, and not a rendered UI. It is a structured specification that agents produce and the platform validates.\n\n## v0.1 scope\n\n- **In scope:** Read-only bindings (`GET`), blocks `Metric`, `Table`, `Text`, layout `Page` → `Section` → block, validation API, vocabulary schema\n- **Out of scope:** Renderer, live API execution, write/action bindings, auth, MCP server\n\n## Naming: RUI vs spec\n\nUse **RUI** in all prose when referring to the JSON artifact. The HTTP path **`/api/specs`** stores a validated RUI — **a spec is a stored RUI**. Request bodies for validate and store use the raw RUI JSON shape, not a wrapper like `{ \"rui\": … }`.\n\n## Base URL\n\nProduction: `https://rapidui.dev`\n\nStart with `GET /llms.txt` or `GET /api/docs`, then `GET /api/schema` for the full vocabulary.\n"},{"id":"workflow","format":"markdown","content":"# Agent workflow\n\nFollow this loop to produce a valid RUI without out-of-band instructions.\n\n## 1. Discover\n\n```http\nGET /llms.txt\nGET /api/docs\nGET /api/schema\n```\n\nFetch the vocabulary before authoring. Do not guess block shapes or binding rules.\n\n## 2. Author a RUI\n\nWrite a `.rui.json` document in memory or as a file. Use only blocks and bindings listed in `/api/schema` for v0.1.\n\nStructure: `Page` → `Section` → `Metric` | `Table` | `Text`.\n\n## 3. Validate (retry loop)\n\n```http\nPOST /api/validate\nContent-Type: application/json\n\n<RUI JSON body>\n```\n\n- On **`valid: true`** — use `normalizedRui` as the canonical artifact\n- On **`valid: false`** — read `errors[]` (each has `code`, `message`, `hint`, `path`); fix and retry\n- Target: converge within **≤5 retries**\n\n### Success response (HTTP 200)\n\n```json\n{\n  \"valid\": true,\n  \"validationVersion\": \"0.1\",\n  \"registryVersion\": \"0.1\",\n  \"normalizedRui\": { }\n}\n```\n\n### Validation failed (HTTP 200)\n\n```json\n{\n  \"valid\": false,\n  \"validationVersion\": \"0.1\",\n  \"registryVersion\": \"0.1\",\n  \"errors\": [\n    {\n      \"path\": \"pages[0].children[0].binding\",\n      \"code\": \"MISSING_VALUE_PATH\",\n      \"message\": \"Metric binding requires valuePath.\",\n      \"hint\": \"Set valuePath to the scalar field (e.g. \\\"openCount\\\").\"\n    }\n  ],\n  \"truncated\": false\n}\n```\n\n### Transport failure (HTTP 400)\n\nInvalid JSON, wrong Content-Type, or body too large → HTTP 400 with `INVALID_JSON` (no `validationVersion`).\n\n## 4. Save\n\n```http\nPOST /api/specs\nContent-Type: application/json\n\n<RUI JSON body>\n```\n\nRe-validates inline — you may POST directly without a prior validate call. Invalid RUI never reaches Postgres.\n\n### Success (HTTP 201)\n\n```json\n{\n  \"specId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"url\": \"https://rapidui.dev/api/specs/550e8400-e29b-41d4-a716-446655440000\",\n  \"viewUrl\": \"https://rapidui.dev/specs/550e8400-e29b-41d4-a716-446655440000\",\n  \"createdAt\": \"2026-05-26T12:00:00.000Z\",\n  \"contentHash\": \"sha256:…\",\n  \"validationVersion\": \"0.1\",\n  \"registryVersion\": \"0.1\",\n  \"normalizedRui\": { }\n}\n```\n\nFlat SavedSpec — no nested `receipt`. Share **`viewUrl`** with the user for human review; use `url` for programmatic retrieve (`GET url` returns the same SavedSpec shape).\n\n### Validation failed (HTTP 200)\n\nSame shape as `POST /api/validate` — fix `errors[]` and retry.\n\n### Storage unavailable (HTTP 503)\n\n```json\n{\n  \"error\": \"STORAGE_UNAVAILABLE\",\n  \"message\": \"RUI store is temporarily unavailable.\"\n}\n```\n\n## Demo prompt (Option A)\n\n> Generate a RUI for an internal support dashboard. Bind to `GET /api/tickets` (ticket list) and `GET /api/tickets/stats` (open and urgent counts).\n\nSee the `examples.supportDashboard` section in `/api/docs` for the golden reference RUI.\n"},{"id":"nesting","format":"markdown","content":"# Nesting rules\n\nRUIs use a fixed three-level hierarchy. Sections cannot nest inside sections.\n\n```txt\nRUI (root)\n├── navigation.items[]     → pageId links to pages[].id\n└── pages[]\n    └── Page\n        └── children[]     → Section only\n            └── Section\n                └── children[]   → Metric | Table | Text only\n```\n\n## Rules\n\n| Level | Allowed children |\n|-------|------------------|\n| `Page` | `Section` only |\n| `Section` | `Metric`, `Table`, `Text` only |\n| Block | None (leaf nodes) |\n\n## IDs\n\n- Every `Page`, `Section`, and block needs a unique `id`\n- Format: lowercase kebab-case (`^[a-z][a-z0-9-]*$`, 1–64 chars)\n- Example: `table-tickets`, `metric-open`\n\n## Navigation\n\n- Every page must appear in `navigation.items` with matching `pageId`\n- Every navigation item must reference an existing page\n\nFetch `GET /api/schema` for full prop definitions per node type.\n"},{"id":"api","format":"json","content":{"validate":{"method":"POST","path":"/api/validate","url":"https://rapidui.dev/api/validate","contentType":"application/json","body":"Raw RUI JSON (version 0.1)","maxBodyBytes":262144,"responses":{"success":{"httpStatus":200,"shape":{"valid":true,"validationVersion":"0.1","registryVersion":"0.1","normalizedRui":"<canonical RUI object>"}},"validationFailed":{"httpStatus":200,"shape":{"valid":false,"validationVersion":"0.1","registryVersion":"0.1","errors":[{"path":"string","code":"string","message":"string","hint":"string"}],"truncated":false},"notes":"Semantic failures return HTTP 200 with valid: false. Fix errors[] and retry."},"transportFailure":{"httpStatus":400,"shape":{"valid":false,"errors":[{"path":"","code":"INVALID_JSON","message":"...","hint":"..."}]},"notes":"Invalid JSON, wrong Content-Type, empty body, or body > 256 KB."}}},"specs":{"method":"POST","path":"/api/specs","url":"https://rapidui.dev/api/specs","contentType":"application/json","body":"Raw RUI JSON (version 0.1) — same shape as POST /api/validate","maxBodyBytes":262144,"responses":{"success":{"httpStatus":201,"shape":{"specId":"UUID","url":"https://rapidui.dev/api/specs/{specId}","viewUrl":"https://rapidui.dev/specs/{specId}","createdAt":"ISO 8601 UTC","contentHash":"sha256:…","validationVersion":"0.1","registryVersion":"0.1","normalizedRui":"<canonical RUI object>"},"notes":"Flat SavedSpec — no nested receipt. Re-validates inline; stores normalizedRui only. Share viewUrl with humans for block-tree review."},"validationFailed":{"httpStatus":200,"shape":{"valid":false,"validationVersion":"0.1","registryVersion":"0.1","errors":[{"path":"string","code":"string","message":"string","hint":"string"}],"truncated":false},"notes":"Same as POST /api/validate — fix errors[] and retry."},"transportFailure":{"httpStatus":400,"shape":{"valid":false,"errors":[{"path":"","code":"INVALID_JSON","message":"...","hint":"..."}]}},"storageUnavailable":{"httpStatus":503,"shape":{"error":"STORAGE_UNAVAILABLE","message":"RUI store is temporarily unavailable."}}},"notes":"A spec is a stored RUI. url is the API retrieve link; viewUrl is the human inspector (§5)."},"specById":{"method":"GET","path":"/api/specs/:id","url":"https://rapidui.dev/api/specs/{specId}","responses":{"success":{"httpStatus":200,"shape":{"specId":"UUID","url":"https://rapidui.dev/api/specs/{specId}","viewUrl":"https://rapidui.dev/specs/{specId}","createdAt":"ISO 8601 UTC","contentHash":"sha256:…","validationVersion":"0.1","registryVersion":"0.1","normalizedRui":"<canonical RUI object>"},"notes":"Same flat SavedSpec as POST 201. url and viewUrl are recomputed on every GET."},"notFound":{"httpStatus":404,"shape":{"error":"NOT_FOUND","specId":"UUID"}},"invalidSpecId":{"httpStatus":400,"shape":{"error":"INVALID_SPEC_ID","message":"specId must be a UUID."}},"storageUnavailable":{"httpStatus":503,"shape":{"error":"STORAGE_UNAVAILABLE","message":"RUI store is temporarily unavailable."}}}},"inspector":{"method":"GET","path":"/specs/:id","url":"https://rapidui.dev/specs/{specId}","notes":"Human RUI inspector — server-rendered type-colored block tree. Public; no auth in v0.1."}}},{"id":"errors","format":"json","content":[{"code":"INVALID_JSON","message":"Request body must be valid JSON.","hint":"Send Content-Type: application/json with the spec object as the raw body."},{"code":"VERSION_MISMATCH","message":"RUI version must be \"0.1\".","hint":"Set `version` to `\"0.1\"` to match the registry."},{"code":"DUPLICATE_ID","message":"Duplicate node id \"{id}\".","hint":"Each Page, Section, and block id must be unique across the entire spec."},{"code":"INVALID_ID_FORMAT","message":"Invalid id \"{id}\".","hint":"Use lowercase kebab-case: `^[a-z][a-z0-9-]*$`, 1–64 chars (e.g. `table-tickets`)."},{"code":"UNKNOWN_TYPE","message":"Unknown node type \"{type}\".","hint":"Use Page, Section, Metric, Table, or Text for v0.1."},{"code":"MISSING_REQUIRED_PROP","message":"Missing required property \"{prop}\".","hint":"Add the property per the spec shape (top-level: `version`, `meta`, `navigation`, `pages`; see GET /api/schema)."},{"code":"INVALID_PROP_TYPE","message":"Invalid value for \"{prop}\".","hint":"Check type and allowed enum values in the schema."},{"code":"UNKNOWN_PROP","message":"Unknown property \"{prop}\".","hint":"Remove extra properties; v0.1 uses strict schemas."},{"code":"EMPTY_PAGES","message":"RUI must include at least one page.","hint":"Add a `pages` array with one or more Page nodes."},{"code":"EMPTY_NAVIGATION","message":"Navigation must include at least one item.","hint":"Add `navigation.items` linking to each page via `pageId`."},{"code":"INVALID_NAV_PAGE_ID","message":"Navigation pageId \"{pageId}\" does not match any page.","hint":"Set `pageId` to an existing `pages[].id`."},{"code":"ORPHAN_PAGE","message":"Page \"{id}\" is not linked from navigation.","hint":"Add a navigation item with `pageId` matching this page."},{"code":"INVALID_PAGE_CHILD","message":"Page children must be Section nodes.","hint":"Only Section nodes allowed under Page."},{"code":"INVALID_SECTION_CHILD","message":"Section children must be Metric, Table, or Text.","hint":"Blocks only under Section — no nested sections."},{"code":"INVALID_NESTING","message":"Sections cannot be nested inside sections.","hint":"Use Page → Section → Block structure only."},{"code":"EMPTY_PAGE","message":"Page must contain at least one section.","hint":"Add a Section to `children`."},{"code":"EMPTY_SECTION","message":"Section must contain at least one block.","hint":"Add Metric, Table, or Text to `children`."},{"code":"INVALID_COLUMNS","message":"Table must have at least one column with unique keys.","hint":"Define `columns[]` with unique `key` per column."},{"code":"MISSING_BINDING","message":"{blockType} requires a read binding.","hint":"Add `binding` with `type: \"read\"`, `method: \"GET\"`, and `path`."},{"code":"MISSING_VALUE_PATH","message":"Metric binding requires valuePath.","hint":"Set `valuePath` to the scalar field (e.g. `\"openCount\"`)."},{"code":"INVALID_BINDING","message":"Invalid read binding.","hint":"Use `type: \"read\"`, `method: \"GET\"`, `path` starting with `/`."},{"code":"INVALID_FILTER_FIELD","message":"Filter field \"{field}\" does not match a column key.","hint":"Set `filter.field` to an existing column `key`."},{"code":"PLANNED_NOT_SUPPORTED","message":"\"{type}\" is planned for a future version.","hint":"v0.1 supports Metric, Table, Text and read (GET) bindings only."},{"code":"INVALID_VALUE_PATH","message":"Invalid valuePath \"{valuePath}\".","hint":"Use dot segments only (e.g. `\"data.items\"`), no JSONPath or brackets."}]},{"id":"examples","format":"json","content":{"supportDashboard":{"prompt":"Generate a RUI for an internal support dashboard. Bind to GET /api/tickets (ticket list) and GET /api/tickets/stats (open and urgent counts).","mockApi":{"endpoints":[{"method":"GET","path":"/api/tickets","description":"Ticket list for the Table binding","responseShape":{"items":[{"id":"TKT-001","subject":"Login issue","status":"open","assignee":"Alex","created":"2026-05-01T10:00:00Z"}]},"bindingNotes":"Table binding uses valuePath `items` to select the row array."},{"method":"GET","path":"/api/tickets/stats","description":"Headline metrics for Metric bindings","responseShape":{"openCount":42,"urgentCount":7},"bindingNotes":"Each Metric uses valuePath for a scalar field (e.g. `openCount`, `urgentCount`)."}]},"goldenRui":{"version":"0.1","meta":{"title":"Support Operations","description":"Internal support tooling for ticket queue management"},"navigation":{"items":[{"pageId":"page-support","label":"Support"}]},"pages":[{"id":"page-support","type":"Page","title":"Support Dashboard","children":[{"id":"section-metrics","type":"Section","title":"Overview","direction":"row","children":[{"id":"metric-open","type":"Metric","label":"Open Tickets","format":"number","binding":{"type":"read","method":"GET","path":"/api/tickets/stats","valuePath":"openCount"}},{"id":"metric-urgent","type":"Metric","label":"Urgent","format":"number","binding":{"type":"read","method":"GET","path":"/api/tickets/stats","valuePath":"urgentCount"}}]},{"id":"section-tickets","type":"Section","direction":"stack","children":[{"id":"text-tickets-heading","type":"Text","content":"All tickets"},{"id":"table-tickets","type":"Table","title":"Tickets","binding":{"type":"read","method":"GET","path":"/api/tickets","valuePath":"items"},"columns":[{"key":"id","label":"ID","type":"string"},{"key":"subject","label":"Subject","type":"string"},{"key":"status","label":"Status","type":"badge"},{"key":"assignee","label":"Assignee","type":"string"},{"key":"created","label":"Created","type":"date"}],"filter":{"field":"status","label":"Status","options":[{"value":"open","label":"Open"},{"value":"pending","label":"Pending"},{"value":"closed","label":"Closed"}]}}]}]}]}}}},{"id":"gettingStarted","format":"markdown","content":"# Getting started\n\nCopy-paste block for agent evals and external sessions.\n\n## Base URL\n\n```\nhttps://rapidui.dev\n```\n\n## Fetch order\n\n1. `GET https://rapidui.dev/llms.txt`\n2. `GET https://rapidui.dev/api/schema`\n3. Author RUI JSON\n4. `POST https://rapidui.dev/api/validate` — loop until `valid: true`\n5. `POST https://rapidui.dev/api/specs` — persist; receive flat SavedSpec (201)\n\n## Support dashboard prompt\n\n```\nGenerate a RUI for an internal support dashboard. Bind to GET /api/tickets (ticket list) and GET /api/tickets/stats (open and urgent counts).\n```\n\n## Expected shape\n\n- Metric row: open tickets, urgent count (`GET /api/tickets/stats`)\n- Filterable Table: id, subject, status, assignee, created (`GET /api/tickets`, `valuePath: \"items\"`)\n- Blocks: at least `Metric` and `Table`\n\n## Golden reference\n\nThe full golden RUI is in `/api/docs` → `sections` → `examples.supportDashboard.goldenRui`.\n\nValidate your output against `POST /api/validate` — do not emit React or JSX.\n\n## SavedSpec handoff (v0.1)\n\nAfter `POST /api/specs` returns **201**, use the flat response:\n\n```json\n{\n  \"specId\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"url\": \"https://rapidui.dev/api/specs/550e8400-e29b-41d4-a716-446655440000\",\n  \"viewUrl\": \"https://rapidui.dev/specs/550e8400-e29b-41d4-a716-446655440000\",\n  \"createdAt\": \"2026-05-26T12:00:00.000Z\",\n  \"contentHash\": \"sha256:…\",\n  \"validationVersion\": \"0.1\",\n  \"registryVersion\": \"0.1\",\n  \"normalizedRui\": { }\n}\n```\n\nShare **`viewUrl`** with the user for human review (type-colored block tree). Use `url` for programmatic retrieve via `GET url`.\n"}]}