This is the MCP / AI reference for systemHUB — written for AI agents and developers. If you are a person looking for how-to help, use help.systemhub.com instead.

Authoring content

An agent that can call create_system is not yet an agent that produces good systemHUB documents. This page is the authoring contract: which HTML survives, which is silently changed, and the conventions that make a document genuinely usable. It is extracted from the live editor configuration (a custom CKEditor 5 build) and verified by round-trip audit — not guessed.

The rule that matters most

🚨 Send raw HTML, never entity-escaped HTML. Write <h2>Title</h2>, not &lt;h2&gt;Title&lt;/h2&gt;. The API stores escaped content verbatim and returns success — but the document renders as visible tag soup (literal <h2> characters on screen). Nothing warns you. Escaping HTML while serialising into JSON is a classic agent failure mode: if your framework auto-escapes, unescape before sending, and read the document back after writing to confirm it rendered.

The MCP/REST layer stores your HTML verbatim. The editor is the filter. Content outside the supported set is not rejected at write time — it is silently removed or downgraded the first time a human opens the document in the editor and saves. No error is raised. So author within the supported set, or your content will change under the user’s feet later.

The four content fields

Every system, policy, and training document has four content areas. All except Title are rich text with the same formatting rules:

FieldAPI parameterPurpose
TitletitlePlain text, max 255 chars.
OverviewdescriptionOne or two sentences: what this is and why it matters. Not the steps.
Details (body)contentThe main content. Steps live here.
NotesnoteInternal notes for editors and reviewers.

Supported formatting (survives the editor)

Silently stripped (do not use)

Don’t writeWhat happens / do instead
<h4> <h5> <h6>Downgraded to plain paragraph. Only H1–H3 exist.
Inline <code>Removed, text kept. Use a <pre><code> block instead.
<sup> / <sub>Removed entirely.
<mark> without a marker-* class, or pen-* classesRemoved. Only the 4 markers survive.
background-color on a <span>Removed. Use a marker highlight.
Formatting via CSS class (e.g. class="text-center")Ignored. Inline style only.
External images <img src="https://…">Removed entirely. Images must be uploaded through the editor UI — the API cannot inject images. Mark the spot instead: <p><mark class="marker-yellow">[Upload screenshot of the dashboard here]</mark></p>

Auto-normalised (harmless): <b><strong>, <em><i>, <del><s>; raw <iframe> becomes a responsive media wrapper.

Structured fields are not body content

Email templates and video/media lists are structured data, not HTML:

Do not jam these into the content HTML; use the dedicated tools so they render in the app’s Templates and Video & Media sections.

Attachments: use fileUrl, not base64

add_document_attachments takes either contentBase64 or fileUrl (a public HTTP(S) URL it fetches server-side).

Use fileUrl for anything that isn’t tiny. Base64 is not merely more expensive, it stops working: the encoded string is ~1.37x the file size and has to pass through the tool call as a single literal, so the agent must hold the whole thing in context. Most agent harnesses truncate a tool output of this size, at which point the file cannot be read back in one piece and base64 is simply unavailable. In practice base64 is fine below ~30KB and unusable above it. fileUrl has no such ceiling — the server fetches the bytes directly, and attaching a dozen files is a dozen two-line calls.

Attaching a local file

The URL must return the raw bytes. This is the step that catches people out: a normal Google Drive share link (drive.google.com/file/d/<ID>/view) serves an HTML viewer page, not the file. Convert it to the direct-download form.

  1. Upload the file to Drive and make it link-readable (anyone / reader, discovery off).
  2. Attach it using https://drive.google.com/uc?export=download&id=<FILE_ID> as fileUrl.
  3. Verify, then clean up — see below — and revoke the temporary link once the attachment exists.

Any host that returns raw bytes works just as well: an S3 object, a static site, a signed URL that is still valid when the call runs.

Verify the bytes, don’t trust the 200. If the URL serves an HTML interstitial or an error page, the upload still succeeds — you get an attachment containing that HTML instead of your file. Fetch the returned downloadUrl and checksum it against the source before you consider the job done. It is the only way to catch a wrong-URL upload, because nothing in the response tells you.

Three more behaviours worth knowing before you script against it:

Attachments cannot be replaced. Uploading a file whose name already exists on the document returns skipped with reason: "duplicate" and leaves the original content in place — it is not an error and it is not an overwrite. To ship a corrected file you must either use a different filename or delete the old attachment in the app, since the MCP has no delete.

Size limit

Edits above roughly 126KB of content can fail with a 502. edit_* replaces the entire content field on every call, so building a long document incrementally makes this worse — each edit resends everything written so far. Split genuinely large content across multiple documents instead.

The anatomy of a good system (the SYSTEMology house format)

A real system answers four questions in order — when does this run, what are the steps, how do you know it worked, and what does it look like done well. Structure every draft as:

  1. Overview = context (description field): what this system is and why it matters, 1–2 sentences.
  2. Trigger — the first thing in the body: when do you run this? The event, schedule, or signal that kicks the system off (“When a new contract is signed…”, “Every Monday before 10am…”). A system without a trigger never gets run.
  3. Steps — numbered, each as an <h3>, action-first (“Send the welcome email”, not “Welcome email”), in the order someone actually does the work.
  4. Definition of done — the last section: how do you know it worked? The observable outcome (“The client has received the welcome email and the kickoff call is in both calendars”). Without it, “done” means “I stopped”.

And the craft rules around that skeleton:

Quick example

What an unbriefed agent writes (half disappears on first human save):

<h4>Step 1</h4>
<p>Open the <code>CRM</code> and click <span style="background-color:yellow">New Deal</span>.</p>
<img src="https://example.com/screenshot.png">

systemHUB-ready:

<h3>1. Create the new deal</h3>
<p>Open the CRM and click <strong>New Deal</strong>.</p>
<p><mark class="marker-yellow">[Upload a screenshot of the New Deal button here]</mark></p>

Next: Tool reference →