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_version vs max_stable_version — The API returns both. max_version may be a pre-release (e.g. 2.0.0-beta.1). Always prefer max_stable_version unless pre-releases are needed. This distinction is widely confused.
  • User-Agent enforcement — crates.io has enforced the User-Agent requirement more strictly since 2024. Missing it returns 403, not a helpful error.
  • Ember SPA wait strategydomcontentloaded fires on an empty HTML shell. Must use networkidle. 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

PageURL PatternNotes
Homepagehttps://crates.io/Search form + recent/trending crates
Search resultshttps://crates.io/search?q={query}SPA — wait for networkidle
Browse allhttps://crates.io/cratesAll crates, sortable
Crate detailhttps://crates.io/crates/{name}Readme tab (default)
Version historyhttps://crates.io/crates/{name}/versionsAll versions with yanked status
Dependencieshttps://crates.io/crates/{name}/dependenciesWhat this crate depends on
Dependentshttps://crates.io/crates/{name}/reverse_dependenciesCrates that depend on this
By keywordhttps://crates.io/keywords/{keyword}e.g. /keywords/serialization
By categoryhttps://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=10Search without browser
JSON API (versions)https://crates.io/api/v1/crates/{name}/versionsAll versions with yanked flag

Page Structures

Homepage

ElementSelectorNotes
Search input (desktop)input[aria-label="Search"]Two exist on page; first is desktop, second is mobile
Search submitbutton[type="submit"]Submits the search form
Browse All Cratesa[href="/crates"]
Log inbutton:has-text("Log in with GitHub")GitHub OAuth only

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

ElementSelectorNotes
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.versionText: "v1.0.228" — .version is the one un-hashed class
Copy Cargo.toml snippetbutton[title="Copy Cargo.toml snippet to clipboard"]Copies serde = "1.0" etc.
Sort: Relevancea[href*="sort=relevance"]
Sort: All-Time Downloadsa[href*="sort=downloads"]
Sort: Recent Downloadsa[href*="sort=recent-downloads"]
Sort: Recent Updatesa[href*="sort=recent-updates"]
Sort: Newly Addeda[href*="sort=new"]
Pagination nexta[rel="next"]Standard link rel

Crate Detail (/crates/{name})

ElementSelectorNotes
Crate versionspan.versionText: "v1.0.228" — shown in header
Copy Cargo.tomlbutton[title="Copy Cargo.toml snippet to clipboard"]Same as search results
Tab: Readmea[href="/crates/{name}"]Default tab
Tab: Versionsa[href="/crates/{name}/versions"]Shows version count in text
Tab: Dependenciesa[href="/crates/{name}/dependencies"]
Tab: Dependentsa[href="/crates/{name}/reverse_dependencies"]
Tab: Securitya[href="/crates/{name}/security"]
Keyword linksa[href^="/keywords/"]e.g. #serialization
Category linksa[href^="/categories/"]
Homepage linka[href*="//"][href*="."][href^="http"]:not([href*="crates.io"])External homepage link in sidebar

Versions Page (/crates/{name}/versions)

