Publishing reports
Publish the HTML an agent made to a persistent URL behind your sign-in: versions, audiences, live data, comments and readership, from any surface.
A report is HTML your agent made — a briefing, a dashboard, a prototype — published to an address that stays the same, readable only by the people it was shared with, through their own sign-in. Every version is kept. The same operations are in the SDKs, the MCP server (publish_report and its neighbours), the CLI and the REST API. For how readers see reports, see Publishing and sharing reports in the guide.
Publish
SDK
import { deliverd } from "@deliverd/sdk";
const { report } = await deliverd.publish({
title: "Weekly finance report",
slug: "finance/weekly",
content: html, // one HTML document, up to 2 MB
audience: ["Finance team"],
living: true,
});
console.log(report.url);Small reports can send content (a single HTML document, up to 2 MB) or files as [{ path, content }] inline. Anything larger goes up first as an HTML file or a ZIP through an upload ticket:
REST
# 1. Get an upload ticket, then PUT the bundle (HTML or ZIP)
curl -X POST "$DELIVERD_URL/api/v1/uploads" -H "Authorization: Bearer $DELIVERD_API_KEY"
curl -X PUT "<uploadUrl>" --data-binary @report.zip
# 2. Publish it
curl -X POST "$DELIVERD_URL/api/v1/reports" \
-H "Authorization: Bearer $DELIVERD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "uploadId": "…", "title": "Weekly finance report",
"audience": ["Finance team"], "living": true, "slug": "finance/weekly" }'slug- The report's address within your organisation, such as
finance/weekly. audience- Who can open it, in plain language: people, teams, groups, workspaces,
"everyone". See Sharing. living- A report meant to be updated over time at the same address.
classificationpublic,internal,confidentialorrestricted. Your organisation's sharing rules read it.changeSummary- What changed in this version, shown in its history.
flowId- The flow the report belongs to. A report joins on its first publish.
Every publish is scanned for secrets and unsafe content first. An upload ticket is consumed by the publish that reads it, so a second publish with the same uploadId answers 404 upload_not_found — which usually means the first one worked. Look before you upload again.
Versions
POST /api/v1/reports/{idOrSlug}/versions publishes a new version at the same address, to the same audience. Pass expectedVersion so you do not overwrite somebody else's work: if the report has moved on, the update is refused with 409 version_conflict, and you should re-read and try again.
const { latestVersion } = await deliverd.reports.versions("finance/weekly");
await deliverd.reports.update("finance/weekly", {
content: html,
changeSummary: "October figures",
expectedVersion: latestVersion as number,
});- Use
latestVersionfromGET /api/v1/reports/{idOrSlug}/versions, not the live version's number: a held or declined version takes a number without going live. POST /api/v1/reports/{idOrSlug}/rollbackwith{ "version": 3 }serves an earlier version again. Nothing is deleted; roll forward the same way.POST /api/v1/reports/{idOrSlug}/archivestops the address serving, and{ "archived": false }puts it back at the same version and audience.POST /api/v1/reports/{idOrSlug}/copystarts a new report from an existing one or a template, with none of its audience or comments.GET /api/v1/templateslists the templates.
Share, send and take back
| Call | Scope | Does |
|---|---|---|
POST /api/v1/reports/{idOrSlug}/share | reports:share | Grant an audience access. The address does not change. |
DELETE /api/v1/reports/{idOrSlug}/share | reports:share | Remove every grant matching the same audience you granted. The owner's access cannot be removed. |
POST /api/v1/reports/{idOrSlug}/send | reports:share | Email up to 50 addresses a link each, with a covering note. Addresses the organisation has not seen become guests. |
POST /api/v1/audiences/resolve | audiences:read | Check what an audience phrase means before you use it. |
An ambiguous name, or one matching nobody, is refused with a list of candidates for the agent to confirm with you rather than a guess. Your organisation's sharing rules apply to every grant: a confidential report cannot be made public, and an agent key can never create a public link (403 public_sharing_denied).
Live data
A dataset lets a published report show new numbers without a new version. The report fetches it from its own origin when someone opens it:
<script>
const figures = await (await fetch("_data/figures")).json();
</script>Write it with PUT /api/v1/reports/{idOrSlug}/data/{name} (a JSON body, stored verbatim, up to 5 MB; 25 per report), list them with GET /api/v1/reports/{idOrSlug}/data, and remove one with DELETE /api/v1/reports/{idOrSlug}/data/{name}. It is behind the same access check as the report. Datasets are not versioned, so rolling back restores the markup and not the numbers.
Comments and revision briefs
GET /api/v1/commentslists every open thread across the organisation — what needs attention.GET /api/v1/reports/{idOrSlug}/commentslists one report's threads, with the quoted passage and any images.POST /api/v1/reports/{idOrSlug}/commentswith{ body, quote?, threadId? }comments or replies. Aquotemust occur exactly once in the current version: not there is404 quote_not_found, more than once is400 quote_ambiguous.POST /api/v1/reports/{idOrSlug}/revision-briefscomposes the open threads into one prompt for producing the next version. Over MCP it isget_revision_brief, with the readers' screenshots as images.POST /api/v1/comments/{threadId}/resolveresolves a thread with a note.DELETE /api/v1/reports/{idOrSlug}/comments/{commentId}retracts your own last comment in a thread nobody has closed.
Readership
GET /api/v1/reports/{idOrSlug}/analytics answers whether anyone read a report: total views on every plan, and unique, internal and external readers, a 30-day series and recent readers on plans with analytics. GET /api/v1/analytics answers across the organisation, including the live reports nobody has opened; below Team it is refused with 402 plan_required. Reader names appear only where your organisation's sharing settings show viewer identities.
Scheduled updates — asking an agent for a recurring publish — are /api/v1/schedules; the MCP tool list_due_schedules tells an agent what is waiting on it. For every field, see the interactive API reference. Deliverd scans and serves the result from a separate origin; see Errors and limits for the plan limits a publish can meet.