pypi

Use when finding Python packages, reading descriptions, checking version history, or getting pip install commands. Provides API and navigation flows for pypi.org.

PyPI — Python Package Index

Overview

Covers the full read workflow on PyPI: searching for packages, reading package detail pages (description, version history, download files), and retrieving metadata via the JSON API. The JSON API is the recommended approach for automated workflows — no CAPTCHA, no browser required.

Not covered here: Package upload/publishing, account management, user profiles, API tokens.

Auth required for: Uploading packages, managing releases. All browsing and search are public.


Agent Paths

Programmatic [API] — No browser needed

  • Package metadata + all versions: GET https://pypi.org/pypi/{name}/json
  • Specific version metadata: GET https://pypi.org/pypi/{name}/{version}/json
  • Search: No clean JSON search API — use browser or navigate directly to /project/{name}/

Required headers: None — all public endpoints are open

Browser [Browser] — Requires browser control

  • Use selectors and Navigation Flows below
  • Required when: searching without knowing the package name, browsing download files, reading classifiers/maintainer info

When to use which

  • Default to Programmatic if you know the package name — one call returns everything
  • Use Browser when: you need to search for a package by description (not name), or the task involves downloading/copying the pip install command
  • Avoid browser search (/search/) — hCaptcha blocks automated browsers. Use the JSON API instead.

Agent Knowledge Note

Most frontier LLMs have PyPI's API and package structure in training data. This skill adds value via:

  • hCaptcha on /search/ — automated browsers are consistently blocked on the search results page. Training data may suggest using the search UI; the correct path is the JSON API or direct /project/{name}/ navigation.
  • Tabs are preloaded, not lazy — Description, History, and Files tabs are all server-rendered in one HTML response. Tab click is CSS-only. Never wait for a network event after clicking a tab. This is the opposite of crates.io.
  • Search results show no version number — The .package-snippet elements contain the creation date, not the current version. To get the version, navigate to /project/{name}/ or use the JSON API.
  • robots.txt disallows /search* — scraping the search page is against ToS; combined with hCaptcha, the JSON API is the correct path.

Site Map

PageURL PatternNotes
Homepagehttps://pypi.org/Search form
Search resultshttps://pypi.org/search/?q={query}hCaptcha-protected for bots — see Gotchas
Package detailhttps://pypi.org/project/{name}/Description, history, files tabs
Specific versionhttps://pypi.org/project/{name}/{version}/Version-pinned detail
JSON API (latest)https://pypi.org/pypi/{name}/jsonFull metadata, no auth, no CAPTCHA
JSON API (version)https://pypi.org/pypi/{name}/{version}/jsonSpecific version metadata

Page Structures

Homepage

ElementSelectorNotes
Search inputinput[name="q"]Placeholder: "Type '/' to search projects"
Search submitbutton.search-form__button
Nav: Log ina[href^="/account/login/"]
Nav: Registera[href="/account/register/"]

Package Detail (/project/{name}/)

ElementSelectorNotes
Copy pip installbutton.copy-tooltipCopies pip install {name}==version to clipboard
Latest version badgea.status-badgeText: "Latest version" — shows "Yanked" for yanked releases
Description tab#description-tabaria-label="Project description..."
History tab#history-tabaria-label="Release history..."
Files tab#files-tabaria-label="Download files..."
Version history entriesa.cardEach card links to /project/{name}/{version}/
Version number (in card)a.card > p.release__versionText: e.g. "2.32.5" — direct child <p> inside card
Release date (in card)a.card > p.release__version-date timedatetime attr has ISO timestamp; text is humanized date
Maintainer linksa[href^="/user/"]Links to each maintainer's profile
Classifier linksa[href^="/search/?c="]Filter search by classifier (license, Python version, etc.)
Project homepage/source linksa.vertical-tabs__tab[href^="http"]Homepage, docs, source in sidebar
Report malwarea[role="button"][href*="submit-malware-report"]

Search Results (/search/?q={query})

ElementSelectorNotes
Result items.package-snippetEach result is an <a> wrapping name + date + description
Result name.package-snippet__namePackage name
Result creation date.package-snippet__createdWhen the package was first published — not current version
Result description.package-snippet__descriptionOne-line summary
Pagination nexta[aria-label="Next page"]Absent on last page
hCaptcha (if blocked)button#capSubmitAutomated browsers may see this instead of results

Note: Search results do not include the current version number. For current version, navigate to /project/{name}/ or use the JSON API.

