Kolbo.AIKolbo.AI Docs
Developer API

HTML Artifacts

Publish an HTML page, SVG, or Mermaid source to a public Kolbo URL and update it in place.

Publish a self-contained HTML page (or an SVG / Mermaid document) and get back a public, shareable URL. This is what the MCP tool publish_html_artifact uses for one-off deliverables: presentations, landing pages, dashboards, reports.

This endpoint is not under /api/v1. It is a Kolbo app route — POST /api/artifact/quick-share — that also accepts the X-API-Key header, so API-key clients can call it exactly like a /v1 route. There is no /api/v1 equivalent, and its request/response fields are camelCase (allowJs, shareToken), not the snake_case used across /v1.

Endpoint

POST /api/artifact/quick-share

Request Body

FieldTypeRequiredDescription
titlestringYesPage title, also the base of the SEO slug. Missing or empty → 400 title and content are required. Keep it short; the slug is truncated to 50 characters.
contentstringYesThe raw document. For html a full HTML document (DOCTYPE + html/head/body); for svg an <svg> document; for mermaid the Mermaid source text. Missing or empty → 400.
typestringNoDefault: "html". The MCP tool exposes "html" | "svg" | "mermaid"; the underlying record also accepts "react", "jsx", "tsx", "code". Any other value fails schema validation and returns 500.
allowJsbooleanNoDefault: false. Stored on the artifact record. It does not gate script execution on the published page — see the CSP callout. What it does change: when it is exactly true (and type is html), <iframe> tags survive sanitization instead of being stripped. This is evaluated per request, including on updates.
shareTokenstringNoUpdate a previous publish in place. Lookup is scoped to (shareToken, your user); an unknown or foreign token returns 404 Artifact not found. Omit on first publish.
sessionIdstringNoOptional Mongo ObjectId of a chat session to associate. A non-ObjectId string fails validation (500).
messageIdstringNoOptional Mongo ObjectId of a chat message to associate. Same validation caveat.

Identical content is deduplicated. The server hashes the sanitized content and, on a first publish, returns the existing artifact when that hash already exists among your quick-share artifacts (the lookup is scoped to your user plus the container project described below, so an artifact you published from inside a normal Kolbo project does not collide). That response is 200 with duplicate: true instead of 201, and the URL is unchanged.

html content is sanitized before storage. <object>, <embed>, and <applet> are always stripped; <iframe> is stripped too unless the request sends allowJs: true. <script>, forms, canvas, SVG, media elements, and data-* / aria-* attributes survive. Inline event handlers survive only from a fixed allow-list — onclick, onchange, oninput, onsubmit, onload, onerror, onmouseover, onmouseout, onkeydown, onkeyup, onfocus, onblur — so anything else (pointer, drag, touch, wheel handlers) is removed; bind those with addEventListener inside a <script> instead. svg and mermaid content is stored verbatim, without sanitization.

The request body is capped at 10 MB. content travels as JSON, so a page whose markup plus inlined CSS/JS/fonts/data: images exceeds roughly 10 MB is rejected by the body parser with 413 before the handler runs. Do not base64-encode the document to work around this — it only makes the body larger. Load heavy libraries from the CDNs the CSP allows instead of inlining them.

Examples

Publish a New Page

curl -X POST https://api.kolbo.ai/api/artifact/quick-share \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q3 Campaign Dashboard",
    "type": "html",
    "content": "<!DOCTYPE html><html><head><title>Q3</title></head><body><h1>Q3 Campaign</h1></body></html>",
    "allowJs": true
  }'

Response (201):

{
  "status": true,
  "data": {
    "_id": "65f1c8a2e4b0a3c1d9f5e321",
    "title": "Q3 Campaign Dashboard",
    "type": "html",
    "shareToken": "a3f4e2b1c8d90a1b2c3d4e5f60718293",
    "shareableSlug": "q3-campaign-dashboard-a3f4e2b1c8d90a1b2c3d4e5f60718293",
    "siteUrl": null,
    "isShared": true,
    "version": 1,
    "allowJs": true,
    "sharedAt": "2026-07-20T10:30:00Z"
  },
  "message": "Artifact created and shared successfully",
  "duplicate": false
}

data is the full artifact record (including content, contentHash, slug, versions, customDomain, createdAt/updatedAt; trimmed in the example above) plus two computed fields:

FieldNotes
shareToken32-char hex token. Keep it — it is how you update the page later.
shareableSlug<slugified-title>-<shareToken>. This is the path segment on the sites host.
siteUrlOnly non-null when a custom subdomain has been assigned to the artifact in the app and is marked active; it then has the form https://sites.kolbo.ai/<subdomain>. Build the public URL from shareableSlug otherwise.
duplicatetrue when the content hash already existed and no new artifact was created.
updatedPresent on the update path only (true when a new version was written).

Update in Place

Pass the shareToken from a previous publish. The public URL stays the same and the previous content moves into version history.

