crates-io
Use when finding Rust crates, reading documentation, checking versions, or comparing crates. Provides API endpoints and navigation flows for crates.io.
crates.io — Rust Package Registry
Overview
Covers the full read workflow on crates.io: searching for crates, reading crate detail pages, browsing version history, and retrieving metadata via the REST API. The JSON API at /api/v1/crates/{name} is the recommended path for automation — returns everything in one call, no CAPTCHA, no SPA rendering delays.
Not covered here: Publishing crates, managing ownership, account settings. Browsing and search are fully public.
Auth required for: Publishing, yank/unyank, transferring ownership. All read operations are public.
Agent Paths
Programmatic [API] — No browser needed
- Search:
GET https://crates.io/api/v1/crates?q={query}&per_page=10&sort=relevance - Crate detail + versions:
GET https://crates.io/api/v1/crates/{name} - All versions with yanked flags:
GET https://crates.io/api/v1/crates/{name}/versions - Dependencies for a version:
GET https://crates.io/api/v1/crates/{name}/{version}/dependencies
Required header: User-Agent: myapp/1.0 on every request (returns 403 without it)
Browser [Browser] — Requires browser control
- Use selectors and Navigation Flows below
- Required when: browsing the rendered README, checking the Security Advisories tab, interacting with a logged-in account (owner actions)
When to use which
- Default to Programmatic — covers all metadata, search, versions, dependencies, download counts
- Use Browser when: the task requires reading the rendered README (Markdown not in API), or viewing security advisory details
Agent Knowledge Note
Most frontier LLMs have crates.io's API structure in training data. This skill adds value via:
- Hashed CSS classes — Every CSS class on crates.io is an Ember.js CSS Module with a content hash suffix (e.g.
_ee3a027e7). These change on every deploy. Training data selectors using class names are stale. Only safe class:span.version. max_versionvsmax_stable_version— The API returns both.max_versionmay be a pre-release (e.g.2.0.0-beta.1). Always prefermax_stable_versionunless pre-releases are needed. This distinction is widely confused.- User-Agent enforcement — crates.io has enforced the
User-Agentrequirement more strictly since 2024. Missing it returns 403, not a helpful error. - Ember SPA wait strategy —
domcontentloadedfires on an empty HTML shell. Must usenetworkidle. Tab clicks navigate to new URLs (not toggle hidden content). - Features list is API-only — The crate features object is not rendered anywhere in the browser UI. You cannot find it by scrolling pages.
For obscure crates with little online presence, treat all sections as primary. For popular crates (serde, tokio, reqwest), API field paths are stable but all CSS selectors are always stale.
Site Map
| Page | URL Pattern | Notes |
|---|---|---|
| Homepage | https://crates.io/ | Search form + recent/trending crates |
| Search results | https://crates.io/search?q={query} | SPA — wait for networkidle |
| Browse all | https://crates.io/crates | All crates, sortable |
| Crate detail | https://crates.io/crates/{name} | Readme tab (default) |
| Version history | https://crates.io/crates/{name}/versions | All versions with yanked status |
| Dependencies | https://crates.io/crates/{name}/dependencies | What this crate depends on |
| Dependents | https://crates.io/crates/{name}/reverse_dependencies | Crates that depend on this |
| By keyword | https://crates.io/keywords/{keyword} | e.g. /keywords/serialization |
| By category | https://crates.io/categories/{slug} | e.g. /categories/web-programming |
| JSON API (crate) | https://crates.io/api/v1/crates/{name} | Full metadata + versions |
| JSON API (search) | https://crates.io/api/v1/crates?q={query}&per_page=10 | Search without browser |
| JSON API (versions) | https://crates.io/api/v1/crates/{name}/versions | All versions with yanked flag |
Page Structures
Homepage
| Element | Selector | Notes |
|---|---|---|
| Search input (desktop) | input[aria-label="Search"] | Two exist on page; first is desktop, second is mobile |
| Search submit | button[type="submit"] | Submits the search form |
| Browse All Crates | a[href="/crates"] | |
| Log in | button:has-text("Log in with GitHub") | GitHub OAuth only |
Search Results (/search?q={query})
| Element | Selector | Notes |
|---|---|---|
| Result heading (container) | [role="heading"][aria-level="2"] | One per result; contains name, version, copy button |
| Result name link | [role="heading"][aria-level="2"] a[href^="/crates/"] | Navigates to crate detail |
| Result version | [role="heading"][aria-level="2"] span.version | Text: "v1.0.228" — .version is the one un-hashed class |
| Copy Cargo.toml snippet | button[title="Copy Cargo.toml snippet to clipboard"] | Copies serde = "1.0" etc. |
| Sort: Relevance | a[href*="sort=relevance"] | |
| Sort: All-Time Downloads | a[href*="sort=downloads"] | |
| Sort: Recent Downloads | a[href*="sort=recent-downloads"] | |
| Sort: Recent Updates | a[href*="sort=recent-updates"] | |
| Sort: Newly Added | a[href*="sort=new"] | |
| Pagination next | a[rel="next"] | Standard link rel |
Crate Detail (/crates/{name})
| Element | Selector | Notes |
|---|---|---|
| Crate version | span.version | Text: "v1.0.228" — shown in header |
| Copy Cargo.toml | button[title="Copy Cargo.toml snippet to clipboard"] | Same as search results |
| Tab: Readme | a[href="/crates/{name}"] | Default tab |
| Tab: Versions | a[href="/crates/{name}/versions"] | Shows version count in text |
| Tab: Dependencies | a[href="/crates/{name}/dependencies"] | |
| Tab: Dependents | a[href="/crates/{name}/reverse_dependencies"] | |
| Tab: Security | a[href="/crates/{name}/security"] | |
| Keyword links | a[href^="/keywords/"] | e.g. #serialization |
| Category links | a[href^="/categories/"] | |
| Homepage link | a[href*="//"][href*="."][href^="http"]:not([href*="crates.io"]) | External homepage link in sidebar |
Versions Page (/crates/{name}/versions)
| Element | Selector | Notes |
|---|---|---|
| Version links | a[href^="/crates/{name}/"] | Each links to /crates/{name}/{version} |
| Yanked badge | [title*="yanked"], [aria-label*="yanked"] | If present, version is yanked |
Navigation Flows
Flow: Search for a Crate [Browser]
Starting point: https://crates.io/
Prerequisites: None
| Step | Action | Target | Value | Wait For | Timeout |
|---|---|---|---|---|---|
| 1 | navigate | — | https://crates.io/ | input[aria-label="Search"] visible | 8s |
| 2 | type | input[aria-label="Search"] | Crate name | — | — |
| 3 | press | input[aria-label="Search"] | Enter | networkidle | 5s |
Success state: URL is https://crates.io/search?q={query} with [role="heading"][aria-level="2"] elements visible.
Branching:
- If results are empty → "No crates found" message — try the JSON search API:
GET https://crates.io/api/v1/crates?q={query}
Flow: View Crate Detail [Browser]
Starting point: Search results or direct URL Prerequisites: None
| Step | Action | Target | Value | Wait For | Timeout |
|---|---|---|---|---|---|
| 1 | navigate | — | https://crates.io/crates/{name} | span.version visible | 8s |
| 2 | read | span.version, button[title="Copy Cargo.toml snippet to clipboard"], page body | — | Readme loaded | — |
Success state: Crate page loaded with version badge, readme content, keyword links, and tab nav visible.
Flow: Browse Version History [Browser]
Starting point: https://crates.io/crates/{name} or direct
Prerequisites: None
| Step | Action | Target | Wait For | Timeout |
|---|---|---|---|---|
| 1 | navigate | — | https://crates.io/crates/{name}/versions | a[href^="/crates/{name}/"] visible |
| 2 | read | a[href^="/crates/{name}/"] | All version links loaded | — |
To navigate to a specific version:
| Step | Action | Target | Wait For | Timeout |
|---|---|---|---|---|
| 3 | click | a[href="/crates/{name}/{version}"] | Version detail loads | 5s |
Success state: URL is https://crates.io/crates/{name}/{version} with that version's readme and metadata.
Flow: Get Crate Metadata [API] (Recommended for Automation)
Starting point: None — direct HTTP fetch
Prerequisites: Must set User-Agent header
| Step | Action | Target | Notes |
|---|---|---|---|
| 1 | fetch | https://crates.io/api/v1/crates/{name} | Header: User-Agent: myapp/1.0 required |
| 2 | read | crate.name, crate.max_stable_version, crate.description | Core fields |
Success state: JSON with top-level keys crate, versions, keywords, categories.
API Response Shapes
GET /api/v1/crates/{name}
{
"crate": {
"name": "reqwest",
"max_version": "0.12.12",
"max_stable_version": "0.12.12",
"downloads": 374986592,
"recent_downloads": 61138793,
"description": "higher level HTTP client library",
"homepage": null,
"repository": "https://github.com/seanmonstar/reqwest",
"documentation": "https://docs.rs/reqwest",
"keywords": ["http", "request", "client"],
"categories": ["web-programming::http-client"],
"created_at": "2016-04-07T07:00:21.936875+00:00",
"updated_at": "2024-11-15T01:10:38.851882+00:00"
},
"versions": [
{
"num": "0.12.12",
"yanked": false,
"license": "MIT OR Apache-2.0",
"rust_version": "1.63",
"published_by": { "login": "seanmonstar", "name": "Sean McArthur" },
"features": {
"default": ["charset", "http2", "macos-system-configuration"],
"json": ["serde_json"],
"stream": ["futures-core", "tokio/fs"],
"blocking": ["futures-util/io", "tokio/rt-multi-thread"]
},
"created_at": "2024-11-15T01:10:38.851882+00:00",
"dl_path": "/api/v1/crates/reqwest/0.12.12/download"
}
],
"keywords": [{ "keyword": "http", "crates_cnt": 1842 }],
"categories": [{ "slug": "web-programming::http-client", "crates_cnt": 215 }]
}
Key fields:
| Field | Description |
|---|---|
crate.max_stable_version | Latest non-prerelease version — use this for Cargo.toml |
crate.max_version | Latest version — may be a pre-release |
crate.recent_downloads | Downloads in last 90 days — use for activity/health signal |
versions[].yanked | true means this version is retracted — do not use |
versions[].rust_version | Minimum Rust version (MSRV) — null means unspecified |
versions[].features | Feature flags object — not available in browser UI |
versions[].published_by.login | Publisher's GitHub username |
GET /api/v1/crates?q={query}&per_page=10
{
"crates": [
{
"name": "reqwest",
"max_version": "0.12.12",
"max_stable_version": "0.12.12",
"downloads": 374986592,
"recent_downloads": 61138793,
"description": "higher level HTTP client library",
"updated_at": "2024-11-15T01:10:38.851882+00:00"
}
],
"meta": {
"total": 47,
"next_page": "?q=reqwest&per_page=10&page=2"
}
}
GET /api/v1/crates/{name}/{version}/dependencies
{
"dependencies": [
{
"crate_id": "serde",
"req": "^1.0",
"optional": false,
"default_features": true,
"features": ["derive"],
"kind": "normal"
}
]
}
kind values: "normal", "dev", "build" — filter to "normal" for runtime deps.
Common Agent Tasks
"Compare two crates" (e.g. "reqwest vs ureq")
- Fetch both:
GET /api/v1/crates/reqwestandGET /api/v1/crates/ureq(can be parallel) - Compare from
crate{}:downloads,recent_downloads,description,repository - Compare from
versions[0]:license,rust_version(MSRV),featureskeys - Fetch deps for both:
GET /api/v1/crates/{name}/{version}/dependencies - Compare: required dep count (
kind="normal"andoptional=false), optional dep count, shared deps
Output: table of name / latest stable / all-time downloads / recent downloads / MSRV / license / required dep count
"Is this crate safe to use?"
- Check
versions[0].yanked— iftrue, latest version is retracted; checkversions[1]for last good version - Check
crate.recent_downloadsrelative tocrate.downloads— if < 0.1% of total and total is large, the crate may be abandoned - Navigate browser to
/crates/{name}/security— look for advisory entries - Check
crate.repositoryexists and is a valid GitHub/GitLab URL (null = no source) - Check
versions[0].published_by— is the publisher the known maintainer?
Output: summary of yanked status, activity trend, advisory count, source availability
"Add this crate to my Cargo.toml"
- Fetch:
GET /api/v1/crates/{name}— readcrate.max_stable_version - Output the Cargo.toml line:
{name} = "{max_stable_version}" - If the crate has feature flags the user wants, read
versions[0].featuresand append:{name} = { version = "{version}", features = ["feature1"] }
Dynamic Content Patterns
- Ember.js SPA: Every interaction is client-side. Always wait for
networkidle(notdomcontentloaded) after navigation. Expect 2–5s load time for the JS bundle. - ALL CSS classes are hashed: Class names like
crate-spec_ee3a027e7are Ember.js CSS Modules with a content hash — they change on every deploy. Never use class selectors exceptspan.version(the only un-hashed class found). Use[aria-label],[title],[role],[href]attributes instead. - Ember IDs are auto-generated: Elements with
id="ember4",id="ember17", etc. are framework-assigned and change on every render. Never use#ember*IDs. - Tab navigation triggers full page loads: Unlike PyPI's preloaded tabs, crates.io tab clicks navigate to a new URL and fetch new content. Wait for
networkidleafter each tab click. - Search is client-side rendered: The search results page fetches from
/api/v1/crates?q=...and renders in Ember. Wait fornetworkidlebefore reading results.
Timing & Waiting
| Transition | Wait For | Typical Delay |
|---|---|---|
| Initial page load | networkidle | 2–5s (JS bundle + API fetch) |
| Search results | networkidle | 1–3s |
| Tab click (Versions, Dependencies) | networkidle | 1–2s |
| Type to search field | No wait — instant | — |
Rate Limits
| Scope | Limit | Notes |
|---|---|---|
| API | ~1 req/sec recommended | No hard limit documented; burst of ~10 tolerated before 429 |
| API (429) | Retry-After header | Back off and retry after the specified delay |
| Browser | No documented limit | Rapid navigation triggers bot detection heuristics |
Parallel requests: Safe to fire 2–3 API requests in parallel (e.g. comparing two crates). Avoid firing 10+ simultaneously — crates.io's CDN will rate-limit the session.
Common Gotchas
-
All CSS classes are hashed — never use them. Every CSS class on crates.io has a content hash suffix (e.g.
_ee3a027e7). These change whenever Ember rebuilds. The only exception isspan.version. Use attribute selectors ([aria-label],[title],[role],[href]) exclusively. -
Ember IDs change on every render.
id="ember4"is meaningless — skip it entirely. Same for any#ember*selector. -
domcontentloadedis useless here. The HTML shell is empty JavaScript. Usenetworkidle— it fires after Ember finishes fetching data and rendering. -
User-Agent required for API. The crates.io API returns
403without aUser-Agentheader. SetUser-Agent: yourapp/1.0 ([email protected])per their policy. -
Tab clicks load new pages. Unlike many SPAs that show/hide content on tab click, crates.io navigates to a new URL (
/versions,/dependencies, etc.) and fetches new data. Always wait fornetworkidleafter clicking a tab. -
Two search inputs. There's a desktop search (
input[aria-label="Search"]) and a hidden mobile search. Both havearia-label="Search"— the first one in DOM order is desktop. Usedocument.querySelector('input[aria-label="Search"]')to get the first (desktop) one. -
Yanked versions appear in history. The
/versionspage lists all versions including yanked ones. Checkversions[].yankedin the API response, or look for[title*="yanked"]elements in the browser. -
max_versionvsmax_stable_version. The API returns both. If a crate has a pre-release (e.g.2.0.0-beta.1),max_versionmay be the pre-release whilemax_stable_versionis the last stable. Always prefermax_stable_versionunless pre-releases are specifically needed.
Anti-Patterns
- NEVER use CSS class selectors (except
span.version) — all other classes have content-hash suffixes that change every Ember deploy. Your selector will silently match nothing. - NEVER use
#ember*IDs — Ember auto-generates these on every render.#ember4today is a different element tomorrow. - NEVER use
domcontentloaded— the page HTML is an empty shell until Ember's JS bundle loads and fetches data. Content isn't in the DOM yet. - NEVER omit the
User-Agentheader on API calls — instant 403, no helpful error message. - NEVER scrape HTML for the features list — crate features (
versions[].features) are not rendered anywhere in the browser UI. They exist only in the API response. Scrolling pages looking for them will waste tool calls and find nothing. - NEVER use
:nth-childor positional selectors — Ember re-renders lists and positional indexes are unstable.
Freshness
| Skill verified | 2026-02-20 |
| API stable since | 2020 — v1 API has had no breaking changes |
| Selector stability | LOW — Ember CSS Modules hash all class names on every deploy; only span.version is stable |
| Breaking change risk | Low for API, High for browser selectors |