Put your Thingiverse stats on your own website, and let your coding agent keep them current on a schedule. Designs published, total downloads, makes and collections, plus your six most-downloaded designs as image tiles.
Packaged as an agent skill — a SKILL.md your agent reads and follows.
/thingiverse-web walks you through the Thingiverse API token, installs the section into
your page, and sets up the automatic refresh on whatever machine you have. Works with
Claude Code, Cursor, the Grok CLI, or anything else that can read a skill file and run a
shell. It is also a plain set of scripts — see Without an agent.
Companion skill: github-web — the same idea for your GitHub contributions, streaks and pinned repos.
The data is rendered into static HTML at build time. A Node script reads the Thingiverse REST API and rewrites marked-up regions of your page in place. That means:
- No client-side JavaScript. Works on a site with
script-src 'none', and with JS disabled entirely. - No token in the browser. The credential never leaves the machine that runs the sync.
- No API call per visitor. No rate limits, no latency, no third-party request from your reader's browser.
- Thumbnails are downloaded, not hotlinked. Thingiverse CDN URLs are signed and rotate; a hotlinked tile goes blank on its own schedule. The fetch saves each image under your site and points the tile at the local copy.
- It is just HTML. Your page keeps working if this project disappears.
The trade-off is that it needs something to run on a schedule. That can be a server you own, or GitHub Actions for free — the package ships templates for both.
| Element | Notes |
|---|---|
| Profile line | thingiverse.com/<you> + a tagline you write |
| Designs published | live count |
| Downloads · total | summed across every published thing |
| Makes posted, collections | live counts |
| Six design tiles | your most-downloaded, with a local thumbnail, category and download count |
| Provenance line | reads · sample data until a real sync has succeeded — see below |
Desktop and mobile variants are both supported (data-tv-view="desktop" /
"mobile"); a page can carry either or both. Mobile shows four tiles, desktop six.
git clone https://github.com/BlinkingSun/thingiverse-web-skill.git \
~/.claude/skills/thingiverse-webThen run /thingiverse-web and answer the questions.
Other agents keep their skills elsewhere — Cursor and the Grok CLI can both be pointed at the same directory. The skill is plain Markdown and shell; nothing in it is Claude-specific.
git clone https://github.com/BlinkingSun/thingiverse-web-skill.git thingiverse-web
cd thingiverse-web
# 1. paste assets/section.html into your page, assets/section.css into your stylesheet
# replace YOUR_THINGIVERSE_USERNAME, YOUR_TAGLINE and YOUR_BLURB
# 2. credentials
cp .env.example .env && chmod 600 .env # then edit it
# 3. see it render before you have a token
mkdir -p data && cp reference/thingiverse.sample.json data/thingiverse.json
TARGETS=path/to/index.html node scripts/render-thingiverse.mjs
# 4. the real thing
./scripts/sync.shSKILL.md reads perfectly well as a manual setup guide — the phases are the same either
way.
- Node 18+ (uses the built-in
fetch) - A Thingiverse App Token — free, and issued instantly. See docs/API-SETUP.md.
- Somewhere to run a scheduled job — or GitHub Actions.
If your site sets a Content-Security-Policy, the renderer emits inline-styled markup, so
you need style-src ... 'unsafe-inline' or a nonce. Nothing else needs changing — in
particular script-src 'none' is fine, which is the whole point.
1. Thingiverse renders its design list client-side. You cannot scrape the profile page — there is nothing in the HTML to scrape. The REST API is the only route, which is why a token is not optional here.
2. Do not hotlink the CDN. cdn.thingiverse.com thumbnail URLs are signed and rotate.
A page that references them directly looks perfect on the day you build it and quietly
turns into six grey boxes later. fetch-thingiverse.mjs downloads each image to
$SITE_DIR/assets/tv/<id>.jpg and writes the local path into the data file. Publish
that directory with your page — it is part of the output, not a cache.
3. There is no year-to-date download figure. The API exposes a lifetime
download_count per thing and nothing else. The stat is therefore labelled
downloads · total, and the fetch appends {date, total} to a history array on each
run. Once you have two entries you can derive a real delta; until then, do not label it
YTD, because it is not.
Fail-closed, deliberately. A bad token, a network failure, a malformed response or a thumbnail that will not download aborts the run and leaves the data file at its last-good state, so the page keeps rendering the previous numbers and tiles rather than gaining a hole. A half-updated section is worse than a stale one, because it looks fine.
Data explicitly marked "stale": true is refused outright — the renderer exits non-zero
rather than publish numbers a failed fetch left behind.
The provenance line is an honesty flag. Until a real sync has succeeded, the section
reads channel 2 of 4 · sample data. Only a successful fetch clears the suffix — so a
page that has never synced says so out loud instead of presenting invented numbers as
yours. Do not delete the suffix by hand.
A missing container is fatal. If the renderer cannot find a [data-tv] region it
expects, it exits non-zero and names it.
More in docs/TROUBLESHOOTING.md.
SKILL.md the guided setup (also a fine manual)
assets/section.html the markup — [data-tv] hooks, no data
assets/section.css self-contained styles + the tokens they need
scripts/fetch-thingiverse.mjs REST + thumbnails -> data/thingiverse.json
scripts/validate-thingiverse.mjs schema + sanity checks
scripts/render-thingiverse.mjs data -> your HTML, in place
scripts/sync.sh fetch -> validate -> render -> (publish)
publish.sh.example your deploy step; copy and edit
scheduling/ launchd, systemd, cron, Task Scheduler, GitHub Actions
schema/ JSON Schema for the data file
reference/ sample data, so you can render before you have a token
docs/ API setup, troubleshooting
Thingiverse blue #2b52fe is fine as a fill, but at 10px on the near-black card it
measures 3.53:1 — below WCAG AA. Type uses a lightened #8fa4ff (≈6:1) instead. The
data file keeps the two separate as theme.accent and theme.accentText. If you re-theme
the section, keep that split rather than using one blue for both.
Extracted from the Thingiverse section of makerinparadise.com, where it has been running against the live API since 2026-08-02.
MIT. See LICENSE.
Not affiliated with or endorsed by Thingiverse or MakerBot. "Thingiverse" is their trademark; this project only reads their public API with your own token.