Files Tab (within package detail, #files panel)

ElementSelectorNotes
File name filterinput[name="bdist-filenames-filter"]Filter wheel filenames
Interpreter filterselect[name="bdist-tags-interpreters"]e.g. cp313, py3
ABI filterselect[name="bdist-tags-abis"]e.g. cp313, none
Platform filterselect[name="bdist-tags-platforms"]e.g. win_amd64, any
Source dist linka[href$=".tar.gz"]Direct download URL
Wheel linka[href$=".whl"]Direct download URL

Navigation Flows

Flow: Search for a Package [Browser]

Starting point: https://pypi.org/ Prerequisites: None

StepActionTargetValueWait ForTimeout
1navigatehttps://pypi.org/input[name="q"] visible5s
2typeinput[name="q"]Search term
3pressinput[name="q"]EnterURL contains /search/3s

Success state: URL is https://pypi.org/search/?q={term} with .package-snippet results visible.

Branching:

  • If button#capSubmit appears → hCaptcha is blocking — search not available for this browser session. Navigate directly to /project/{name}/ if you know the package name, or use the JSON API: GET https://pypi.org/pypi/{name}/json

Flow: View Package Detail [Browser]

Starting point: Any page Prerequisites: None

StepActionTargetValueWait ForTimeout
1navigatehttps://pypi.org/project/{name}/#description-tab visible5s
2readbutton.copy-tooltip, a.status-badge, page bodyDescription loaded

Success state: Package page with description, version badge, maintainers, and sidebar links visible. Install command visible in header area.


Flow: Browse Version History [Browser]

Starting point: https://pypi.org/project/{name}/ Prerequisites: None

StepActionTargetWait ForTimeout
1click#history-tabHistory panel visible1s
2reada.cardAll version cards rendered

To navigate to a specific version:

StepActionTargetWait ForTimeout
3clicka.card:has-text("{version}")Version detail page3s

Success state: URL becomes https://pypi.org/project/{name}/{version}/ with that version's metadata. Status badge may read "Latest version" or "Yanked".


Flow: Get Package Metadata [API]

Starting point: None — direct HTTP fetch Prerequisites: None

StepActionTargetNotes
1fetchhttps://pypi.org/pypi/{name}/jsonReturns full metadata as JSON
2readinfo.name, info.version, info.summaryCore fields

Success state: JSON with top-level keys info, releases, urls, vulnerabilities.


API Response Shapes

GET /pypi/{name}/json

{
  "info": {
    "name": "requests",
    "version": "2.32.3",
    "summary": "Python HTTP for Humans.",
    "requires_python": ">=3.8",
    "home_page": "https://requests.readthedocs.io",
    "license": "Apache-2.0",
    "author": "Kenneth Reitz",
    "author_email": "[email protected]",
    "requires_dist": [
      "charset-normalizer<4,>=2",
      "idna<4,>=2.5",
      "urllib3<3,>=1.21.1",
      "certifi>=2017.4.17"
    ],
    "classifiers": [
      "License :: OSI Approved :: Apache Software License",
      "Programming Language :: Python :: 3.11"
    ],
    "project_urls": {
      "Homepage": "https://requests.readthedocs.io",
      "Source": "https://github.com/psf/requests"
    }
  },
  "releases": {
    "2.32.3": [
      {
        "filename": "requests-2.32.3-py3-none-any.whl",
        "url": "https://files.pythonhosted.org/packages/.../requests-2.32.3-py3-none-any.whl",
        "size": 64928,
        "upload_time_iso_8601": "2024-05-29T15:37:05.856790Z",
        "requires_python": ">=3.8",
        "yanked": false,
        "yanked_reason": null
      }
    ]
  },
  "urls": [
    {
      "filename": "requests-2.32.3-py3-none-any.whl",
      "url": "https://files.pythonhosted.org/packages/...",
      "packagetype": "bdist_wheel"
    }
  ],
  "vulnerabilities": []
}

Key fields:

FieldDescription
info.versionLatest version string
info.requires_pythonPython version constraint
info.requires_dist[]PEP 508 dependency strings
releasesObject keyed by version string → array of distribution files
releases[ver][].yankedtrue means this specific file is yanked
urls[]Download URLs for latest version
vulnerabilities[]Known CVEs from OSV database — empty array = no known vulns

Common Agent Tasks

"What version of {package} should I install?"

  1. Fetch: GET https://pypi.org/pypi/{name}/json
  2. Read info.version — this is the latest stable version
  3. Read info.requires_python — confirm compatibility with user's Python version
  4. Read vulnerabilities[] — if non-empty, mention known CVEs
  5. Output: pip install {name}=={version} and any compatibility caveats

"Is {package} actively maintained?"

  1. Fetch: GET https://pypi.org/pypi/{name}/json
  2. Read releases keys — count total releases; sort and get the 3 most recent
  3. Read releases[latest][0].upload_time_iso_8601 — when was the last release?
  4. Read info.home_page and project_urls.Source — is there a linked repo?
  5. Read vulnerabilities[] — any unpatched CVEs?
  6. Output: latest release date, total release count, source link, vulnerability count

"Compare {pkg1} and {pkg2}"

  1. Fetch both in parallel: GET /pypi/{pkg1}/json and GET /pypi/{pkg2}/json
  2. Compare: info.version, info.requires_python, info.license
  3. Compare dependency count: len(info.requires_dist)
  4. Compare release history: len(releases) and recency of latest release
  5. Compare vulnerability status: len(vulnerabilities) for each
  6. Output: side-by-side table of version / Python req / license / dep count / release count / last release / vuln count

Dynamic Content Patterns

  • MPA with server-rendered HTML: No JS framework. Every page navigation is a full page load. Use domcontentloaded.
  • Tabs are already in DOM: All three tab panels (description, history, files) are server-rendered on the same page. Clicking a tab toggles CSS visibility only — no network request, no waiting.
  • Version history is fully inline: All releases are listed in the #history panel with no pagination, even for packages with 100+ versions.
  • hCaptcha on /search/: Automated headless browsers are frequently challenged by hCaptcha on the search endpoint. Direct package URLs (/project/{name}/) and the JSON API are unaffected.
  • Yanked releases: Appear in history with " yanked" in the card text. pip will refuse to install them unless the version is pinned exactly.

Timing & Waiting

TransitionWait ForTypical Delay
Page navigationdomcontentloaded0.5–2s
Tab click (description/history/files)No wait — content already in DOM<100ms
Search submitURL changes to /search/1–3s

Rate Limits

ScopeLimitNotes
JSON APINo documented rate limitRequests are served by CDN; generally very permissive
Browser /search/hCaptcha-gatedAutomated browsers are blocked; use API instead
Download filesBandwidth throttling on very large downloadsNot relevant for metadata tasks

Parallel requests: Safe to fire multiple metadata API requests in parallel — the JSON API is CDN-backed and handles high concurrency.


Common Gotchas

  1. hCaptcha blocks automated search/search/ is blocked for headless browsers. Detect via button#capSubmit. Workaround: use the JSON API (https://pypi.org/pypi/{name}/json) or navigate directly to /project/{name}/ if you know the package name.

  2. Tabs don't trigger network requests — Description, history, and files content are all server-rendered in one HTML response. Clicking #history-tab just toggles visibility. Never wait for a network event after tab click.

  3. Package name canonicalization — PyPI treats hyphens and underscores as equivalent, and names are case-insensitive. pip_tools, pip-tools, and Pip-Tools all go to the same page. The canonical name is in info.name from the JSON API.

  4. Yanked releases still appear in history — Yanked versions stay visible in a.card entries with "yanked" in the text. They won't install via pip without explicit version pinning.

  5. robots.txt disallows /search* — Well-behaved crawlers should not scrape search. Combined with hCaptcha, the intended path for programmatic lookup is the JSON API.

  6. Version history shows all releases — No pagination on the history tab. document.querySelectorAll('a.card').length gives the total release count.

  7. History entries from JSON APIreleases key in the JSON API response is an object keyed by version string. Iterate Object.keys(data.releases).reverse() for chronological order.


Anti-Patterns

  • NEVER use the browser search page for automation — hCaptcha blocks headless browsers on /search/. Use GET /pypi/{name}/json or navigate directly to /project/{name}/.
  • NEVER wait for a network event after clicking a tab — tabs are preloaded in the HTML. Waiting for networkidle or load after a tab click will timeout or proceed immediately with wrong content.
  • NEVER expect version number in search results.package-snippet elements show the creation date (.package-snippet__created), not the current version. There is no current version in search results — go to the detail page or API.
  • NEVER use .card__title to extract version — the version text is in a.card > p.release__version (direct child <p>). .card__title does not exist.
  • NEVER assume releases is an arrayreleases is an object keyed by version string, not an array. Iterate Object.keys(data.releases).

Freshness

Skill verified2026-02-20
API stable since2018 — PyPI JSON API (PEP 503 + warehouse); breaking changes are extremely rare
Selector stabilityHIGH — PyPI uses intentional class names (.package-snippet, a.card, .copy-tooltip); no hashing
Breaking change riskLow — PyPI is very conservative about UI changes