Portal JSON — Component Reference
Complete reference for every component in the portal JSON format: fields,
types, allowed values, and defaults. For a tutorial overview see
PORTAL_JSON.md; for styling (custom_css hooks + limits)
see PORTAL_CUSTOMIZATION.md.
All values come from the live schema (app/portal_io.py, app/composer.py,
app/render.py). Unknown fields are dropped on import; any field you omit
falls back to the default shown here.
Legend — Type column: string, int, bool, object, array<T>,
enum(...). Req column: ● required, ○ optional.
1. Document envelope
The top-level object.
| Field | Req | Type | Default | Notes |
|---|---|---|---|---|
apifleet_portal |
● | int |
— | Format version. Must be 1. |
name |
● | string |
— | Portal display name. Non-empty. |
slug |
○ | string |
from name |
URL slug seed; made unique per workspace. |
theme |
○ | object |
{} |
Theme object. |
specs |
○ | array<string> |
[] |
Spec filenames to attach. Each must already exist in the workspace. |
pages |
● | array<object> |
— | ≥1 Page. Array order = nav order. Max 500. |
nav |
○ | object |
auto | Nav object. Omit → auto-generated from pages. |
2. Theme object
Every theme key is optional; omitted keys use the portal default. Colors accept
any CSS color string (#rrggbb, rgb(...), named). Grouped by purpose.
2.1 Color
| Key | Type | Default | Notes |
|---|---|---|---|
primary |
string (color) |
#5b5bd6 |
Brand color. |
accent |
string (color) |
= primary | Secondary accent. |
bg |
string (color) |
#ffffff |
Page background. |
surface |
string (color) |
#ffffff |
Card / panel background. |
sidebar_bg |
string (color) |
= bg | Sidebar background. |
text |
string (color) |
#1a1a2e |
Body text. |
muted |
string (color) |
#6b7280 |
Secondary text. |
border |
string (color) |
derived | Hairline borders. |
border_strong |
string (color) |
derived | Emphasized borders. |
on_brand |
string (color) |
auto-contrast | Text on a brand fill. Auto-computed if omitted. |
mode |
enum(light, dark, system) |
light |
Color mode. system follows the viewer's OS. |
elevation |
enum(flat, raised) |
flat |
Card shadow. flat = bordered, raised = soft shadow. |
2.2 Search overlay (optional accents)
Derived from brand/surface by default; set only to tune the ⌘K search chrome.
| Key | Type | Notes |
|---|---|---|
search_highlight |
string (color) |
Match-highlight background. |
search_overlay |
string (color) |
Overlay panel background. |
search_kbd |
string (color) |
Keyboard-hint chip background. |
2.3 Typography
| Key | Type | Default | Notes |
|---|---|---|---|
font |
string (CSS font stack) |
system sans | Body font. |
font_heading |
string (CSS font stack) |
= body | Heading font. |
mono |
string (CSS font stack) |
system mono | Code font. |
font_scale |
string/number |
1 |
Multiplier driving the heading ramp. |
heading_weight |
string/number |
700 |
Heading font-weight. |
heading_tracking |
string |
-0.02em |
Heading letter-spacing. |
Curated webfonts (first family in a stack triggers a Google Fonts import,
only if external fonts are enabled): Inter, Manrope, Plus Jakarta Sans,
Lora, Newsreader, Source Serif 4, JetBrains Mono, IBM Plex Mono.
Any other family is used as-is with no external request.
2.4 Shape / density
| Key | Type | Default | Notes |
|---|---|---|---|
radius |
string (CSS length) |
12px |
Card/control corner radius. |
border_width |
string (CSS length) |
1px |
Border thickness. |
density |
string |
— | Spacing scale knob. |
2.5 Layout (page skeleton)
| Key | Type | Allowed values | Default |
|---|---|---|---|
width |
enum |
narrow (860px), default (1180px), wide (1360px) |
default |
toc |
enum |
off, right |
off |
search |
enum |
sidebar, topbar-right, off |
sidebar |
secondary_nav |
enum |
tabs, breadcrumbs |
tabs |
2.6 Branding & chrome
| Key | Type | Notes |
|---|---|---|
logo_url |
string (URL) |
Header logo (light mode). |
logo_dark_url |
string (URL) |
Header logo for dark mode. |
favicon_url |
string (URL) |
Browser tab icon. |
cta_label |
string |
Header call-to-action text. Renders only with cta_url. |
cta_url |
string (URL) |
CTA destination. |
banner_text |
string |
Top announcement banner text. |
banner_url |
string (URL) |
Makes the banner a link. |
footer_text |
string |
Footer text. Defaults to © {portal name}. |
github_url |
string (URL) |
Footer/header GitHub link. |
discord_url |
string (URL) |
Discord link. |
twitter_url |
string (URL) |
X / Twitter link. |
custom_css |
string |
Freeform CSS. Sanitized at render (a </style> breakout is neutralized). |
2.7 Background (art direction)
theme.background is a declarative BackgroundSpec — a stack of layers the
renderer composes into one CSS background, painted identically in the composer
canvas, the live-preview iframe, and the published page. It is config, not CSS
or JS. Design rationale lives in ART_DIRECTION_SPEC.md;
this table is the shipped schema.
BackgroundSpec
| Field | Type | Default | Notes |
|---|---|---|---|
layers |
array (1–12) |
— (required) | Paint order is bottom → top: index 0 is the bottom-most layer. An empty or >12 list drops the whole spec. |
scrim |
"auto" | { "color": ColorValue } |
none | Readability overlay painted on top of every layer. "auto" fades the page bg from ~55% to ~8%; an object paints a flat tint. |
scope |
"site" | "hero" |
"site" |
site paints the whole page body. hero paints only a top band (.doc-bgband, height: clamp(320px,52vh,560px)). |
animated |
true |
off | Drifts a soft, blurred copy of the spec's gradient layers on a ::before. Pure CSS; frozen under prefers-reduced-motion. |
minHeight |
string |
— | Stored and round-tripped; reserved for section bands (not yet read by the renderer). |
Layer — every layer has a type. All types accept an optional
visibility: { "desktop"?: bool, "mobile"?: bool }; a layer set false for a
surface is dropped there (mobile drop emits a @media (max-width:640px) rule).
type |
Fields read | Notes |
|---|---|---|
color |
color: ColorValue |
Flat fill. |
gradient |
kind, + per-kind below |
kind ∈ linear | radial | conic | mesh. |
gradient (linear) |
angle (deg, def 180), stops |
— |
gradient (radial/conic) |
center: {x,y} (%, def 50,50), stops |
— |
gradient (mesh) |
blobs: [{ color, x, y, radius }] |
x,y,radius are %; each blob is a soft radial. |
grid |
color: ColorValue (def {token:"border"}), size (CSS length, def "32px") |
Thin line grid. |
noise |
intensity (0–1, def 0.06) |
Deterministic SVG grain data-URI. |
image |
assetUrl (URL), fit ∈ cover|contain|tile (def cover) |
Centered. URL passes a scheme guard. |
decoration |
assetUrl (URL), anchor (9-grid, def top-right), sizePx (16–1200, def 240) |
Single non-repeating image pinned to an anchor; width min(sizePx, 45vw), height auto. |
stop = { "color": ColorValue, "at": 0–100 }. Anchors are the 9-grid
top-left … center … bottom-right. The animated layer type is reserved
(accepted but not painted) — use the spec-level "animated": true flag for
motion.
ColorValue — either a CSS color string ("#5b5bd6", "#5b5bd6cc", …) or a
theme-token ref { "token": <name>, "alpha"?: 0–1 }. Token names mirror the CSS
variables: brand, accent, bg, surface, text, muted, border (note
brand, not the theme field name primary). alpha wraps the token in
color-mix(... N%, transparent); an unknown token falls back to brand. Prefer
token refs over literals so a background reskins with the theme.
"background": {
"scope": "hero",
"scrim": "auto",
"animated": true,
"layers": [
{ "type": "color", "color": { "token": "bg" } },
{ "type": "gradient", "kind": "mesh", "blobs": [
{ "color": { "token": "brand" }, "x": 18, "y": 22, "radius": 55 },
{ "color": { "token": "accent" }, "x": 82, "y": 30, "radius": 48 }
] },
{ "type": "noise", "intensity": 0.06 }
]
}
Guarantees. render_background(theme) is a pure function of (spec, theme),
so the same spec yields byte-identical CSS in preview and publish. Image and
decoration URLs pass a scheme guard (blocks javascript: etc.); a missing or
unsafe URL drops just that layer, never the page.
3. Page object
| Field | Req | Type | Allowed / Default | Notes |
|---|---|---|---|---|
slug |
● | string |
— | Unique within the document. URL segment. |
title |
○ | string |
= slug | Page heading + nav label. |
parent_slug |
○ | string/null |
null |
Nests under a section. |
visible_to |
○ | enum(anyone, authenticated) |
anyone |
authenticated gates to signed-in org members. |
blocks |
○ | array<object> |
[] |
Blocks, rendered top → bottom. Max 500. |
4. Blocks
Every block is { "type": <enum>, "config": <object> }.
type — one of:
markdown, code, callout, image, tabs, card_grid, divider,
endpoint, schema, endpoint_list.
Config fields per type below. Unknown config keys are dropped.
4.1 markdown
| Field | Type | Default | Notes |
|---|---|---|---|
source |
string |
"## New section\n\nWrite your content here." |
CommonMark + tables. |
4.2 code
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
language |
enum |
see list; default bash |
Syntax highlighting. |
title |
string |
"" |
Optional caption above the block. |
source |
string |
"# your code here" |
Code body. |
language values: bash, javascript, typescript, jsx, tsx,
python, go, rust, java, ruby, json, yaml, sql, css,
markup (HTML/XML), plain.
4.3 callout
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
variant |
enum |
info (default), warning, success |
Icon + color. Unknown → info styling. |
markdown |
string |
"Something worth highlighting." |
Body (Markdown). |
4.4 image
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
src |
string (URL) |
"" |
Image URL. Unsafe schemes rejected at render. |
alt |
string |
"" |
Alt text. |
width |
string |
"" |
CSS width (e.g. "480" / "60%"). Empty = natural. |
height |
string |
"" |
CSS height. |
align |
enum(left, center, right) |
center |
Horizontal alignment. Unknown → center. |
4.5 tabs
| Field | Type | Default | Notes |
|---|---|---|---|
tabs |
array<Tab> |
2 sample tabs | In-page tab set. |
Tab object:
| Field | Type | Default | Notes |
|---|---|---|---|
label |
string |
"" |
Tab label. |
markdown |
string |
"" |
Tab content (Markdown). |
id |
string |
auto | Internal; generated on import (any supplied value is ignored). |
4.6 card_grid
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
columns |
int |
3 |
Grid columns (typically 2–4). |
cards |
array<Card> |
3 sample cards | — |
Card object:
| Field | Type | Default | Notes |
|---|---|---|---|
title |
string |
"" |
Card heading. |
description |
string |
"" |
Card body. |
href |
string (URL/path) |
"" |
Use /<page-slug> for another portal page, #anchor for the current page, or an absolute https:// URL externally. |
icon |
string |
"" |
Material Symbol name (e.g. "rocket_launch", "home") — rendered via the bundled Material Symbols font. Pick in the composer, or see app/static/material-symbols.json for the full ~4,300 names. A raw emoji glyph (e.g. "🚀") is still accepted as a legacy fallback. |
icon_url |
string (URL) |
"" |
Small 24×24 image icon. |
image |
string (URL) |
"" |
Hero image. |
id |
string |
auto | Internal; generated on import. |
Media priority when several are set: icon_url → image → icon.
4.7 divider
No config. { "type": "divider", "config": {} }.
4.8 endpoint (spec-driven)
Renders one operation from an attached spec.
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
spec_file |
string |
— | Must be in the document's specs. |
address |
string |
— | op:<operationId> or op:<METHOD> <path>. |
version_policy |
string |
latest |
Which revision to render. |
expand_depth |
int |
2 |
Schema expansion depth. |
show |
object<bool> |
all true |
Section toggles (below). |
show keys (all bool, default true): params, request_body,
responses, code_samples, try_it.
4.9 schema (spec-driven)
| Field | Type | Notes |
|---|---|---|
spec_file |
string |
Must be in specs. |
address |
string |
schema:<Name>. |
4.10 endpoint_list (spec-driven)
Renders every operation of a spec as a compact list.
| Field | Type | Default | Notes |
|---|---|---|---|
spec_file |
string |
— | Must be in specs. |
title |
string |
"Endpoints" |
Section heading. |
filter |
object |
{} |
Reserved for tag/method filtering. |
Finding
addressvalues: export a spec-driven portal (Settings → General → Export JSON) to see the exactop:/schema:addresses the spec exposes.
5. Nav object
The sidebar structure. Omit to auto-generate from the page list; include (as produced by export) to preserve exact sections, icons, and ordering. Pages are referenced by slug, so nav stays valid across import.
{
"topbar": {
"search": true,
"links": [
{ "id": "l1", "label": "Status", "url": "https://status.example.com" }
]
},
"tabs": [
{
"id": "t1",
"label": "Documentation",
"groups": [
{
"id": "g1",
"label": "Getting started",
"items": [
{ "id": "i1", "type": "page", "page_slug": "overview", "icon": "rocket", "hidden": false },
{ "id": "i2", "type": "external", "label": "Anthropic", "url": "https://anthropic.com", "hidden": false }
]
}
]
}
]
}
Topbar (optional):
| Field | Type | Default | Notes |
|---|---|---|---|
search |
bool |
true |
Show portal search in the top bar. |
links |
array<Link> |
[] |
External links shown in the top bar. |
Link: id (string, preserved on round-trip), label (string), and url
(absolute URL).
Tab: id (string), label (string), groups (array<Group>). Use a
unique, stable id when authoring JSON so later composer edits and round-trips
can identify the same navigation node.
Group: id (string), label (string), items (array<Item>). Group and
item IDs should also be unique and stable within the navigation document.
Item:
| Field | Type | Allowed / Default | Notes |
|---|---|---|---|
type |
enum(page, external) |
— | Page link or external URL. |
page_slug |
string |
— | Required when type=page; must match a page slug. |
url |
string (URL) |
— | Required when type=external. |
label |
string |
page title | Override shown text. Required for external. |
icon |
string |
— | Material Symbol name (e.g. rocket_launch, campaign, deployed_code), rendered via the bundled Material Symbols font. Legacy curated names (rocket, announcements, sdks, …) are still accepted and auto-mapped to their Material equivalent. |
hidden |
bool |
false |
Hide from the rendered nav. |
id |
string |
— | Internal composer id; kept verbatim on round-trip. |
Nav is reconciled against the actual pages at render/publish, so minor drift (a page added without a nav entry) is auto-healed.
6. Enumerations quick reference
| Enum | Values |
|---|---|
theme mode |
light, dark, system |
theme elevation |
flat, raised |
layout width |
narrow, default, wide |
layout toc |
off, right |
layout search |
sidebar, topbar-right, off |
layout secondary_nav |
tabs, breadcrumbs |
page visible_to |
anyone, authenticated |
block type |
markdown, code, callout, image, tabs, card_grid, divider, endpoint, schema, endpoint_list |
code language |
bash, javascript, typescript, jsx, tsx, python, go, rust, java, ruby, json, yaml, sql, css, markup, plain |
callout variant |
info, warning, success |
image align |
left, center, right |
nav item type |
page, external |
7. Caps & safety
| Limit | Value |
|---|---|
| Document size | 2 MB |
| Pages per document | 500 |
| Blocks per page | 500 |
| Required capability | portal.create (import), portal.view (export) |
Import always creates a new portal; it never edits an existing one.
auth_required is not carried across import — a new portal starts as
Draft (safe default); set its access in Settings afterward.