curl -X POST https://api.kolbo.ai/api/artifact/quick-share \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q3 Campaign Dashboard",
    "content": "<!DOCTYPE html><html>...updated...</html>",
    "shareToken": "a3f4e2b1c8d90a1b2c3d4e5f60718293"
  }'

Response (200):

{
  "status": true,
  "data": { "version": 2, "shareToken": "a3f4e2b1c8d90a1b2c3d4e5f60718293", "...": "..." },
  "message": "Artifact updated in place",
  "duplicate": false,
  "updated": true
}

On the update path: title is applied only when it is a non-blank string, allowJs only when it is a real boolean, messageId only when truthy, and if the new content hashes identically to the current content nothing is written — you get 200 with duplicate: true, updated: false, and no version bump. sessionId is ignored on this path, and type is not written to the record — the artifact keeps the type it was created with. type does still decide how the new content is treated: omit it and the incoming content is sanitized as HTML, so always resend the same type you published with when updating an svg or mermaid artifact. Likewise, resend allowJs: true on an update if the page relies on <iframe> embeds, or they are stripped from the new version.

Reading the Published Page

Three public surfaces serve the same content. None require authentication.

URLReturns
https://sites.kolbo.ai/<shareableSlug>The page itself. The host resolves a custom subdomain first, then falls back to the share token embedded in the slug. Unknown slug → a branded 404 page.
https://api.kolbo.ai/api/shared-artifact-raw/<shareToken>Raw content as text/html.
https://api.kolbo.ai/api/shared-artifact/<shareToken>JSON metadata plus content (and the owner's public profile fields).

Both /api/shared-artifact* routes accept either the plain token or the full slug-token form. Both resolve the token with an isShared: true filter, so an unknown token and an artifact that has been un-shared in the app produce the same response: 404 with a JSON body — not an HTML page.

Rendered content is always served as text/html, whatever the artifact's type. Both surfaces that return the document itself — shared-artifact-raw and the sites host — send the stored content verbatim under Content-Type: text/html; charset=utf-8. (/api/shared-artifact/<shareToken> is the JSON surface and is unaffected.) An svg document still renders because browsers render inline SVG, but a mermaid artifact is served as its raw source text — the public URL does not render it into a diagram. Publish Mermaid as an html page that embeds the diagram if you need a viewable link.

The two /api/shared-artifact* routes are rate limited to 30 requests/minute per IP. POST /api/artifact/quick-share has no dedicated limiter of its own — only the global safety net of 5,000 requests/minute per API key.

Scripts always run on the published page; allowJs does not change that. Both surfaces that serve the document send a fixed Content-Security-Policy, which allows script-src 'self' 'unsafe-inline' 'unsafe-eval' plus jsDelivr, cdnjs, unpkg, d3js.org, cdn.tailwindcss.com, and Cloudflare Insights. Also enforced: connect-src 'self' + Cloudflare Insights (no arbitrary fetch/XHR), form-action 'none' (no form submissions), object-src 'none', base-uri 'none', frame-ancestors 'self' https://*.kolbo.ai, and images/media from any HTTPS origin. frame-src differs between the two: the sites host allows frames only from YouTube and Vimeo, while shared-artifact-raw also allows 'self' and https://*.kolbo.ai (so one Kolbo artifact can embed another). Remember that <iframe> tags only survive in html artifacts published with allowJs: true.

Slug lookups are cached for 60 seconds. A freshly updated page can serve the previous version to the sites host for up to a minute.

Where Artifacts Live

Quick-share artifacts are attached to a per-user container project named "Kobi Code Artifacts", created on first publish and archived so it stays out of the normal project picker. There is no project_id parameter on this endpoint.

Errors

StatusCause
400title or content missing/empty.
401Missing or invalid API key.
403Key minted with scope enforcement and lacking the write (or admin) permission — code API_KEY_READ_ONLY. A USD-billed key, which may only call the developer-console API, gets code USD_KEY_ROUTE_RESTRICTED. Also returned for a restricted or email-unverified account.
404shareToken supplied but no artifact with that token belongs to you.
413Request body over the 10 MB JSON limit.
500Invalid type, non-ObjectId sessionId/messageId, or a storage failure. The body is { "status": false, "error": "..." } (note error, not message).

MCP Tool

publish_html_artifact wraps this endpoint:

MCP argSent as
titletitle (trimmed)
contentcontent — optional when file_path is given; pass exactly one of the two
file_pathNot sent. The tool reads the file (absolute local path on local stdio installs, or an https:// URL) and sends its text as content. Capped at 5 MB; local paths are rejected over the remote connector
typetype — "html" | "svg" | "mermaid", default "html"
allow_jsallowJs — always sent, as true only for an exact boolean true, otherwise false
share_tokenshareToken (trimmed), omitted when blank

The tool never sends sessionId or messageId; those are only reachable by calling the endpoint directly.

It returns { url, shareToken, shareableSlug, duplicate, updated, title }. Against production url is the artifact's siteUrl when a custom subdomain is active, otherwise https://sites.kolbo.ai/<shareableSlug>; on non-production hosts it falls back to the shared-artifact-raw URL.