The pkg.go.dev API Is Finally Official — Here's What You Can Actually Do With It
The Go team just shipped an official API for pkg.go.dev. Here's what it exposes, how to use it for automation, and why this matters for anyone building tooling around the Go ecosystem.
Why This Matters Right Now
If you've ever tried to programmatically query Go package metadata, you know the pain. Scraping pkg.go.dev, hitting undocumented endpoints, reverse-engineering the frontend's XHR calls — it's been a mess. The Go team has historically been quiet about whether an official API would ever exist, and the community has been building fragile workarounds for years.
That changes today. Published on 2026-05-22, the Go team announced an official API for pkg.go.dev. This isn't a beta, not a "we're thinking about it" RFC — it's a shipped, documented, supported API endpoint that you can hit right now.
For anyone building Go tooling, dependency analysis pipelines, security scanners, or even just internal dashboards that track what packages your org depends on, this is a big deal. It means you can stop maintaining brittle scrapers and start building on a stable foundation.
I've been reverse-engineering APIs for a living, so I have strong opinions about what makes a good public API. Let me walk through what the Go team shipped, how to use it, and where I think it falls short.
What the pkg.go.dev API Exposes
The API provides structured access to the same data you see on the pkg.go.dev website: package metadata, version information, documentation, imports, importers, and license details. It's a read-only REST API — no authentication required for public packages, which is the right call for an ecosystem that's fundamentally built on open source.
The base URL is https://api.pkg.go.dev/v1, and it follows predictable REST conventions. You query by module path and version, and you get back JSON. Simple, no GraphQL complexity, no OAuth dance for public data.
What's notable is the scope. This isn't just "give me the latest version of a module." You can query the full version history, get structured documentation (parsed from Go doc comments), retrieve dependency graphs, and even get vulnerability information tied to specific versions. That last one is huge — it means you can build security tooling that doesn't need to cross-reference multiple data sources.
The rate limiting is generous but present: 20 requests per second for unauthenticated clients, with higher limits available via an API key you can request through the Go team's developer portal. For most automation use cases, 20 rps is more than enough.
Technical Deep-Dive: The API Surface
Let's look at what's actually available. The core endpoints follow this pattern:
GET /v1/modules/{module_path}
GET /v1/modules/{module_path}/versions
GET /v1/modules/{module_path}@{version}
GET /v1/modules/{module_path}@{version}/packages/{package_path}
GET /v1/modules/{module_path}@{version}/dependencies
GET /v1/modules/{module_path}@{version}/vulnerabilities
GET /v1/search?q={query}&limit={n}&offset={n}These are clean, predictable, and follow the module path conventions that Go developers already understand. The @{version} syntax mirrors what you'd use in go get, which is a nice touch.
Here's a practical example — fetching metadata for a specific module version:
curl -s "https://api.pkg.go.dev/v1/modules/golang.org/x/net@v0.33.0" | jq .This returns a JSON response with the module's description, license, repository URL, published timestamp, and Go version compatibility. The response structure looks like this:
{
"module_path": "golang.org/x/net",
"version": "v0.33.0",
"commit_time": "2025-12-01T14:22:00Z",
"is_retracted": false,
"licenses": [{"type": "BSD-3-Clause", "path": "LICENSE"}],
"repository": {"url": "https://go.googlesource.com/net"},
"go_version": "1.22",
"packages": ["html", "html/atom", "http2", "http2/h2c", "idna", "..."]
}The is_retracted field is particularly useful for security automation. Module retraction in Go is the mechanism authors use to signal "don't use this version," and having it in the API response means your CI pipeline can flag retracted dependencies without parsing go.mod retract directives yourself.
The dependency endpoint is where things get interesting for supply chain security:
curl -s "https://api.pkg.go.dev/v1/modules/github.com/gin-gonic/gin@v1.10.0/dependencies" | jq '.dependencies[] | .path'This gives you the full transitive dependency tree in a single call. Previously, you'd need to clone the repo, run go mod graph, and parse the output. Now it's one HTTP request.
The vulnerability endpoint ties directly into the Go vulnerability database (vuln.go.dev):
curl -s "https://api.pkg.go.dev/v1/modules/golang.org/x/net@v0.20.0/vulnerabilities" | jq .This returns any known vulnerabilities affecting that specific version, including CVE IDs, affected symbol ranges, and fixed versions. It's essentially govulncheck as a service, which is exactly what I've wanted for building lightweight security dashboards.
The search endpoint supports the same query syntax as the website, including filtering by license, number of importers, and last updated date:
curl -s "https://api.pkg.go.dev/v1/search?q=grpc+middleware&limit=10" | jq '.results[].module_path'One design decision I appreciate: the API uses module_path consistently, not mixing "package" and "module" terminology the way some Go tooling does. A module contains packages, and the API hierarchy reflects that cleanly.
How to Start Using This Today
Let's build something practical. Here's a script that checks all your project's dependencies for known vulnerabilities using the new API:
#!/bin/bash
# check-deps-vuln.sh — Query pkg.go.dev API for vulnerabilities in your go.mod
set -euo pipefail
API_BASE="https://api.pkg.go.dev/v1"
# Parse go.mod for direct dependencies
go list -m -json all 2>/dev/null | jq -r 'select(.Indirect != true) | "\(.Path)@\(.Version)"' | while read -r dep; do
module_path=$(echo "$dep" | cut -d'@' -f1)
version=$(echo "$dep" | cut -d'@' -f2)
# URL-encode the module path (slashes become %2F in path params)
encoded_path=$(echo "$module_path" | sed 's|/|%2F|g')
response=$(curl -sf "${API_BASE}/modules/${encoded_path}@${version}/vulnerabilities" 2>/dev/null || echo '{"vulnerabilities":[]}')
vuln_count=$(echo "$response" | jq '.vulnerabilities | length')
if [ "$vuln_count" -gt 0 ]; then
echo "⚠️ ${dep} — ${vuln_count} known vulnerability(ies)"
echo "$response" | jq -r '.vulnerabilities[] | " CVE: \(.cve_id) | Fixed in: \(.fixed_version)"'
fi
doneThis script pulls your direct dependencies from go.mod, queries the API for each one, and reports any with known vulnerabilities. It's not a replacement for govulncheck (which does deeper symbol-level analysis), but it's useful for quick CI checks or dashboards where you can't run Go tooling directly.
You can also use the API to build a dependency freshness report:
# Check how far behind latest each dependency is
go list -m -json all 2>/dev/null | jq -r 'select(.Indirect != true) | "\(.Path)@\(.Version)"' | while read -r dep; do
module_path=$(echo "$dep" | cut -d'@' -f1)
current_version=$(echo "$dep" | cut -d'@' -f2)
encoded_path=$(echo "$module_path" | sed 's|/|%2F|g')
latest=$(curl -sf "${API_BASE}/modules/${encoded_path}/versions" | jq -r '.versions[-1].version' 2>/dev/null)
if [ -n "$latest" ] && [ "$latest" != "$current_version" ]; then
echo "📦 ${module_path}: ${current_version} → ${latest}"
fi
doneBoth of these would have required either running go commands (which means having the Go toolchain available) or scraping the website. Now it's just curl and jq.
Impact: Who Benefits and How
The immediate beneficiaries are teams building Go-specific security tooling. Companies like Snyk, Sonatype, and Socket have been maintaining their own Go package indexes by scraping the module proxy and pkg.go.dev. An official API means they can get structured, reliable data without the maintenance burden of scrapers that break every time the frontend changes.
For platform teams running internal developer portals (think Backstage or similar), this API makes it trivial to show dependency health information. You can build a widget that shows "3 of your dependencies have known CVEs" without needing to run govulncheck in a container somewhere.
The search endpoint opens up interesting possibilities for developer experience tooling. IDE plugins could use it for package discovery, CLI tools could suggest alternatives when a dependency is deprecated, and onboarding docs could auto-link to the right package documentation.
Worth noting: The API only covers public modules. If you're using private modules hosted on internal registries, you'll still need your own metadata service. The Go team was clear that this API reflects what's publicly visible on pkg.go.dev — nothing more.
What I'd Do Differently (And What's Missing)
The API is solid for a v1, but there are gaps. There's no webhook or streaming endpoint — you can't subscribe to "notify me when a new vulnerability is published for modules I care about." You have to poll. For security automation, that's a meaningful limitation.
There's also no batch endpoint. If you have 200 dependencies and want to check vulnerabilities for all of them, that's 200 sequential API calls. A batch endpoint that accepts a list of module@version pairs and returns vulnerabilities for all of them would cut latency dramatically. I expect this will come in v2.
The rate limit of 20 rps without auth is fine for interactive use but tight for CI pipelines in large organizations. If you have 50 repos each with 100+ dependencies, and they all run on push, you'll hit limits fast. Get an API key early if you're planning to integrate this into CI.
Here's how I'd structure a more resilient client that handles rate limiting:
import requests
import time
from urllib.parse import quote
class PkgGoDevClient:
BASE_URL = "https://api.pkg.go.dev/v1"
def __init__(self, api_key=None, max_retries=3):
self.session = requests.Session()
if api_key:
self.session.headers["Authorization"] = f"Bearer {api_key}"
self.max_retries = max_retries
def get_vulnerabilities(self, module_path, version):
encoded = quote(module_path, safe="")
url = f"{self.BASE_URL}/modules/{encoded}@{version}/vulnerabilities"
return self._request(url)
def get_versions(self, module_path):
encoded = quote(module_path, safe="")
url = f"{self.BASE_URL}/modules/{encoded}/versions"
return self._request(url)
def _request(self, url):
for attempt in range(self.max_retries):
resp = self.session.get(url)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 2))
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Rate limited after {self.max_retries} retries: {url}")This handles the 429 responses gracefully and respects the Retry-After header, which the API properly returns.
Broader Context: Go's Ecosystem Maturity
This API is part of a clear pattern from the Go team: making the ecosystem more programmatically accessible. We've seen the vulnerability database get a proper API, the module proxy (proxy.golang.org) has been stable for years, and now pkg.go.dev joins them. The Go ecosystem is arguably the most API-friendly language ecosystem right now — compare this to PyPI's API (decent but inconsistent), npm's registry API (powerful but complex), or Maven Central (don't get me started).
What I find interesting is the security-first framing. The vulnerability endpoint being a first-class citizen in v1 — not an afterthought added later — tells you where the Go team's priorities are. Supply chain security isn't a bolt-on feature; it's baked into the platform's data layer.
For those of us building automation around dependency management and security scanning, this is the kind of infrastructure that makes the job dramatically easier. No more duct-taping together three different data sources to answer "is this dependency safe to use?" One API, one call, one answer. That's how it should work, and I'm glad the Go team finally shipped it.
Related posts
- Automation
Automating web3 workflows at scale — a sanitized case study
How I built custom tooling to manage hundreds of wallets, automate on-chain transactions, and run social bots across multiple protocols.
May 18, 2026 · 10 min - Automation
CloakBrowser: I tested it against 5 bot detectors — here's what happened
CloakBrowser claims to be a stealth Chromium that passes every bot detection test. I installed it, ran it against reCAPTCHA v3, Cloudflare Turnstile, and FingerprintJS to see if the hype is real.
May 19, 2026 · 8 min - Security
How I got free cinema credit by ordering -2 popcorns
A missing input validation on M-Tix Cinema XXI's food ordering API let me increase my account balance by submitting negative quantities. No tools needed — just a browser.
May 19, 2026 · 6 min