octri.json
octri.json is your docs site's configuration as one JSON file. Appearance, navigation, the home page, redirects, top bar buttons, social links, SEO, page feedback, the Try it playground, custom code and custom components: everything a setting can describe is here, in one place you can read top to bottom and change in a few keystrokes.
You open it from the Files panel in API Studio, under Config.
Octri does not store octri.json. It is built from your project's current settings each time you open it, and saving writes your changes back to those same settings. Change the theme in Docs settings and the file shows it the next time you open it; change the file and Docs settings shows it. The two can never drift apart.
Editing the file
Click octri.json under Config in the Files panel. It opens in the code editor in the middle of API Studio.
The file starts with a $schema line:
{
"$schema": "https://app.octri.dev/schemas/octri.json"
}The editor reads that schema as you type. It suggests every key that fits where your cursor is, lists the allowed values for each setting, shows what a setting does when you hover it, and underlines a mistake before you save. The schema is public, so any editor that understands JSON Schema, such as VS Code, gives you the same help if you edit a copy on your own machine. Bring that copy back with ⋯ → Import into octri.json on the Config group: its text becomes an unsaved edit in the editor, so you can read it over before you save.
Save with the Save button or Ctrl+S (Cmd+S on a Mac). Revert throws away unsaved edits.
When a save reaches readers
The label above the editor reads Draft and live, because the file holds settings that follow two different rules.
| Saved as a draft, live when you Publish | Live as soon as you save |
|---|---|
navigation.tabs | appearance |
navigation.home | navigation.buttons |
navigation.redirects | socials |
seo | |
feedback | |
playground | |
customCode | |
components |
Each setting follows the same rule it follows everywhere else in the studio. Your sidebar, home page and redirects go out together in the next release, alongside your page drafts. A colour change or a new social link is on the docs site the moment you save.
Only navigation, the home page and redirects wait for Publish. Changing appearance, seo or playground changes your live docs straight away, so check those changes in the preview first.
How a save is read
Saving compares the file you saved with the file as it was when you opened it, and writes only the difference.
- Only what you changed is written. A setting you did not touch is never sent. If someone changes the logo in Docs settings while you edit the sidebar here, your save keeps their logo.
- Every setting is optional. Leaving one out means its default.
- Deleting a setting resets it to its default. Remove
"layout": "floating"and the layout goes back tofull. - Deleting a whole section leaves it alone. If
appearance,navigationorsocialsis missing entirely, nothing in it changes. Deletingnavigationby accident cannot empty your docs site. - Keys are strict. A misspelt key is an error, not a setting that silently does nothing.
- Nothing is written if anything is wrong. A refused save lists every problem with its place in the file, for example
navigation.tabs[1].items[0].page, and you fix them and save again.
Inside navigation, tabs, buttons and redirects are lists, and a save writes the list exactly as the file has it. Deleting the buttons or redirects key removes every button or every rule, and deleting home leaves the site with no home page. To keep them, leave the keys in place and edit the entries.
If the same settings change somewhere else while you have the file open, a notice offers Load latest. Saving anyway still writes only the parts you changed.
The shape of the file
Every section at the top level is optional.
{
"$schema": "https://app.octri.dev/schemas/octri.json",
"appearance": { },
"navigation": { },
"socials": { },
"seo": { },
"feedback": { },
"playground": { },
"customCode": { },
"components": { }
}| Section | What it holds |
|---|---|
appearance | Colours, logo, favicon, fonts, text sizes, layout, sidebar, top bar, code themes, the On this page rail, the repository link |
navigation | Tabs, sidebars, the home page, redirects and top bar buttons |
socials | Social icons and your own links under each page |
seo | Titles, descriptions, link previews and indexing |
feedback | The "Was this helpful?" question |
playground | The Try it panel on endpoint pages |
customCode | Whether your custom CSS and HTML load |
components | Descriptions and props of your custom components |
customCode and components appear on the Growth plan and above. On a lower plan they are left out of the file, and adding them is refused with "Custom code is on the Growth plan and above."
appearance
How the docs site looks. Every setting here applies on save, exactly like the Appearance panel in Docs settings.
"appearance": {
"colors": { "primary": "#FF6A00" },
"colorScheme": "dark",
"themeToggle": false,
"layout": "floating",
"contentWidth": "default",
"corners": "default",
"logo": {
"light": "https://example.com/logo.svg",
"dark": "https://example.com/logo-dark.svg",
"link": "https://example.com",
"show": true
},
"favicon": "https://example.com/favicon.png",
"fonts": { "body": "inter", "heading": "body", "code": "jetbrains-mono" },
"textSize": { "sidebar": "large", "content": "large", "code": "smaller" },
"sidebar": { "style": "default", "width": "wide", "resizable": true, "lines": false, "icons": false },
"navbar": { "layout": "default", "search": "centered", "surface": "solid" },
"codeTheme": { "light": "octri", "dark": "octri" },
"tableOfContents": { "endpoints": true, "pages": true },
"repository": { "url": "https://github.com/acme/api", "stars": true }
}Colour, scheme and layout
| Setting | Values | Default | What it does |
|---|---|---|---|
colors.primary | #rrggbb | #ff6a00 | The brand colour: links, active sidebar rows, primary buttons |
colorScheme | system, light, dark | system | The scheme a reader starts in. system follows their operating system |
themeToggle | true, false | true | Shows the light and dark switch in the top bar |
layout | full, contained, framed, floating | full | How the page is framed (see below) |
contentWidth | narrow, default, wide | default | Width of the reading column |
corners | sharp, default, rounded | default | Corner rounding across the site. rounded also turns buttons and badges into pills |
The four layouts:
full: the sidebar and the On this page rail sit at the screen edges.contained: the whole page is one centred block.framed: the content sits on a raised panel.floating: the sidebar is a card held off the edges of the screen.
Logo and favicon
| Setting | Default | What it does |
|---|---|---|
logo.light | empty | Logo image URL for the light scheme, and for both schemes when logo.dark is empty |
logo.dark | empty | Logo image URL for the dark scheme. Empty uses the light logo |
logo.link | empty | Where clicking the logo goes: a path or a full URL. Empty means the docs home |
logo.show | true | false shows the project name instead of the logo, and keeps the URLs above for later |
favicon | empty | Favicon image URL |
Image URLs are up to 500 characters. Upload images in Docs settings first, then copy their URLs here.
Fonts
| Setting | Values | Default |
|---|---|---|
fonts.body | manrope, inter, plus-jakarta-sans, dm-sans, system, google | manrope |
fonts.heading | body, manrope, inter, plus-jakarta-sans, dm-sans, google | body |
fonts.code | jetbrains-mono, fira-code, ibm-plex-mono, system, google | jetbrains-mono |
fonts.google.body | Any Google Fonts family name, e.g. "Roboto" | empty |
fonts.google.heading | A Google Fonts family name | empty |
fonts.google.code | A Google Fonts family name | empty |
heading: "body" uses the body face for headings. Set any slot to google and it uses the family named in the matching fonts.google field:
"fonts": {
"body": "google",
"code": "google",
"google": { "body": "Source Sans 3", "code": "Source Code Pro" }
}Text size
textSize.sidebar, textSize.content and textSize.code each take smaller, large or larger. The defaults are large for the sidebar and content, and smaller for code, so code examples sit a step below the page text.
Sidebar
| Setting | Values | Default | What it does |
|---|---|---|---|
sidebar.style | default, bordered, minimal | default | How sidebar rows are drawn |
sidebar.width | compact, default, wide | default | The sidebar's starting width |
sidebar.resizable | true, false | true | Lets readers drag the sidebar wider or narrower |
sidebar.lines | true, false | false | Draws a line down each folder's children |
sidebar.icons | true, false | true | Shows folder, page and link icons |
Top bar
| Setting | Values | Default | What it does |
|---|---|---|---|
navbar.layout | default, stacked, centered | default | default is one row. stacked puts the tabs on a second row. centered puts the tabs in the middle |
navbar.search | sidebar, centered, button | sidebar | Search at the top of the sidebar, centred in the top bar, or as a button |
navbar.surface | solid, frosted, transparent, colored | frosted | What the top bar is made of |
navbar.color.light | #rrggbb or empty | empty | The top bar fill in the light scheme. Empty uses the primary colour |
navbar.color.dark | #rrggbb or empty | empty | The top bar fill in the dark scheme. Empty uses the primary colour |
navbar.color is only read while surface is colored.
Code, rail and repository
| Setting | Values | Default | What it does |
|---|---|---|---|
codeTheme.light | octri, github, monokai, nord, dracula | octri | Code highlighting in the light scheme |
codeTheme.dark | Same as above | octri | Code highlighting in the dark scheme |
tableOfContents.endpoints | true, false | false | The On this page rail on endpoint pages |
tableOfContents.pages | true, false | true | The On this page rail on written pages |
repository.url | A repository URL, or empty | empty | Your source repository, linked from the top bar |
repository.stars | true, false | false | Shows the repository's star count beside the link |
Your custom stylesheet is not in octri.json. It is its own file, styles/custom.css, in the Files panel.
navigation
Your docs site's structure: the tabs along the top bar, the sidebar under each one, the home page, redirects and the top bar buttons.
"navigation": {
"tabs": [
{
"label": "Guides",
"type": "content",
"path": "guides",
"items": [
{
"folder": "Getting started",
"items": ["guides/getting-started/quickstart", "guides/getting-started/authentication"]
}
]
},
{ "label": "API Reference", "type": "reference", "path": "api-reference" },
{ "label": "Changelog", "type": "changelog" },
{ "label": "Status", "type": "link", "url": "https://status.example.com" }
],
"home": { "page": "guides/getting-started/quickstart" },
"redirects": [{ "from": "/old-guides/*", "to": "/guides/*" }],
"buttons": [{ "label": "Get an API key", "url": "https://example.com/signup", "style": "primary" }]
}Tabs
tabs lists the top bar's tabs from left to right, up to 50. It is saved as a draft and goes live when you publish.
| Field | What it does |
|---|---|
label | The tab's name in the top bar. Required |
type | What the tab is (see below). Required |
path | The tab's first URL segment, e.g. guides. Leave it out to follow the label. Lowercase letters, numbers and single dashes |
url | Where a link tab goes |
icon | An icon from the docs icon set, e.g. rocket. none shows no icon |
hidden | true keeps the tab out of the published top bar |
items | The tab's sidebar, top to bottom (see Sidebar rows) |
hide | Pages and endpoints this tab would list on its own that readers should not see |
The four tab types:
type | What it is |
|---|---|
content | A sidebar you build yourself from items |
reference | Every endpoint in your spec, kept current for you as the spec changes |
changelog | Your project's release feed. It has no sidebar |
link | A top bar link to url, on another site or this one. It has no sidebar |
A reference tab lists every endpoint without you naming them. Its items hold only the folders you arranged by hand; every other endpoint is listed for you. To keep an endpoint out of the reference sidebar, name it in hide:
{
"label": "API Reference",
"type": "reference",
"path": "api-reference",
"hide": ["GET /internal/health", "POST /internal/reindex"]
}Two tabs cannot share a path, and a tab cannot take a path the docs site uses itself: api, changelog, docs, gate, no-project, not-found and theme-test. A refused path says so: "/api is used by the docs site itself. Pick another path."
Sidebar rows
A row in items takes one of five shapes. The simplest is a bare string.
A page is named by where it sits: its tab, its folders, then its name, the same path as its URL and as its file under pages/ in the Files panel.
"guides/getting-started/quickstart"{
"page": "guides/getting-started/quickstart",
"label": "Quickstart",
"icon": "rocket",
"hidden": false,
"path": "quickstart"
}The fields a row can carry:
| Field | Rows | What it does |
|---|---|---|
label | page, endpoint, link | The sidebar text. Leave it out to use the page's or endpoint's own title. Required on a link |
icon | all | An icon from the docs icon set, in lowercase with dashes. none shows no icon. Leave it out for the default mark |
hidden | all | true keeps the row out of the published sidebar. The page itself still exists |
path | page, endpoint, folder | This entry's own URL segment. Leave it out to follow the name |
badge | folder | A short word after the folder's name, up to 24 characters, e.g. New |
badgeColor | folder | accent, gray, green, blue, amber, red or purple. accent is your primary colour |
page | folder | A page the folder's name opens when clicked |
collapsible | folder | false keeps the folder always open, with no chevron |
A folder named "" groups its rows under no heading, for rows that belong together but need no title.
A page path that matches no page, such as "guides/getting-started/webhooks", makes a new page called webhooks in that place when you save, with a line of starter text for you to replace, just as Add → Page in the sidebar would. Its name must use lowercase letters, numbers and single dashes. If a page with that name already exists somewhere else, the save is refused and tells you its real path: No page at "guides/webhooks". The page named "webhooks" is at "guides/events/webhooks"; name it by that path.
Paths are read against the file as you opened it. If you move a row into another folder, keep naming the page by its old path in that save; the next time you open the file, it shows the new one.
Home page
home names the page served at the root of your docs site, /. It is saved as a draft.
"home": { "page": "guides/overview", "hideSidebar": true }| Field | What it does |
|---|---|
page | The page shown at /, named by its path |
hideSidebar | true shows the home page without the sidebar and the On this page rail, for a landing page look |
Set "home": null to have no home page.
Redirects
redirects sends readers from addresses that no longer exist to where the content lives now. Rules are checked in order, up to 500 of them, and are saved as a draft so a rename and the rule that keeps its old address working go live together.
"redirects": [
{ "from": "/getting-started", "to": "/guides/quickstart" },
{ "from": "/v1/*", "to": "/guides/*" },
{ "from": "/guides/old-page", "to": "/guides/new-page", "force": true }
]| Field | What it does |
|---|---|
from | The old path on your docs site, starting with /. End it in /* to match that path and everything under it |
to | Where readers go instead, a path on the same site. End it in /* to carry the rest of the path across |
force | true for a page that redirects even though it is still published. Set from the page's own dialog |
How the rules behave:
- A live page always wins. A rule only answers for a path where nothing is published, so a redirect can never hide a working page. The exception is a
forcerule. - Wildcards carry the rest of the path. With
/v1/* → /guides/*, a reader at/v1/auth/keyslands on/guides/auth/keys. Atocan end in/*only when itsfromdoes. forcerules come from the page dialog. Choosing Redirect to another page in a page's dialog writes a rule withforce: truethat names both pages by their permanent address, so renaming either page never breaks it. Aforcerule cannot use wildcards.- Octri already redirects old addresses of your own pages. When a page moves between folders, readers are sent on without a rule. Write rules for what Octri cannot guess: a page whose name changed, a section split in two, or paths from a docs site you had before Octri.
A rule is refused when:
| Problem | Message |
|---|---|
| The path is on another site | Use a path on your docs site, like /guides/install. Other sites can't be redirected to. |
The path does not start with / | Start the path with /. |
The path has ? or # | Leave out ? and #: a redirect matches the path only. |
A * is anywhere but the end | A * can only end the path, after a slash, as in /old/*. |
from is the home page | The docs home page can't be redirected. |
from is an address the docs site uses itself | /api is used by the docs site itself, so it can't be redirected. |
from and to are the same | This sends readers back to the same path. |
| The wildcard would loop | This sends readers somewhere inside the old path, which would redirect again and again. |
The same from appears twice | /old is already redirected above. |
In the editor each message starts with the rule's number, e.g. Redirect 3: Start the path with /.
Top bar buttons
buttons adds up to 3 call-to-action buttons at the right of the top bar. They apply on save.
"buttons": [
{ "label": "Dashboard", "url": "https://app.example.com", "style": "outline" },
{ "label": "Get an API key", "url": "https://app.example.com/signup", "style": "primary" }
]| Field | What it does |
|---|---|
label | The button's text, up to 40 characters. Required |
url | An http(s) URL, a mailto: address, or a path starting with / |
style | plain text, an outline button, or a filled primary button. Defaults to plain |
Keep primary for the one action you most want readers to take.
socials
Social icons and links of your own, shown under each page. They apply on save.
"socials": {
"github": "https://github.com/acme",
"x": "https://x.com/acme",
"discord": "https://discord.gg/acme",
"custom": [
{ "label": "Community forum", "url": "https://forum.acme.com", "icon": "users" },
{ "label": "Book a demo", "url": "https://acme.com/demo", "icon": "" }
]
}Each platform takes its URL: github, gitlab, x, linkedin, youtube, instagram, facebook, tiktok, reddit, discord, slack, telegram, whatsapp, twitch, mastodon, medium, devto, stackoverflow, npm, producthunt and website. Delete a platform's line to remove its icon.
custom holds up to 5 links of your own, in order. Each has a label (up to 40 characters), a url, and an icon from the docs icon set. Set icon to "" to show the label as a button instead of an icon.
seo
How your docs appear in search results and link previews. These settings apply on save.
"seo": {
"enabled": true,
"titleMode": "dynamic",
"title": "Acme Docs",
"titleTemplate": "%s · Acme Docs",
"description": "Guides and API reference for the Acme payments API.",
"ogImage": "https://acme.com/og.png",
"keywords": "payments, api, sdk",
"indexable": true,
"twitter": "acme"
}| Setting | Default | What it does |
|---|---|---|
enabled | true | false turns off all search and social metadata: no link previews, no canonical links, no sitemap, no llms.txt, and every page asks not to be indexed |
titleMode | dynamic | dynamic gives each page its own title through titleTemplate. static uses title on every page |
title | empty | The site's title, up to 120 characters. Empty uses the project name |
titleTemplate | empty | How a page title is written in dynamic mode. %s is the page's title. Empty means %s | <title> |
description | empty | What search results and link previews show for the home page, and for any page without a description of its own. Up to 300 characters |
ogImage | empty | The image link previews show on social sites and chat apps |
keywords | empty | Keywords, separated by commas |
indexable | true | false asks search engines not to index or crawl the docs. Link previews keep working |
twitter | empty | Your X handle for link previews, without the @ |
A page's own description is the description in its page file's frontmatter. See SEO for how titles and descriptions are put together.
feedback
"feedback": { "enabled": true }true asks "Was this helpful?" at the bottom of every page. Votes are anonymous and show in Analytics. It applies on save and defaults to false.
playground
The Try it panel on endpoint pages, where readers send real requests to your API. These settings apply on save.
"playground": {
"enabled": true,
"baseUrl": "https://sandbox.acme.com/v1",
"auth": { "type": "bearer", "headerName": "Authorization" },
"allowedOrigins": ["https://acme.com"],
"timeoutMs": 15000
}| Setting | Default | What it does |
|---|---|---|
enabled | false | Shows Try it on endpoint pages |
baseUrl | empty | Where Try it requests go. Empty follows your project's API server, as your SDKs do. Point it at a sandbox to keep test traffic off production |
auth.type | inherit | The credential a reader fills in: inherit, none, apiKey, basic, bearer or oauth2. inherit follows your project's API settings |
auth.headerName | Authorization | The header the credential is sent in, e.g. X-API-Key |
allowedOrigins | none | Sites besides your docs that may send requests through the playground, up to 20, e.g. https:/ |
timeoutMs | 10000 | How long a request may run before it is abandoned, from 1000 to 60000 milliseconds |
auth.type and auth.headerName are saved together, so changing one writes both.
customCode
"customCode": { "enabled": true }true loads the files in custom-code/ on every docs page: styles.css, head.html for the page's <head>, and body-end.html for the end of its <body>. false keeps those files saved but unused, which is a quick way to rule them out when something looks wrong. The code itself is edited in those files, not here. Growth plan and above; applies on save.
components
The descriptions and props of your custom components, keyed by tag. Growth plan and above; applies on save.
"components": {
"PlanCard": {
"description": "A pricing plan with its price and a call to action.",
"props": [
{ "name": "title", "type": "string", "description": "The plan's name." },
{ "name": "price", "type": "number", "default": "0" },
{ "name": "featured", "type": "boolean", "default": "false" },
{ "name": "accent", "type": "color" },
{ "name": "tier", "type": "select", "options": ["free", "pro", "enterprise"], "default": "pro" },
{ "name": "children", "type": "node", "description": "What the plan includes." }
]
}
}| Field | What it does |
|---|---|
description | What the component is for, shown in the editor's component menu |
props | The props an author can set, in the order the editor lists them, up to 40 |
Each prop has:
| Field | What it does |
|---|---|
name | What an author writes in the tag: title in <PlanCard title="Pro">. Letters, numbers and underscores, not starting with a number |
type | string, number, boolean, color, node (takes markup) or select (takes one of options) |
default | The value used when an author leaves the prop out |
description | Help text the editor shows beside the prop |
options | The choices a select prop offers, in order, up to 20. A select prop needs at least one |
This section edits components that already exist. Create a component from ⋯ on the Components group in the Files panel, or in Docs settings; its template and stylesheet are files under components/. Naming a tag that does not exist is refused with No component "Hero". Create components in Docs settings → Components; this file edits the ones that exist. A component you leave out of the list is left as it is, never deleted.
When a save is refused
A refused save writes nothing and lists each problem with where it is in the file. The ones you are most likely to meet:
| Message | What to do |
|---|---|
Not valid JSON: … | A missing comma, an extra trailing comma or an unclosed bracket. The line number points at it |
Unrecognized key(s) in object: 'colour' | A misspelt or unknown key. Check the spelling against this page, or let the editor suggest it |
A colour is #rrggbb, e.g. #ff6a00. | Colours are six-digit hex, with the # |
No endpoint GET / | The method or path does not match your spec. Copy it from the endpoint page |
No page at "…". A new page's name uses lowercase letters, numbers and single dashes. | The last part of a page path is not a valid page name |
No page at "…". The page named "…" is at "…"; name it by that path. | The page exists somewhere else. Use the path the message gives |
A row needs one of "page", "endpoint", "link" or "folder", or is a bare string. | A row object has none of the four keys that say what it is |
A nav button links to an http(s) URL, a mailto: address, or a path starting with / | Fix the button's url |
A select prop needs at least one choice in options. | Add options to the select prop |
Custom code is on the Growth plan and above. | Remove the change to customCode or components, or upgrade |
Every other file beside octri.json: pages, styles, custom code and components.
Tabs, folders and ordering, set from the sidebar instead of the file.
Build the components the components section describes.
How titles, descriptions and link previews come together.