pin
Crawl public Pinterest pins, boards, and users into structured records.
pin is a single pure-Go binary. It reads public Pinterest the way a
logged-out browser does: a pin and its related feed, a search, a board with its
pins and sections, a profile with its saved pins and boards, a topic feed, and
the trending interests. No API key, no login, nothing to run alongside it.
The same package is also a resource-URI driver,
so a host program like ant can address Pinterest
as pinterest:// URIs.
pin is an independent tool and is not affiliated with Pinterest.
Install
go install github.com/tamnd/pinterest-cli/cmd/pin@latest
Or grab a prebuilt binary from the releases, or run
the container image:
docker run --rm ghcr.io/tamnd/pin:latest --help
Usage
pin get <ref> # one pin by id or URL
pin search <query> # search for pins
pin related <ref> # pins related to a pin
pin topic <slug> # pins under an interest, e.g. home-decor
pin trending # the trending interest topics
pin board show <ref> # a board's metadata
pin board pins <ref> # a board's pins
pin board sections <ref> # a board's sections
pin user show <name> # a profile's metadata
pin user pins <name> # a user's saved pins
pin user boards <name> # a user's public boards
pin ref id <ref> # classify any reference into its (kind, id)
pin ref url <kind> <id> # build the canonical URL for a (kind, id)
pin --help # the whole command tree
A reference is whatever you have: a bare id, a full pinterest.com URL, a
user/board path, or an @handle. The ref commands resolve these offline,
with no network call.
Every command shares one output contract: -o table|json|jsonl|csv|tsv|url|raw,
--fields to pick columns, --template for a custom line, and --limit to cap
results. The default adapts to where output goes (a table on a terminal, JSONL
in a pipe), so the same command reads well by hand and parses cleanly
downstream.
pin board show pinterestman/ball-is-life -o json | jq .followers
pin user boards pinterestman --limit 5 --fields name,pins,followers
What anonymous access reaches
pin reads only what Pinterest serves to a logged-out browser. Two paths back
it: board and profile metadata come from the JSON a board or profile page embeds
(__PWS_INITIAL_PROPS__), and everything that paginates comes from Pinterest's
public resource API.
Pinterest applies anti-bot measures that vary by source IP. From a residential
browser every command below returns data. From a datacenter or cloud IP the
metadata paths (board show, user show, user boards) keep working, while the
pin-grid feeds (search, related, topic, trending, board pins,
user pins, and get) can come back empty even though the request succeeds.
When a feed is withheld, pin exits with no results (exit 3) rather than
pretending. This is a property of the network you run from, not a bug in pin.
Serve it
The same operations are available over HTTP and as an MCP tool set for agents,
with no extra code:
pin serve --addr :7777 # GET /v1/... returns NDJSON
pin mcp # speak MCP over stdio
Use it as a resource-URI driver
pin registers a pinterest domain the way a program registers a database
driver with database/sql. A host enables it with one blank import:
import _ "github.com/tamnd/pinterest-cli/pinterest"
Then ant (or any program that links the package)
dereferences pinterest:// URIs without knowing anything about Pinterest:
ant get pinterest://pin/<id> # fetch a pin
ant get pinterest://board/<user>/<slug> # fetch a board
ant get pinterest://user/<name> # fetch a profile
ant url pinterest://pin/<id> # the live https URL
Development
cmd/pin/ thin main: hands cli.NewApp to kit.Run
cli/ assembles the kit App from the pinterest domain
pinterest/ the library: HTTP client, resource API, bootstrap parser,
data models, and domain.go (the driver)
docs/ tago documentation site
make build # ./bin/pin
make test # go test ./...
make vet # go vet ./...
Every read command is declared once as a kit operation in pinterest/domain.go.
That single declaration becomes the CLI subcommand, the HTTP route, and the MCP
tool, so the three surfaces never drift.
Releasing
Push a version tag and GitHub Actions runs GoReleaser, which builds the archives,
Linux packages, the multi-arch GHCR image, checksums, SBOMs, and a cosign
signature:
git tag v0.1.0
git push --tags
The Homebrew and Scoop steps self-disable until their tokens exist, so the first
release works with no extra secrets.
License
Apache-2.0. See LICENSE.