Share an nbconvert HTML export
jupyter nbconvert --to html is the most reliable thing in the notebook
ecosystem. It takes an .ipynb and gives you one file that renders in any
browser, with the plots where you left them.
Then the file sits in your working directory, and the actual question — how does someone else see this — starts over from scratch. Email it and it's an attachment nobody can comment on. Push it to a gist and the scripts don't run. Commit it and the diff is a wall of base64.
Publish it
jupyter nbconvert --to html --embed-images analysis.ipynb
curl -X POST https://commareports.com/api/v1/reports \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html analysis.html \
'{title: "Churn analysis — cohort 2026-Q2", html: $html}')"
Or drag analysis.html into the app.
--embed-images is the flag that matters. Without it, matplotlib output
is written to a sibling analysis_files/ directory and the published
page has broken image slots — the classic
broken-CSS-and-images failure of a
self-contained-looking export that isn't.
Add --no-input when the reader is a stakeholder rather than a
colleague. The distinction is real: the same notebook is a method
document for one audience and a set of conclusions for the other.
What the URL changes
- Comments land on the cell. "Is this filtering out the trial accounts?" pinned to the cell it's about, not paraphrased in Slack three days later. See commenting on HTML.
- Re-runs are revisions. PATCH the same id when the data refreshes. Same URL, new numbers, old numbers still there to compare.
- Interactive outputs survive. Plotly, Bokeh, Altair and ipywidgets
static output all render — scripts run inside a sandboxed iframe
(
allow-scripts, noallow-same-origin). - Private by default. A notebook usually holds real customer numbers. Access is per report — team, email-domain gate, named reviewers, or a password-protected link. See the sharing model.
On a schedule
The notebook that answers a recurring question should not need you to run it. A routine executes it on a cron and PATCHes the same report, so the weekly number is current at a URL people already have — see scheduled HTML reports.
Limits
- HTML body: 5 MB.
--embed-imagesinlines every figure as base64, so a notebook with fifty plots gets large fast. Downsample the figures, or drop--embed-imagesand upload the_files/directory alongside as assets. - Scripts run, sandboxed:
allow-scripts, noallow-same-origin. - 60 requests/minute per token.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.
Related
- Share a Jupyter notebook — the whole workflow
- Papermill runs — parameterized, on a schedule
- Jupyter integration · Publish from CI