Skip to content

Publish a report

publish_report turns a live agent analysis into a frozen, shareable HTML report hosted on your brand’s own subdomain. Your agent calls it after building a report in your chat; you do not call it yourself.

Because publish_report writes something new (it is annotated as a write tool, like the Brand Knowledge Base write tools), most MCP clients ask you to approve the call before it runs.

| Parameter | Type | Required | Description | |---|---|---|---| | title | string | Yes | Human title for the report. Kreel derives the URL slug from it (lowercased, hyphenated, capped at 80 characters). A title that collides with an existing report in the same brand gets a numeric suffix, like -2. Always use the slug/url Kreel returns rather than reconstructing it from the title. | | html | string | Yes | The full, self-contained HTML document to publish. See What to pass as html below. | | brand | string | Only if you can reach more than one brand | Brand name or id. Auto-selected when you can reach exactly one brand. | | supersedes | string | No | The slug or id of a previously published report, in the same brand, that this report corrects. See Updating or replacing a report. | | correction_note | string | With supersedes | One line, up to 500 characters, saying what changed. Required when supersedes is set; the new version shows it as Updated on {date}: {note}. | | draft | boolean | No | Save the report as a draft for human approval instead of publishing it. See Drafts and approval. | | rationale | string | No | For a draft: why this report should go out, shown to the reviewer. Defaults to “Review this report before client delivery”. | | evidence | string[] | No | For a draft: up to 20 source references backing it. |

Returns: {status: "published", report_id, slug, url, viewable, kit_version, note}, or for a draft {status: "draft", report_id, decision_id, slug, url: null, viewable: false, note}. viewable is true only when the report can actually be opened right now (a handle is claimed, a password is set, and the workspace’s billing is current). note is present whenever there is something you need to do before the report is viewable; see Access gating.

Tell your agent to build one self-contained HTML document: structure, styling, and any charts inline in the page, not split across files. CSS and JavaScript are allowed and continue to work once published.

A few things to know about how the HTML is handled:

  • Reference images by URL, not base64. Kreel snapshots images for you at publish time (see below), so inlining large base64 images just bloats the document.
  • Do not add your own trust or methodology section. Kreel automatically appends a data-trust section server-side: connected sources, last successful sync and data-through dates, common metric definitions, and a note that revenue reported by different channels overlaps and should not be summed. Your authored HTML renders below that section inside a sandboxed frame, and cannot hide, rewrite, or navigate over it.
  • Do not summarize revenue by adding it across channels. Store revenue (Shopify, refund-corrected) and attributed revenue (Meta, Google, Klaviyo) are different bases. Have your agent call get_brand_context before building the report and disclose any readiness warnings, the same as it would for a live answer.

If your report includes creative thumbnails from get_creatives, only one URL form survives publishing:

Images up to 4 MB are inlined directly into the published document. Larger images are stored at a durable URL on the report itself and load from there instead, behind the same password gate as the report.

A published report lives at:

https://{handle}.reports.kreel.ai/{slug}
  • {handle} is a subdomain an owner or admin claims once per brand, in Manage reports, not per report. It is globally unique across all of Kreel.
  • {slug} is derived from title and only needs to be unique within your brand.

This is a separate origin from app.kreel.ai. Report HTML and JavaScript run isolated from your dashboard session and from any other brand’s reports.

A newly published report is not publicly reachable right away. Two things must be true first, both set by an owner or admin in Manage reports:

  1. The brand has a claimed report handle.
  2. The brand has a report password set (minimum 8 characters).

Until both are true, publish_report still succeeds and stores the report, but the response tells you what is missing:

  • No handle claimed yet: url is null, viewable: false, with a note that an owner or admin needs to claim a handle.
  • Handle claimed but no password set: viewable: false, with a note that an owner or admin needs to set a password.
  • Both set, but the workspace’s billing has lapsed: viewable: false, with a note that the report is temporarily not being served (see below).

