Share a Jupyter notebook as HTML

The analysis is done. The notebook runs top to bottom. Now a PM, a data lead, and two engineers need to read it and tell you what's wrong with it — and none of them are going to pip install anything to do it.

The usual answers each solve half the problem. nbviewer renders notebooks beautifully, but only ones that are already public, and nobody can comment. GitHub renders .ipynb files in the repo, but statically, behind repo access, with no way to discuss a specific output. Emailing the exported .html file works exactly once, and then the feedback scatters across reply-all.

Comma is the missing step: export the notebook to HTML, publish it, and collect comments anchored to the exact cell or table row being discussed.

The pipeline

1. Export with nbconvert

jupyter nbconvert --to html analysis.ipynb

This snapshots the notebook as it last ran — markdown, code, rendered outputs — into a single HTML file, with figure outputs embedded. Two flags worth knowing:

  • --no-input hides the code cells, which turns the export into a stakeholder-facing report instead of a wall of Python.
  • --embed-images (recent nbconvert versions) inlines images your markdown cells reference from disk, so the file is fully self-contained.

2. Publish it to Comma

Three ways in, same result: paste the HTML into the app, drag-drop the file, or POST it from a terminal. The create endpoint takes JSON with a required html field plus optional title and description:

jq -Rs '{title: "Retention analysis — July", html: .}' analysis.html |
  curl -s -X POST https://commareports.com/api/v1/reports \
    -H "Authorization: Bearer $COMMA_API_TOKEN" \
    -H "Content-Type: application/json" \
    --data-binary @-

The response carries a share_url. Tokens come from Settings → API tokens, are scoped and revocable, and the HTML limit is 5 MB — notebooks heavy with embedded figures can exceed that, in which case attach the big images as assets (25 MB per file) instead.

Pass "visibility": "private" at create time to keep the report invite-only, or set access later from the share dialog — private, your team, any signed-in user, or anyone with the link. This is the part nbviewer structurally can't do.

3. Review like a document

Reviewers open the link in a browser — no Jupyter, no environment, no repo access. They highlight a sentence in your markdown commentary or a row in a results table and pin a comment to it, Google-Docs style. Threads, replies, resolve, reopen. Commenting is free and unlimited on every plan; nobody needs a paid seat to say "cell 12's join looks wrong."

4. Re-run, republish, same link

When the comments come back, fix the notebook, export again, and PATCH:

jq -Rs '{html: .}' analysis.html |
  curl -s -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
    -H "Authorization: Bearer $COMMA_API_TOKEN" \
    -H "Content-Type: application/json" \
    --data-binary @-

Comma appends a revision under the same URL, keeps the comment threads attached, and can diff any two revisions — so "did the fix change the numbers?" is one click, not an archaeology project.

The honest comparison

Comma nbviewer GitHub rendering Emailing the .html
Private notebooks Yes — private / invite / team / public No — public URLs only Behind repo access Whoever has the file
Comments Anchored to cells, text, and table rows None On JSON source lines, not outputs Reply-all
Stable link Yes, survives re-runs Yes, but render-only Yes, but static No link at all
Revisions & diffs Yes, built in No Git history of raw JSON Attachment archaeology
Reviewer needs A browser A browser A GitHub account + access A browser
Agent can publish Yes — REST + MCP under one scoped token No Via git, awkwardly No

Where the alternatives win: nbviewer is zero-setup for notebooks that are already public — if your notebook lives in a public gist and nobody needs to comment, nbviewer is fine. GitHub rendering is unbeatable for code-review of the notebook's source. Comma is for reviewing the findings.

What renders, honestly

Comma renders your export untouched inside an opaque-origin sandbox — no reformatting, no restyling. One deliberate exception: the API strips <script> tags before storage, as defense-in-depth on an endpoint that accepts arbitrary HTML. For a Jupyter export that means markdown, code listings, tables, and static figures (matplotlib/seaborn as PNG or SVG) survive faithfully, while script-driven interactivity — ipywidgets, Plotly and Bokeh tooltips — renders statically or not at all.

For review that's usually the right trade: a comment can anchor to a rendered chart; it can't anchor to a hover state. Keep the live notebook for exploration and publish the snapshot for review.

From an agent, skip the curl

If Claude Code or Cursor is the thing running the notebook, use Comma's MCP server instead of shell plumbing. The same comma_sk_… token gates both surfaces, and the agent gets typed tools: create_report, update_report, list_comments, reply_to_comment. The end of an agentic task becomes "run the notebook, export, publish to Comma, return the share link" — and the start of the next one becomes "read the open comments and address them." See Use Comma with Claude Code for the one-line install.

Making it recur

Two ways to keep the report fresh:

  • Your scheduler, Comma as destination. A cron job or CI pipeline runs jupyter nbconvert and the PATCH call above. Works on every plan.
  • Comma routines. Hosted scheduling that re-runs a skill and republishes at any cadence on every plan. You pay only for the AI compute — bring your own AWS Bedrock key for free, or draw down prepaid credits. See Routines →.

Try it

Publishing, collaboration, and routines are all free. Export a notebook you've already run and publish it — the difference shows up in the first comment, which lands on the row of the table it's about instead of in a Slack thread describing where to look.

Create your first report →

Related