Install any skill in seconds. Free to start, no credit card required.
Get Started Free →DOI content negotiation and metadata retrieval techniques
.claude/skills/brycewang-stanford-doi-resolution-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 167% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 68% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 93% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 56% | 0% |
Master DOI content negotiation to programmatically retrieve structured metadata, citation data, and formatted references from any Digital Object Identifier.
Every DOI (e.g., 10.1038/s41586-021-03819-2) resolves to a landing page by default. However, the DOI system supports HTTP content negotiation: by sending different Accept headers, you can retrieve structured metadata in various formats instead of an HTML page.
The DOI resolver endpoint is https://doi.org/{doi} or equivalently https://dx.doi.org/{doi}.
| Accept Header | Format | Use Case | |---------------|--------|----------| | application/vnd.citationstyles.csl+json | CSL-JSON | Programmatic metadata extraction | | text/x-bibliography; style=apa | Formatted citation | Ready-to-paste APA reference | | text/x-bibliography; style=bibtex | BibTeX | LaTeX bibliography import | | application/x-bibtex | BibTeX (alt) | LaTeX bibliography import | | application/rdf+xml | RDF/XML | Linked data applications | | text/turtle | Turtle RDF | Linked data applications | | application/vnd.crossref.unixref+xml | CrossRef Unixref | Full CrossRef metadata |
bashcurl -LH "Accept: application/vnd.citationstyles.csl+json" \ https://doi.org/10.1038/s41586-021-03819-2
pythonimport requests doi = "10.1038/s41586-021-03819-2" headers = {"Accept": "application/vnd.citationstyles.csl+json"} response = requests.get(f"https://doi.org/{doi}", headers=headers, allow_redirects=True) metadata = response.json() print(f"Title: {metadata['title']}") print(f"Authors: {', '.join(a.get('family', '') for a in metadata.get('author', []))}") print(f"Journal: {metadata.get('container-title', 'N/A')}") print(f"Year: {metadata.get('published', {}).get('date-parts', [[None]])[0][0]}") print(f"Type: {metadata.get('type')}")
bash# APA format curl -LH "Accept: text/x-bibliography; style=apa" \ https://doi.org/10.1038/s41586-021-03819-2 # Chicago format curl -LH "Accept: text/x-bibliography; style=chicago-author-date" \ https://doi.org/10.1038/s41586-021-03819-2 # Harvard format curl -LH "Accept: text/x-bibliography; style=harvard-cite-them-right" \ https://doi.org/10.1038/s41586-021-03819-2
bashcurl -LH "Accept: application/x-bibtex" \ https://doi.org/10.1038/s41586-021-03819-2
Output:
bibtex@article{Jumper_2021, title={Highly accurate protein structure prediction with AlphaFold}, volume={596}, DOI={10.1038/s41586-021-03819-2}, journal={Nature}, author={Jumper, John and Evans, Richard and ...}, year={2021}, pages={583--589} }
The CrossRef API provides richer metadata and supports batch queries without content negotiation.
pythonimport requests doi = "10.1038/s41586-021-03819-2" response = requests.get( f"https://api.crossref.org/works/{doi}", headers={"User-Agent": "ResearchClaw/1.0 (mailto:you@university.edu)"} ) work = response.json()["message"] print(f"Title: {work['title'][0]}") print(f"Publisher: {work['publisher']}") print(f"Citation count: {work.get('is-referenced-by-count', 0)}") print(f"Reference count: {work.get('references-count', 0)}") print(f"License: {work.get('license', [{}])[0].get('URL', 'N/A')}")
pythondois = [ "10.1038/s41586-021-03819-2", "10.1126/science.abj8754", "10.1016/j.cell.2021.06.025" ] results = [] for doi in dois: resp = requests.get( f"https://api.crossref.org/works/{doi}", headers={"User-Agent": "ResearchClaw/1.0 (mailto:you@university.edu)"} ) if resp.status_code == 200: results.append(resp.json()["message"]) else: print(f"Failed to resolve: {doi}")
pythonimport re def normalize_doi(raw_input): """Extract and normalize a DOI from various input formats.""" # Match DOI pattern: 10.XXXX/... match = re.search(r'(10\.\d{4,9}/[^\s]+)', raw_input) if match: doi = match.group(1) # Remove trailing punctuation doi = doi.rstrip('.,;:)') return doi.lower() return None # Examples normalize_doi("https://doi.org/10.1038/s41586-021-03819-2") # 10.1038/s41586-021-03819-2 normalize_doi("DOI: 10.1038/s41586-021-03819-2.") # 10.1038/s41586-021-03819-2 normalize_doi("See paper at doi.org/10.1038/s41586-021-03819-2 for details") # works too
User-Agent header that includes a mailto: contact. This is their "polite pool" with higher rate limits.allow_redirects=True (or -L in curl) as DOIs redirect through the resolver.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 11,181 | 9,199 | -18% | 1 | 1 | 0% | 2,174 | 3,658 | +68% | 0 | 0 | — |
case-04 | pass→pass | 40,337 | 11,872 | -71% | 1 | 1 | 0% | 2,045 | 3,955 | +93% | 0 | 0 | — |
case-01 | pass→pass | 15,964 | 40,880 | +156% | 1 | 1 | 0% | 2,656 | 4,137 | +56% | 0 | 0 | — |
case-02 | fail→fail | 19,020 | 13,094 | -31% | 1 | 1 | 0% | 3,529 | 4,054 | +15% | 0 | 0 | — |
case-03 | pass→pass | 15,859 | 18,475 | +16% | 1 | 1 | 0% | 2,496 | 5,656 | +127% | 0 | 0 | — |
case-06 | fail→pass | 19,666 | 7,037 | -64% | 1 | 1 | 0% | 1,191 | 3,180 | +167% | 0 | 0 | — |
case-07 | pass→pass | 4,132 | 2,490 | -40% | 1 | 1 | 0% | 826 | 2,442 | +196% | 0 | 0 | — |
case-08 | pass→pass | 9,668 | 3,800 | -61% | 1 | 1 | 0% | 1,359 | 2,464 | +81% | 0 | 0 | — |
case-09 | pass→pass | 6,397 | 3,245 | -49% | 1 | 1 | 0% | 1,395 | 2,614 | +87% | 0 | 0 | — |
case-10 | pass→pass | 6,870 | 4,258 | -38% | 1 | 1 | 0% | 1,106 | 2,718 | +146% | 0 | 0 | — |
case-11 | pass→pass | 5,107 | 3,050 | -40% | 1 | 1 | 0% | 947 | 2,322 | +145% | 0 | 0 | — |
case-12 | pass→pass | 4,837 | 2,494 | -48% | 1 | 1 | 0% | 878 | 2,372 | +170% | 0 | 0 | — |
case-13 | pass→pass | 9,439 | 6,587 | -30% | 1 | 1 | 0% | 1,761 | 3,031 | +72% | 0 | 0 | — |
case-14 | pass→pass | 4,015 | 3,832 | -5% | 1 | 1 | 0% | 814 | 2,539 | +212% | 0 | 0 | — |
case-15 | pass→pass | 8,978 | 8,722 | -3% | 1 | 1 | 0% | 1,848 | 3,552 | +92% | 0 | 0 | — |
case-16 | pass→pass | 13,660 | 16,897 | +24% | 1 | 1 | 0% | 2,156 | 4,694 | +118% | 0 | 0 | — |
case-17 | pass→pass | 6,914 | 3,541 | -49% | 1 | 1 | 0% | 1,071 | 2,616 | +144% | 0 | 0 | — |
case-18 | pass→pass | 6,922 | 2,838 | -59% | 1 | 1 | 0% | 1,223 | 2,282 | +87% | 0 | 0 | — |
case-19 | pass→pass | 7,882 | 4,876 | -38% | 1 | 1 | 0% | 1,513 | 2,904 | +92% | 0 | 0 | — |
case-20 | fail→pass | 6,700 | 7,080 | +6% | 1 | 1 | 0% | 1,288 | 3,029 | +135% | 0 | 0 | — |
case-21 | pass→pass | 4,449 | 4,157 | -7% | 1 | 1 | 0% | 799 | 2,580 | +223% | 0 | 0 | — |
case-22 | pass→pass | 12,236 | 12,375 | +1% | 1 | 1 | 0% | 1,863 | 3,701 | +99% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +9 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.