Once a password is set, a visitor enters it once and it unlocks every report on that brand, not just the one they opened. Rotating the password signs everyone out.

With draft=true, or whenever the brand has Require approval for agent-published reports turned on, publish_report publishes nothing. It freezes the complete report as a draft (report kit, snapshotted images, branding, the data-trust section and any correction notice, exactly as they will publish) and queues it as a publish_report decision.

  • The setting is on by default for Agency workspaces and off otherwise. An owner or admin changes it per brand on the Reports page’s Drafts tab; that choice survives plan changes.
  • A draft has no public URL. Its slug is reserved, but the report origin never serves it, even to a visitor who has already unlocked the brand.
  • An owner or admin reviews the frozen draft in the dashboard (Reports > Drafts, or the Decisions queue) and approves or rejects it. Approval publishes exactly the reviewed bytes and records who approved it; later changes to branding or data never alter them.
  • A rejection needs a reason. Read it back with get_decisions(decision_id=...) (the closed list shows it too) and address it before proposing again.
  • Approval is a dashboard-only step: members, viewers, API keys and MCP tokens can never approve or reject.

While approval is required, publishing with an API key over REST or the CLI (POST /api/reports, kreel publish) is refused with 409 report_approval_required and creates nothing. Publish through this tool instead, which saves the draft for review. Scheduled report agents follow the same setting: a brand that requires approval holds every scheduled report run for review, whatever the job’s own approval mode.

Reports are immutable once published. There is no edit call.

Never call publish_report twice with the same title. If a live report - one that has not already been superseded - with that exact title already exists in your brand, the call is refused instead of quietly creating a second, disconnected report. The refusal comes back with existing_slug and existing_url naming the report already in place. Get your human’s approval, then re-call publish_report with supersedes set to that existing_slug and a correction_note. If the title match is a coincidence and you actually meant a different, unrelated report, change the title instead of retrying.

To correct a report you own the intent to replace, publish a new report and pass supersedes set to the old report’s slug or id, with a correction_note saying what changed. The old report stays stored and individually viewable, but its URL now redirects to the new one, which shows Updated on {date}: {note}. A correction saved as a draft changes nothing until it is approved: every shared link keeps serving what it served. supersedes accepts a reference to any report previously published in this brand, including one that has already been superseded itself - it always links at the current end of that report’s correction chain, so a chain of two or more corrections still redirects straight to the newest report, not an intermediate one.

supersedes must point at a report in the same brand. A cross-brand reference is rejected.

There is no MCP delete or archive call. Removing a report entirely is a permanent, human-only action in the dashboard - see Manage reports in the dashboard.

Claiming a handle, setting or rotating the password, and viewing or deleting existing reports all happen in the dashboard, not through the agent. See Hosted reports for the Reports page: claiming a handle, setting a password, and reading each report’s view count and last-viewed date.

Deleting a report is a permanent, human-only action available to owners and admins there. publish_report itself can only create new reports; it cannot delete or overwrite one.

  1. Ask your connected agent to build and publish the report, naming your brand if you have more than one:

    Using Kreel, build my Acme performance report for last month and publish it with publish_report using brand "Acme".

    Or use the ready-made monthly report prompt instead of writing this from scratch.

  2. The agent gathers the data it needs, typically get_brand_context for readiness and revenue policy, get_performance for the month’s channel-by-channel numbers, and get_creatives if the report includes creative highlights.

  3. The agent composes one HTML document from that data and calls the tool:

    publish_report(
    title="July 2026 Performance Report",
    html="<html>...</html>",
    brand="Acme"
    )
  4. Kreel stores the report, appends the data-trust section, and returns a slug and (if your brand already has a handle and password set) a url you can open right away.

  5. If the response came back with viewable: false or no url, an owner or admin sets the handle and password once, in Hosted reports, and the same link starts working without republishing anything.

Email support@kreel.ai.