ElementSelectorNotes
Version linksa[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

StepActionTargetValueWait ForTimeout
1navigatehttps://crates.io/input[aria-label="Search"] visible8s
2typeinput[aria-label="Search"]Crate name
3pressinput[aria-label="Search"]Enternetworkidle5s

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

StepActionTargetValueWait ForTimeout
1navigatehttps://crates.io/crates/{name}span.version visible8s
2readspan.version, button[title="Copy Cargo.toml snippet to clipboard"], page bodyReadme 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

StepActionTargetWait ForTimeout
1navigatehttps://crates.io/crates/{name}/versionsa[href^="/crates/{name}/"] visible
2reada[href^="/crates/{name}/"]All version links loaded

To navigate to a specific version:

StepActionTargetWait ForTimeout
3clicka[href="/crates/{name}/{version}"]Version detail loads5s

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

StepActionTargetNotes
1fetchhttps://crates.io/api/v1/crates/{name}Header: User-Agent: myapp/1.0 required
2readcrate.name, crate.max_stable_version, crate.descriptionCore 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:

FieldDescription
crate.max_stable_versionLatest non-prerelease version — use this for Cargo.toml
crate.max_versionLatest version — may be a pre-release
crate.recent_downloadsDownloads in last 90 days — use for activity/health signal
versions[].yankedtrue means this version is retracted — do not use
versions[].rust_versionMinimum Rust version (MSRV) — null means unspecified
versions[].featuresFeature flags object — not available in browser UI
versions[].published_by.loginPublisher'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")

  1. Fetch both: GET /api/v1/crates/reqwest and GET /api/v1/crates/ureq (can be parallel)
  2. Compare from crate{}: downloads, recent_downloads, description, repository
  3. Compare from versions[0]: license, rust_version (MSRV), features keys
  4. Fetch deps for both: GET /api/v1/crates/{name}/{version}/dependencies
  5. Compare: required dep count (kind="normal" and optional=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?"

  1. Check versions[0].yanked — if true, latest version is retracted; check versions[1] for last good version
  2. Check crate.recent_downloads relative to crate.downloads — if < 0.1% of total and total is large, the crate may be abandoned
  3. Navigate browser to /crates/{name}/security — look for advisory entries
  4. Check crate.repository exists and is a valid GitHub/GitLab URL (null = no source)
  5. 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"

  1. Fetch: GET /api/v1/crates/{name} — read crate.max_stable_version
  2. Output the Cargo.toml line: {name} = "{max_stable_version}"
  3. If the crate has feature flags the user wants, read versions[0].features and append: {name} = { version = "{version}", features = ["feature1"] }

Dynamic Content Patterns

  • Ember.js SPA: Every interaction is client-side. Always wait for networkidle (not domcontentloaded) after navigation. Expect 2–5s load time for the JS bundle.
  • ALL CSS classes are hashed: Class names like crate-spec_ee3a027e7 are Ember.js CSS Modules with a content hash — they change on every deploy. Never use class selectors except span.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 networkidle after each tab click.
  • Search is client-side rendered: The search results page fetches from /api/v1/crates?q=... and renders in Ember. Wait for networkidle before reading results.

Timing & Waiting

TransitionWait ForTypical Delay
Initial page loadnetworkidle2–5s (JS bundle + API fetch)
Search resultsnetworkidle1–3s
Tab click (Versions, Dependencies)networkidle1–2s
Type to search fieldNo wait — instant

Rate Limits

ScopeLimitNotes
API~1 req/sec recommendedNo hard limit documented; burst of ~10 tolerated before 429
API (429)Retry-After headerBack off and retry after the specified delay
BrowserNo documented limitRapid 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

  1. 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 is span.version. Use attribute selectors ([aria-label], [title], [role], [href]) exclusively.

  2. Ember IDs change on every render. id="ember4" is meaningless — skip it entirely. Same for any #ember* selector.

  3. domcontentloaded is useless here. The HTML shell is empty JavaScript. Use networkidle — it fires after Ember finishes fetching data and rendering.

  4. User-Agent required for API. The crates.io API returns 403 without a User-Agent header. Set User-Agent: yourapp/1.0 ([email protected]) per their policy.

  5. 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 for networkidle after clicking a tab.

  6. Two search inputs. There's a desktop search (input[aria-label="Search"]) and a hidden mobile search. Both have aria-label="Search" — the first one in DOM order is desktop. Use document.querySelector('input[aria-label="Search"]') to get the first (desktop) one.

  7. Yanked versions appear in history. The /versions page lists all versions including yanked ones. Check versions[].yanked in the API response, or look for [title*="yanked"] elements in the browser.

  8. max_version vs max_stable_version. The API returns both. If a crate has a pre-release (e.g. 2.0.0-beta.1), max_version may be the pre-release while max_stable_version is the last stable. Always prefer max_stable_version unless 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. #ember4 today 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-Agent header 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-child or positional selectors — Ember re-renders lists and positional indexes are unstable.

Freshness

Skill verified2026-02-20
API stable since2020 — v1 API has had no breaking changes
Selector stabilityLOW — Ember CSS Modules hash all class names on every deploy; only span.version is stable
Breaking change riskLow for API, High for browser selectors