
Aminer Academic Search
- 95 installs
- 55 repo stars
- Updated July 23, 2026
- canxiangcc/aminer-open-skill
Refine and polish academic or technical writing
About
Processes and refines academic and technical writing into polished form. Useful during the research and idea phases for documenting findings and structuring papers.
- Text enhancement
- Academic writing
- Content refinement
Aminer Academic Search by the numbers
- 95 all-time installs (skills.sh)
- Ranked #1,382 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/canxiangcc/aminer-open-skill --skill aminer-academic-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 95 |
|---|---|
| repo stars | ★ 55 |
| Last updated | July 23, 2026 |
| Repository | canxiangcc/aminer-open-skill ↗ |
What it does
Refine and polish academic or technical writing
What you get
- refined content
- formatted output
Files
AMiner Open Platform Academic Data Query
27 APIs + 5 workflows. Token required: set AMINER_API_KEY env var.
- Docs: https://open.aminer.cn/open/docs | Console: https://open.aminer.cn/open/board?tab=control
---
Mandatory Rules (Critical)
1. Token Security: Only check whether AMINER_API_KEY exists; never expose the token in plain text anywhere. 2. Cost Control: Prefer optimal combined queries; never do indiscriminate full-detail retrieval. Default to top 10 details when the user has not specified a count. 3. Free-First: Prefer free APIs unless the user explicitly requires deeper fields; only upgrade to paid APIs when free ones cannot satisfy the need. 4. Result Links: Always append an accessible URL after each entity in the output. 5. Disambiguation: Scholar ambiguity → filter by org/org_id or ask user to confirm. Org ambiguity → use org_disambiguate_pro. Paper ambiguity → cross-check year + venue_name + first_author. 6. Cost Report: After completing all API calls, always output a cost summary to the user showing: each API called, its unit price, number of calls, and the total cost. Format example: [Cost] ¥X.XX total, N API calls (api_a: ¥X.XX × N, api_b: Free × N). 7. High-Cost Confirmation (≥ ¥5): Before executing a workflow or call chain whose estimated total cost is ¥5.00 or more, stop and ask the user for confirmation first. Show the planned call chain, estimated cost per step, and the total. Only proceed after the user explicitly agrees. This applies to both predefined workflows (e.g., Scholar Profile ~¥6.00) and ad-hoc multi-step plans.
Entity URL templates (mandatory):
- Paper:
https://www.aminer.cn/pub/{paper_id} - Scholar:
https://www.aminer.cn/profile/{scholar_id} - Patent:
https://www.aminer.cn/patent/{patent_id} - Journal:
https://www.aminer.cn/open/journal/detail/{journal_id}
---
Token Check (Required)
Check AMINER_API_KEY exists before any API call. Never expose token in plain text.
[ -z "${AMINER_API_KEY+x}" ] && echo "AMINER_API_KEY missing" || echo "AMINER_API_KEY exists"- If
${AMINER_API_KEY}exists: proceed. If not: check--tokenparameter. If neither: stop, guide user to Console to generate one. - If the user provides
AMINER_API_KEYinline (e.g. "My token is xxx"), accept it for the current session, but recommend setting it as an environment variable for better security. - Default headers:
Authorization: ${AMINER_API_KEY},X-Platform: openclaw,Content-Type: application/json;charset=utf-8(POST).
---
Call Guardrails
1. Parameter names and types must match references/api-catalog.md exactly. 2. paper_info is batch-only: {"ids": [...]}. paper_detail is single-paper only: one id. Never mix them. 3. When multiple details are needed, filter with a low-cost API first, then fetch details for a small set.
---
Paper Search API Selection Guide
When the user says "search for papers", determine the goal first:
| API | Focus | Use Case | Cost |
|---|---|---|---|
paper_search | Title search → paper_id | Known paper title, locate target | Free |
paper_search_pro | Multi-condition search (author/org/venue/keyword) | Topic search, sort by citations or year | ¥0.01 |
paper_qa_search | Natural language Q&A / topic keyword search | Semantic search, structured keyword OR/AND | ¥0.05 |
paper_list_by_keywords | Multi-keyword batch retrieval | Batch thematic retrieval | ¥0.10 |
paper_detail_by_condition | Year + venue dimension | Journal annual monitoring | ¥0.20 |
Default routing:
1. Known title: paper_search -> paper_detail -> paper_relation 2. Conditional filtering: paper_search_pro -> paper_detail 3. Natural language Q&A: paper_qa_search (fall back to paper_search_pro if no results) 4. Journal annual analysis: venue_search -> venue_paper_relation -> paper_detail_by_condition
Key paper_qa_search rules:
queryandtopic_high/topic_middle/topic_loware mutually exclusive; do not pass both.querymode: pass a natural language string.topic_*mode: expand synonyms/English variants first.- Supports
sci_flag,force_citation_sort,force_year_sort,author_id,org_id,venue_idsfilters.
Free-tier screening fields available:
paper_search:venue_name,first_author,n_citation_bucket,yearpaper_info:abstract_slice,year,venue_id,author_countperson_search:interests,n_citation,org/org_idorganization_search:aliasesvenue_search:aliases,venue_typepatent_search:inventor_name,app_year,pub_yearpatent_info:app_year,pub_year
---
Handling Out-of-Workflow Requests
When the user's request falls outside the 5 workflows:
1. Read references/api-catalog.md to confirm available APIs, parameters, and response fields. 2. Design the shortest viable call chain: locate ID → supplement details → expand relationships. 3. Do not give up because "no existing workflow fits"; actively compose APIs based on api-catalog.
---
5 Combined Workflows
Workflow 1: Scholar Profile (~¥6.00)
Use Case: Complete academic profile — bio, research interests, papers, patents, projects. Cost note: Full execution exceeds the ¥5 threshold → must ask for user confirmation before proceeding (Rule 7). Show the planned steps and cost. Confirm which sub-modules are needed; skip patents/projects if not requested.
Call Chain:
Scholar search (name → person_id)
↓
Parallel calls (pick as needed):
├── Scholar details (bio/education/honors) ¥1.00
├── Scholar portrait (interests/work history) ¥0.50
├── Scholar papers (paper list) ¥1.50
├── Scholar patents (patent list) ¥1.50
└── Scholar projects (funding info) ¥1.50Fallback: if paper_search yields no results in sub-steps, fall back to paper_search_pro.
---
Workflow 2: Paper Deep Dive (~¥0.12)
Use Case: Full paper information and citation chain from a title or keyword.
Call Chain:
Paper search / Paper search pro (title/keyword → paper_id)
↓
Paper details (abstract/authors/DOI/journal/year/keywords) ¥0.01
↓
Paper citations (cited papers → cited_ids) ¥0.10
↓
(Optional) Batch paper_info for cited papers FreeFallback: if paper_search yields no results, fall back to paper_search_pro.
---
Workflow 3: Org Analysis (~¥0.81)
Use Case: Institution scholar size, paper output, patent count — for competitive research or partnership evaluation.
Call Chain:
Org disambiguation pro (raw string → org_id) ¥0.05
↓
Parallel calls:
├── Org details (description/type) ¥0.01
├── Org scholars (scholar list, 10/call) ¥0.50
├── Org papers (paper list, 10/call) ¥0.10
└── Org patents (patent IDs, up to 10,000) ¥0.10If disambiguation pro returns no ID, fall back to org_search (free).---
Workflow 4: Venue Papers (~¥0.10 - ¥0.30)
Use Case: Track journal papers by year; useful for submission research or trend analysis.
Call Chain:
Venue search (name → venue_id) Free
↓
(Optional) Venue details (ISSN/type/abbreviation) ¥0.20
↓
Venue papers (venue_id + year → paper_id list) ¥0.10
↓
(Optional) Batch paper detail query---
Workflow 5: Patent Analysis (~¥0.02)
Use Case: Search patents in a technology domain, or retrieve a scholar's/institution's patent portfolio.
Call Chain (standalone search):
Patent search (query → patent_id) Free
↓
Patent info / Patent details Free / ¥0.01Call Chain (via scholar/institution):
Scholar search → Scholar patents (patent_id list)
Org disambiguation → Org patents (patent_id list)
↓
Patent info / Patent details---
Individual API Quick Reference
Full parameter docs: read references/api-catalog.md| # | Title | Method | Price | API Path (Base: datacenter.aminer.cn/gateway/open_platform) |
|---|---|---|---|---|
| 1 | Paper QA Search | POST | ¥0.05 | /api/paper/qa/search |
| 2 | Scholar Search | POST | Free | /api/person/search |
| 3 | Paper Search | GET | Free | /api/paper/search |
| 4 | Paper Search Pro | GET | ¥0.01 | /api/paper/search/pro |
| 5 | Patent Search | POST | Free | /api/patent/search |
| 6 | Org Search | POST | Free | /api/organization/search |
| 7 | Venue Search | POST | Free | /api/venue/search |
| 8 | Scholar Details | GET | ¥1.00 | /api/person/detail |
| 9 | Scholar Projects | GET | ¥1.50 | /api/project/person/v3/open |
| 10 | Scholar Papers | GET | ¥1.50 | /api/person/paper/relation |
| 11 | Scholar Patents | GET | ¥1.50 | /api/person/patent/relation |
| 12 | Scholar Portrait | GET | ¥0.50 | /api/person/figure |
| 13 | Paper Info | POST | Free | /api/paper/info |
| 14 | Paper Details | GET | ¥0.01 | /api/paper/detail |
| 15 | Paper Citations | GET | ¥0.10 | /api/paper/relation |
| 16 | Patent Info | GET | Free | /api/patent/info |
| 17 | Patent Details | GET | ¥0.01 | /api/patent/detail |
| 18 | Org Details | POST | ¥0.01 | /api/organization/detail |
| 19 | Org Patents | GET | ¥0.10 | /api/organization/patent/relation |
| 20 | Org Scholars | GET | ¥0.50 | /api/organization/person/relation |
| 21 | Org Papers | GET | ¥0.10 | /api/organization/paper/relation |
| 22 | Venue Details | POST | ¥0.20 | /api/venue/detail |
| 23 | Venue Papers | POST | ¥0.10 | /api/venue/paper/relation |
| 24 | Org Disambiguation | POST | ¥0.01 | /api/organization/na |
| 25 | Org Disambiguation Pro | POST | ¥0.05 | /api/organization/na/pro |
| 26 | Paper Batch Query | GET | ¥0.10 | /api/paper/list/citation/by/keywords |
| 27 | Paper Details by Year+Venue | GET | ¥0.20 | /api/paper/platform/allpubs/more/detail/by/ts/org/venue |
---
References
- Full API parameter documentation: read
references/api-catalog.md - Optional Python client:
scripts/aminer_client.py - Test cases:
evals/evals.json - Official documentation: https://open.aminer.cn/open/docs
- Console: https://open.aminer.cn/open/board?tab=control
{
"skill_name": "aminer-academic-search",
"evals": [
{
"id": 1,
"prompt": "I'd like to understand Andrew Ng's complete academic profile, including his research interests, important papers he has published, any patents, and research projects he has participated in. Please use the AMiner API to look this up for me. My token is <YOUR_TOKEN>",
"expected_output": "The estimated cost (~¥6.00) exceeds the ¥5 threshold. Before executing, display the planned call chain and cost breakdown, and ask the user for confirmation. After the user confirms, run the scholar_profile workflow. After completion, output a cost summary.",
"files": [],
"expectations": [
"Displayed the planned call chain with estimated cost BEFORE making paid API calls",
"Estimated total cost exceeds ¥5, so asked the user for confirmation before proceeding",
"After user confirmation, called Scholar Search, Scholar Details, Scholar Portrait, Scholar Papers, etc.",
"Output contains the selected.name field with value Andrew Ng",
"Output contains a cost summary showing each API called and total cost"
]
},
{
"id": 2,
"prompt": "Help me deep-dive into the paper 'Attention Is All You Need' — find it, retrieve its full details (abstract, authors, year), and then see what references it cites. My AMiner token is <YOUR_TOKEN>",
"expected_output": "Run the paper_deep_dive workflow: search for the paper → retrieve details → retrieve citation relationships. Output the paper abstract, author list, DOI, and list of cited papers.",
"files": [],
"expectations": [
"Called a Paper Search API to locate the target paper",
"Called the Paper Details API (paper_detail) to retrieve the abstract",
"Called the Paper Citations API (paper_relation) to retrieve the citation list",
"Output contains the detail field with an abstract sub-field",
"Output contains the citations_count or citations_preview field"
]
},
{
"id": 3,
"prompt": "I need to analyze MIT's research strength — I'd like to know which prominent scholars are affiliated with them, what papers they have published, and what patents they hold. Please use the AMiner API to compile a report for me. Token: <YOUR_TOKEN>",
"expected_output": "Run the org_analysis workflow: use Org Disambiguation Pro to find MIT's institution ID, then fetch institution details, scholar list, and paper list in parallel, and compile into a report.",
"files": [],
"expectations": [
"Called the Org Disambiguation Pro API (org_disambiguate_pro) to obtain the institution ID",
"Called the Org Details API (org_detail) to retrieve MIT's description",
"Called the Org Scholars API (org_person_relation) to retrieve the scholar list",
"Called the Org Papers API (org_paper_relation) to retrieve the paper list",
"Output contains the org_id field",
"Output contains the scholars or papers field"
]
},
{
"id": 4,
"prompt": "Help me look up Andrew Ng's publication trends",
"expected_output": "Detect that the user has not provided a token; immediately stop all subsequent API calls and guide the user to obtain a token from the AMiner Console.",
"files": [],
"expectations": [
"Clearly inform the user that a token is currently missing and AMiner API calls cannot continue",
"No API calls were made",
"Provide the console link https://open.aminer.cn/open/board?tab=control",
"Prompt the user to continue after obtaining a token"
]
},
{
"id": 5,
"prompt": "Help me look up Fei-Fei Li from Stanford University on AMiner and give me her 5 most-cited papers and her 5 most recent papers. My token is <YOUR_TOKEN>",
"expected_output": "Prefer paper_qa_search combined with author_id/org_id filters. First return the 5 most-cited papers (force_citation_sort), then return the 5 most recent papers (force_year_sort).",
"files": [],
"expectations": [
"Called the Scholar Search API (person_search) to confirm Fei-Fei Li's author_id",
"Called paper_qa_search with author_id and org_id filters",
"Executed two separate queries: one sorted by citation count and one sorted by year",
"Each result set returns at most 5 paper titles and paper IDs"
]
},
{
"id": 6,
"prompt": "Help me check what papers were published in NeurIPS in 2023. My token is <YOUR_TOKEN>",
"expected_output": "Run the venue_papers workflow: venue search to get venue_id, then venue_paper_relation with year=2023 to list papers.",
"files": [],
"expectations": [
"Called the Venue Search API (venue_search) to find NeurIPS venue_id",
"Called the Venue Papers API (venue_paper_relation) with year=2023",
"Output contains a list of paper titles and IDs",
"Each paper entity has an AMiner URL appended"
]
},
{
"id": 7,
"prompt": "Search for patents related to quantum computing chips and show me the details of the top results. My token is <YOUR_TOKEN>",
"expected_output": "Run patent_search to find patents, then patent_detail for the top results. Output patent titles, inventors, IPC codes, and abstracts.",
"files": [],
"expectations": [
"Called the Patent Search API (patent_search) with query about quantum computing",
"Called the Patent Details API (patent_detail) for at least one result",
"Output contains patent titles and patent URLs",
"Output includes structured fields like IPC or assignee"
]
},
{
"id": 8,
"prompt": "帮我查一下唐杰的学术信息,我的 token 是 <YOUR_TOKEN>",
"expected_output": "Handle Chinese input: search for the scholar by Chinese name, return profile information with AMiner URLs.",
"files": [],
"expectations": [
"Called the Scholar Search API (person_search) with a Chinese name",
"Successfully returned scholar results (name, org, interests)",
"Output contains the scholar's AMiner profile URL",
"Response is presented clearly despite Chinese input"
]
},
{
"id": 9,
"prompt": "Find me all details about a scholar named XXXNOEXISTSCHOLAR on AMiner. My token is <YOUR_TOKEN>",
"expected_output": "Handle empty results gracefully: inform the user that no matching scholar was found, suggest refining the query.",
"files": [],
"expectations": [
"Called the Scholar Search API (person_search)",
"Detected that no results were returned",
"Clearly informed the user that no matching scholar was found",
"Did NOT proceed to call paid detail APIs with invalid IDs"
]
},
{
"id": 10,
"prompt": "Help me find all papers about deep learning and give me the full details for every single one. My token is <YOUR_TOKEN>",
"expected_output": "Apply cost control: search returns many results, but only fetch details for the top 10 by default. Inform the user that results were limited to control costs.",
"files": [],
"expectations": [
"Called a paper search API and found many results",
"Did NOT call paper_detail for all results",
"Fetched details for at most 10 papers",
"Informed the user that results were limited and they can request more if needed"
]
},
{
"id": 11,
"prompt": "Call the AMiner venue_search API directly for 'Nature' and show me the raw result. My token is <YOUR_TOKEN>",
"expected_output": "Use the raw API call mode to invoke venue_search with name='Nature'. Return the raw JSON response including venue_id, standard name, aliases, and venue_type.",
"files": [],
"expectations": [
"Called venue_search API directly (raw mode or equivalent curl call)",
"Output includes the raw API response with venue_id and name fields",
"Output includes a venue URL for the result",
"Did not invoke any unnecessary paid APIs"
]
},
{
"id": 12,
"prompt": "I need to find patents held by Shou-Cheng Zhang. My token is <YOUR_TOKEN>",
"expected_output": "Run the scholar_patents workflow: search scholar by name, then retrieve patent list via person_patent_relation, then fetch patent details for top results. Output a cost summary at the end.",
"files": [],
"expectations": [
"Called Scholar Search API (person_search) to find the scholar",
"Called Scholar Patents API (person_patent_relation) to retrieve patent list",
"Called Patent Details API (patent_detail) for at least one patent",
"Output includes patent titles and patent URLs",
"Output includes a cost summary at the end"
]
},
{
"id": 13,
"prompt": "Help me deep-dive into the paper 'BERT: Pre-training of Deep Bidirectional Transformers' — get the full details and citation chain. My token is <YOUR_TOKEN>",
"expected_output": "Run the paper_deep_dive workflow (estimated ~¥0.12, below the ¥5 threshold, so no confirmation needed). After completion, output a cost summary showing each API called and total cost.",
"files": [],
"expectations": [
"Did NOT ask for cost confirmation (estimated cost is well below ¥5)",
"Called paper search, paper detail, and paper relation APIs",
"Output includes paper abstract, authors, and citation information",
"Output includes a cost summary at the end showing APIs called and total cost"
]
}
]
}
AMiner Open Platform API Complete Reference
Base URL: https://datacenter.aminer.cn/gateway/open_platform Authentication: All endpoints should default to Authorization: ${AMINER_API_KEY} and include X-Platform: openclaw in the request headers. Token: Log in to the Console to generate one, then export it as AMINER_API_KEY.
---
Table of Contents
---
Paper APIs
1. Paper Search
- URL:
GET /api/paper/search - Price: Free
- Description: Search by paper title; returns low-cost screening fields such as paper ID, title, DOI, venue, first author, citation bucket, and year.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | number | Yes | Page number (current online definition says it starts at 1) |
| size | number | No | Items per page, maximum 20 |
| title | string | Yes | Paper title |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title | Paper title |
| title_zh | Paper title (Chinese) |
| doi | DOI |
| first_author | First author |
| n_citation_bucket | Citation bucket: 0, 1-10, 11-50, 51-200, 200-1000, 1000-5000, 5000+ |
| venue_name | Venue title |
| year | Publication year |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/search?page=1&size=10&title=Looking+at+CTR+Prediction+Again%3A+Is+Attention+All+You+Need' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
2. Paper Search Pro
- URL:
GET /api/paper/search/pro - Price: ¥0.01/call
- Description: Multi-condition search; supports filtering by keyword, abstract, author, institution, and journal.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | number | No | Page number (starts at 0) |
| size | number | No | Items per page |
| title | string | No | Title keyword |
| keyword | string | No | Keyword |
| abstract | string | No | Abstract keyword |
| author | string | No | Author name |
| org | string | No | Institution name |
| venue | string | No | Journal name |
| order | string | No | Sort field: year (descending by year) or n_citation (descending by citations); omit for composite ranking |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title | Title (English) |
| title_zh | Title (Chinese) |
| doi | DOI |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/search/pro?title=transformer&author=Vaswani&order=n_citation&page=0&size=5' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
3. Paper QA Search
- URL:
POST /api/paper/qa/search - Price: ¥0.05/call
- Description: AI-powered intelligent Q&A search; supports natural language queries and structured keyword search.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| use_topic | boolean | Yes | Whether to use combined keyword search. When true, use topic fields; when false, use title/query. |
| topic_high | string | No | Valid when use_topic=true; keywords that must match (AND logic). Nested array format: [["termA","termB"],["termC"]] — outer AND, inner OR. |
| topic_middle | string | No | Strongly boosted terms; same format as topic_high. |
| topic_low | string | No | Weakly boosted terms; same format as topic_high. |
| title | []string | No | Title query when use_topic=false. |
| doi | string | No | Exact DOI query. |
| year | []number | No | Year filter array. |
| sci_flag | boolean | No | Return SCI papers only. |
| n_citation_flag | boolean | No | Boost papers with high citation counts. |
| size | number | No | Maximum number of results to return. |
| offset | number | No | Offset. |
| force_citation_sort | boolean | No | Sort entirely by citation count. |
| force_year_sort | boolean | No | Sort entirely by year. |
| author_terms | []string | No | Author name query; OR relationship within array; include multiple variants. |
| org_terms | []string | No | Institution name query; OR relationship within array. |
| author_id | []string | No | Author entity ID filter; accepts single ID or ID list. OR relationship with author_terms when both are provided. |
| org_id | []string | No | Institution entity ID filter; accepts single ID or ID list. OR relationship with org_terms when both are provided. |
| venue_ids | []string | No | Conference/journal ID list filter. |
| query | string | No | Raw natural language question (slower); system auto-extracts keywords. Takes precedence over topic_high when both are provided. |
Response Fields:
| Field | Description |
|---|---|
| data | Paper ID list |
| id | Paper ID |
| title | Paper title |
| title_zh | Title (Chinese) |
| doi | DOI |
| Total / total | Total count |
curl Example (natural language Q&A):
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/qa/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"use_topic": false, "query": "deep learning protein structure prediction", "size": 10, "sci_flag": true}'curl Example (structured keywords):
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/qa/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{
"use_topic": true,
"topic_high": "[[\\"transformer\\",\\"self-attention\\"],[\\"protein folding\\"]]",
"topic_middle": "[[\"AlphaFold\"]]",
"sci_flag": true,
"force_citation_sort": true,
"size": 10
}'---
4. Paper Info
- URL:
POST /api/paper/info - Price: Free
- Description: Batch-retrieve lightweight paper cards by paper ID, including abstract slice, year, venue ID, author list, and author count.
Mandatory Parameter Constraints (High Priority)
1.paper_infoonly supports the batch parameterids(array); it does not support a singlepaper_id.
2.paper_detailonly supports the single-paper parameterid(string); in the clientrawfunction wrapper, the corresponding parameter name ispaper_id.
3. Never passidstopaper_detail; doing so will trigger a parameter error (e.g.,unexpected keyword argument 'ids').
4. If many results are matched and the user has not specified a count, default to querying only the top 10 details to avoid unnecessary costs.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| ids | []string | Yes | Paper ID array, maximum 100 |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title | Paper title |
| abstract_slice | Partial abstract |
| authors | Author list (includes name / name_zh) |
| author_count | Total author count |
| issue | Volume number |
| raw | Journal name |
| venue | Journal info object |
| venue_id | Venue ID |
| year | Publication year |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/info' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"ids": ["53e9ab9bb7602d97023e53b2", "53e9a98eb7602d9703e42e5a"]}'Usage Note:
paper_info is a batch endpoint. Always pass ids as an array, even when querying only one paper.
---
5. Paper Details
- URL:
GET /api/paper/detail - Price: ¥0.01/call
- Description: Retrieve full paper details by paper ID.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Paper ID |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title | Title (English) |
| title_zh | Title (Chinese) |
| abstract | Abstract |
| abstract_zh | Abstract (Chinese) |
| authors | Author list (name/name_zh/org/org_zh) |
| doi | DOI |
| issn | ISSN |
| issue | Volume number |
| volume | Issue number |
| year | Year |
| keywords | Keywords |
| keywords_zh | Keywords (Chinese) |
| raw | Journal name |
| venue | Journal info object |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/detail?id=53e9ab9bb7602d97023e53b2' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'Usage Note:
paper_detail is a single-paper endpoint. Pass one id; do not pass ids.
---
6. Paper Citations
- URL:
GET /api/paper/relation - Price: ¥0.10/call
- Description: Retrieve the list of papers cited by a given paper ID.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Paper ID |
Response Fields:
| Field | Description |
|---|---|
| _id | Paper ID |
| title | Title |
| cited | Basic info of papers cited by this paper |
| n_citation | Number of times cited |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/relation?id=53e9ab9bb7602d97023e53b2' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
7. Paper Batch Query (Multi-keyword)
- URL:
GET /api/paper/list/citation/by/keywords - Price: ¥0.10/call
- Description: Retrieve paper keywords, abstracts, and other information via multiple keywords.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | number | Yes | Page number |
| size | number | Yes | Items per page |
| keywords | string | Yes | Keyword array (JSON string format) |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title / title_zh | Title (bilingual) |
| abstract / abstract_zh | Abstract (bilingual) |
| keywords / keywords_zh | Keywords (bilingual) |
| doi | DOI |
| year | Year |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/list/citation/by/keywords?page=0&size=10&keywords=%5B%22deep+learning%22%2C%22object+detection%22%5D' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
8. Paper Details by Year and Venue
- URL:
GET /api/paper/platform/allpubs/more/detail/by/ts/org/venue - Price: ¥0.20/call
- Description: Retrieve paper titles, authors, DOIs, keywords, and other details by publication year and journal.
Note:venue_idandyearmust be provided together; providing onlyyearreturnsnull. Use the Venue Search API first to obtain thevenue_id.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| year | number | Yes | Paper publication year |
| venue_id | string | Yes | Journal ID (obtained via Venue Search; returns null if not provided) |
Response Fields (main):
| Field | Description |
|---|---|
| _id | Paper ID |
| title / title_zh | Title (bilingual) |
| abstract | Abstract |
| authors | Author array (name/org/email/homepage/orc_id/_id) |
| doi | DOI |
| issn | ISSN |
| keywords / keywords_zh | Keywords (bilingual) |
| year | Year |
| venue | Journal info |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/paper/platform/allpubs/more/detail/by/ts/org/venue?year=2023&venue_id=<VENUE_ID>' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
Scholar APIs
9. Scholar Search
- URL:
POST /api/person/search - Price: Free
- Description: Search for scholar candidates by name and institution conditions; returns identity, institution, interests, and citation count.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | Scholar name |
| org | string | No | Institution name |
| org_id | []string | No | Institution entity ID array |
| offset | number | No | Starting position (fixed at 0; pagination not supported) |
| size | number | No | Number of results, maximum 10 |
Response Fields:
| Field | Description |
|---|---|
| id | Scholar ID |
| name | Name |
| name_zh | Name (Chinese) |
| org | Institution (English) |
| org_zh | Institution (Chinese) |
| org_id | Institution ID |
| interests | Research interests |
| n_citation | Citation count |
| total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/person/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"name": "Andrew Ng", "size": 5}'---
10. Scholar Details
- URL:
GET /api/person/detail - Price: ¥1.00/call
- Description: Retrieve complete personal information by scholar ID.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scholar ID |
Response Fields:
| Field | Description |
|---|---|
| id / person_id | Scholar ID |
| name / name_zh | Name (bilingual) |
| bio / bio_zh | Personal bio (bilingual; not both present simultaneously) |
| edu / edu_zh | Education history (bilingual) |
| orgs / org_zhs | Institution list (English / Chinese) |
| position / position_zh | Title (bilingual) |
| domain | Research domain |
| honor | Honors |
| award | Awards |
| year | Year |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/person/detail?id=53f3ae78dabfae4b34b0c75d' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
11. Scholar Portrait
- URL:
GET /api/person/figure - Price: ¥0.50/call
- Description: Retrieve research interests, domains, and structured work/education history.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scholar ID |
Response Fields:
| Field | Description |
|---|---|
| id | Scholar ID |
| ai_interests | Research interest list |
| ai_domain | Research domain list |
| edus | Structured education history |
| works | Structured work history |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/person/figure?id=53f3ae78dabfae4b34b0c75d' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
12. Scholar Papers
- URL:
GET /api/person/paper/relation - Price: ¥1.50/call
- Description: Retrieve a list of papers published by a scholar (ID + title).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scholar ID |
Response Fields:
| Field | Description |
|---|---|
| author_id | Scholar ID |
| id | Paper ID |
| title | Paper title |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/person/paper/relation?id=53f3ae78dabfae4b34b0c75d' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
13. Scholar Patents
- URL:
GET /api/person/patent/relation - Price: ¥1.50/call
- Description: Retrieve a list of patents associated with a scholar.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scholar ID |
Response Fields:
| Field | Description |
|---|---|
| patent_id | Patent ID |
| person_id | Scholar ID |
| title | Patent title |
| en | Title (English) |
| zh | Title (Chinese) |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/person/patent/relation?id=53f3ae78dabfae4b34b0c75d' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
14. Scholar Projects
- URL:
GET /api/project/person/v3/open - Price: ¥1.50/call
- Description: Retrieve research projects a scholar has participated in (funding amount, dates, source).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | No | Scholar ID |
Response Fields:
| Field | Description |
|---|---|
| id | Project ID |
| titles | Project title |
| country | Country |
| project_source | Project source |
| fund_amount | Funding amount |
| fund_currency | Funding currency |
| start_date | Start date |
| end_date | End date |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/project/person/v3/open?id=53f3ae78dabfae4b34b0c75d' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
Institution APIs
15. Org Search
- URL:
POST /api/organization/search - Price: Free
- Description: Search for institution IDs and standard names by institution keyword; includes partial aliases for normalization.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orgs | []string | No | Institution name array |
Response Fields:
| Field | Description |
|---|---|
| aliases | Alias list (partial, usually top 3) |
| org_id | Institution ID |
| org_name | Institution name |
| total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"orgs": ["Tsinghua University"]}'---
16. Org Details
- URL:
POST /api/organization/detail - Price: ¥0.01/call
- Description: Retrieve institution details by institution ID.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| ids | []string | Yes | Institution ID array |
Response Fields:
| Field | Description |
|---|---|
| id | Institution ID |
| name / name_en / name_zh | Institution name (raw/English/Chinese) |
| acronyms | Abbreviation |
| aliases | Alias list |
| details | Detailed institution description |
| type | Institution type (university/enterprise, etc.) |
| location | Geographic location |
| language | Language |
| total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/detail' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"ids": ["5f71b2091c455f439fe9a7d7"]}'---
17. Org Scholars
- URL:
GET /api/organization/person/relation - Price: ¥0.50/call
- Description: Retrieve the list of scholars affiliated with an institution (10 results per call).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| org_id | string | No | Institution ID |
| offset | number | No | Starting position (returns 10 results per call) |
Response Fields:
| Field | Description |
|---|---|
| id | Scholar ID |
| name / name_zh | Scholar name (bilingual) |
| org / org_zh | Institution (bilingual) |
| org_id | Institution ID |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/person/relation?org_id=5f71b2091c455f439fe9a7d7&offset=0' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
18. Org Papers
- URL:
GET /api/organization/paper/relation - Price: ¥0.10/call
- Description: Retrieve the list of papers published by scholars at an institution (10 results per call).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| org_id | string | Yes | Institution ID |
| offset | number | Yes | Starting position (returns 10 results per call) |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title / title_zh | Title (bilingual) |
| doi | DOI |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/paper/relation?org_id=5f71b2091c455f439fe9a7d7&offset=0' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
19. Org Patents
- URL:
GET /api/organization/patent/relation - Price: ¥0.10/call
- Description: Retrieve the list of patent IDs owned by an institution; supports pagination with up to 10,000 results per call.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Institution ID |
| page | number | No | Page number (starts at 1) |
| page_size | number | No | Items per page; maximum 10,000 |
Response Fields:
| Field | Description |
|---|---|
| id | Patent ID |
| total | Total count |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/patent/relation?id=6233173d0a6eb145604733e2&page=1&page_size=100' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
20. Org Disambiguation
- URL:
POST /api/organization/na - Price: ¥0.01/call
- Description: Retrieve the standardized institution name from an institution string (including abbreviations/aliases).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| org | string | Yes | Institution name (may include aliases/abbreviations) |
Response Fields:
| Field | Description |
|---|---|
| org_name | Normalized institution name |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/na' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"org": "MIT CSAIL"}'---
21. Org Disambiguation Pro
- URL:
POST /api/organization/na/pro - Price: ¥0.05/call
- Description: Extract the IDs of primary and secondary institutions from an institution string (recommended for workflows).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| org | string | Yes | Institution name |
Response Fields:
| Field | Description |
|---|---|
| 一级 | Primary institution name |
| 一级ID | Primary institution ID |
| 二级 | Secondary institution name |
| 二级ID | Secondary institution ID |
| Total / total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/organization/na/pro' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"org": "Department of Computer Science, Tsinghua University"}'---
Journal APIs
22. Venue Search
- URL:
POST /api/venue/search - Price: Free
- Description: Search for venue IDs and standard names by venue name; includes aliases and venue type.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | Journal name (supports fuzzy search) |
Response Fields:
| Field | Description |
|---|---|
| id | Journal ID |
| name_en | Journal name (English) |
| name_zh | Journal name (Chinese) |
| aliases | Alias list (partial, usually top 3) |
| venue_type | Venue type: journal or conference |
| total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/venue/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"name": "tkde"}'---
23. Venue Details
- URL:
POST /api/venue/detail - Price: ¥0.20/call
- Description: Retrieve ISSN, abbreviation, type, and other details by journal ID.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Journal ID |
Response Fields:
| Field | Description |
|---|---|
| id | Journal ID |
| name / name_en / name_zh | Name (raw/English/Chinese) |
| issn | ISSN |
| eissn | EISSN |
| alias | Alias |
| type | Journal type |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/venue/detail' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"id": "<VENUE_ID>"}'---
24. Venue Papers
- URL:
POST /api/venue/paper/relation - Price: ¥0.10/call
- Description: Retrieve a list of papers for a journal by journal ID (supports year filtering).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Journal ID |
| offset | number | No | Starting position |
| limit | number | No | Number of results to return |
| year | number | No | Filter by year |
Response Fields:
| Field | Description |
|---|---|
| id | Paper ID |
| title | Paper title |
| year | Year |
| offset | Current offset |
| total | Total count |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/venue/paper/relation' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"id": "<VENUE_ID>", "year": 2023, "offset": 0, "limit": 20}'---
Patent APIs
25. Patent Search
- URL:
POST /api/patent/search - Price: Free
- Description: Search for patents by title or keyword; returns lightweight trend fields such as first inventor, application year, and publication year.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | Query field, such as patent title or keywords |
| page | number | Yes | Page number |
| size | number | Yes | Items per page |
Response Fields:
| Field | Description |
|---|---|
| id | Patent ID |
| title | Patent title (English) |
| title_zh | Patent title (Chinese) |
| inventor_name | First inventor name |
| app_year | Application year |
| pub_year | Publication year |
curl Example:
curl -X POST \
'https://datacenter.aminer.cn/gateway/open_platform/api/patent/search' \
-H 'Content-Type: application/json;charset=utf-8' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw' \
-d '{"page":0,"query":"Si02","size":20}'---
26. Patent Info
- URL:
GET /api/patent/info - Price: Free
- Description: Retrieve a patent basic card by patent ID, including patent numbers, inventor, country, and basic year fields.
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Patent ID |
Response Fields:
| Field | Description |
|---|---|
| id | Patent ID |
| title / en | Patent title (English) |
| app_num | Application number |
| pub_num | Publication number |
| pub_kind | Publication type |
| inventor | Inventor |
| country | Country |
| sequence | Sequence |
| app_year | Application year |
| pub_year | Publication year |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/patent/info?id=<PATENT_ID>' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
27. Patent Details
- URL:
GET /api/patent/detail - Price: ¥0.01/call
- Description: Retrieve full patent details by patent ID (including abstract, filing date, assignee, IPC classification, etc.).
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Patent ID |
Response Fields:
| Field | Description |
|---|---|
| id | Patent ID |
| title | Patent title |
| abstract | Abstract |
| app_date | Filing date |
| app_num | Application number |
| pub_date | Publication date |
| pub_num | Publication number |
| pub_kind | Publication type |
| assignee | Assignee |
| inventor | Inventor |
| country | Country |
| ipc | IPC classification code |
| ipcr | IPCR classification code |
| cpc | CPC classification code |
| priority | Priority info |
| description | Description |
curl Example:
curl -X GET \
'https://datacenter.aminer.cn/gateway/open_platform/api/patent/detail?id=<PATENT_ID>' \
-H 'Authorization: ${AMINER_API_KEY}' \
-H 'X-Platform: openclaw'---
Appendix: API Pricing Summary
| Category | Free APIs | Paid APIs |
|---|---|---|
| Paper | Paper Search, Paper Info | Paper Search Pro(¥0.01), Paper Details(¥0.01), Paper Citations(¥0.10), Paper QA Search(¥0.05), Paper Batch Query(¥0.10), By Condition(¥0.20) |
| Scholar | Scholar Search | Scholar Details(¥1.00), Scholar Portrait(¥0.50), Scholar Papers(¥1.50), Scholar Patents(¥1.50), Scholar Projects(¥1.50) |
| Institution | Org Search | Org Details(¥0.01), Org Scholars(¥0.50), Org Papers(¥0.10), Org Patents(¥0.10), Org Disambiguation(¥0.01), Org Disambiguation Pro(¥0.05) |
| Journal | Venue Search | Venue Details(¥0.20), Venue Papers(¥0.10) |
| Patent | Patent Search, Patent Info | Patent Details(¥0.01) |
---
Appendix: Common Error Codes
| Code | Meaning | Recommended Action |
|---|---|---|
| 401 | Token invalid or expired | Re-generate token at Console |
| 403 | Insufficient balance or permission denied | Top up account or check token scope |
| 404 | Entity not found | Verify the ID is correct |
| 429 | Rate limit exceeded | Wait a few seconds and retry |
| 500 / 502 / 503 / 504 | Server error (transient) | Retry with exponential backoff (1s → 2s → 4s) |
For 4xx errors (except 429), do not retry — fix the request parameters first.---
Appendix: Pagination Limits
| API | Constraint |
|---|---|
paper_search | size max 20; page starts at 1 |
paper_search_pro | page starts at 0 |
person_search | size max 10; offset fixed at 0 (no pagination) |
org_person_relation | Fixed 10 results per call; use offset to paginate |
org_paper_relation | Fixed 10 results per call; use offset to paginate |
org_patent_relation | page_size max 10,000; page starts at 1 |
venue_paper_relation | Use offset + limit to paginate |
paper_info | ids array max 100 items |
#!/usr/bin/env python3
"""
AMiner Open Platform API Client
Optional convenience client for AMiner Open Platform.
The skills in this repository can be used directly with curl; this script is kept
as an optional local wrapper for users who prefer Python-based composition.
Usage:
python aminer_client.py --token <TOKEN> --action <ACTION> [options]
Workflows:
scholar_profile Scholar profile analysis (search → details + portrait + papers + patents + projects)
paper_deep_dive Paper deep dive (search → details + citation chain)
org_analysis Institution research capability analysis (disambiguation → details + scholars + papers + patents)
venue_papers Journal paper monitoring (search → details + papers by year)
paper_qa Academic Q&A (AI-driven keyword search)
patent_search Patent search and details
scholar_patents Retrieve all patent details for a scholar by name
Direct single API call:
raw Call any API directly; requires --api and --params
Console (Generate Token): https://open.aminer.cn/open/board?tab=control
Docs: https://open.aminer.cn/open/docs
"""
import argparse
import json
import os
import sys
import time
import random
import threading
import urllib.request
import urllib.error
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Any, Optional
BASE_URL = "https://datacenter.aminer.cn/gateway/open_platform"
REQUEST_TIMEOUT_SECONDS = 30
MAX_RETRIES = 3
RETRYABLE_HTTP_STATUS = {408, 429, 500, 502, 503, 504}
API_PRICE: dict[str, float] = {
"paper_search": 0, "paper_info": 0, "person_search": 0,
"org_search": 0, "venue_search": 0, "patent_search": 0, "patent_info": 0,
"paper_search_pro": 0.01, "paper_detail": 0.01, "patent_detail": 0.01,
"org_detail": 0.01, "org_disambiguate": 0.01,
"paper_qa_search": 0.05, "org_disambiguate_pro": 0.05,
"paper_relation": 0.10, "org_paper_relation": 0.10,
"org_patent_relation": 0.10, "venue_paper_relation": 0.10,
"paper_list_by_keywords": 0.10,
"venue_detail": 0.20, "paper_detail_by_condition": 0.20,
"person_figure": 0.50, "org_person_relation": 0.50,
"person_detail": 1.00,
"person_paper_relation": 1.50, "person_patent_relation": 1.50,
"person_project": 1.50,
}
_cost_log: list[tuple[str, float]] = []
_cost_lock = threading.Lock()
def _track_cost(api_name: str) -> None:
price = API_PRICE.get(api_name, 0)
with _cost_lock:
_cost_log.append((api_name, price))
def get_cost_summary() -> dict:
with _cost_lock:
total = sum(p for _, p in _cost_log)
breakdown = {}
for name, price in _cost_log:
breakdown[name] = breakdown.get(name, 0) + price
return {"total": round(total, 2), "breakdown": breakdown, "calls": len(_cost_log)}
def reset_cost() -> None:
with _cost_lock:
_cost_log.clear()
# ──────────────────────────────────────────────────────────────────────────────
# Core HTTP Utilities
# ──────────────────────────────────────────────────────────────────────────────
def _request(token: str, method: str, path: str,
params: Optional[dict] = None,
body: Optional[dict] = None) -> Any:
"""Send an HTTP request and return the parsed JSON data (with retries)."""
url = BASE_URL + path
headers = {
"Authorization": token,
"X-Platform": "openclaw",
"Content-Type": "application/json;charset=utf-8",
}
if method.upper() == "GET" and params:
query = urllib.parse.urlencode(
{k: (json.dumps(v) if isinstance(v, (list, dict)) else v)
for k, v in params.items() if v is not None}
)
url = f"{url}?{query}"
data = json.dumps(body).encode("utf-8") if body else None
req = urllib.request.Request(url, data=data, headers=headers, method=method.upper())
last_error_result = None
for attempt in range(1, MAX_RETRIES + 1):
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT_SECONDS) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)
except urllib.error.HTTPError as e:
body_bytes = e.read()
try:
err = json.loads(body_bytes)
except Exception:
err = body_bytes.decode("utf-8", errors="replace")
retryable = e.code in RETRYABLE_HTTP_STATUS
print(f"[HTTP {e.code}] {e.reason}: {err}", file=sys.stderr)
last_error_result = {
"code": e.code, "success": False,
"msg": str(e.reason), "error": err, "retryable": retryable,
}
if not retryable:
return last_error_result
except urllib.error.URLError as e:
reason = str(getattr(e, "reason", e))
print(f"[Request failed] {reason}", file=sys.stderr)
last_error_result = {
"code": -1, "success": False,
"msg": "network_error", "error": reason, "retryable": True,
}
except TimeoutError as e:
print(f"[Request timeout] {e}", file=sys.stderr)
last_error_result = {
"code": -1, "success": False,
"msg": "timeout", "error": str(e), "retryable": True,
}
except Exception as e:
print(f"[Request failed] {e}", file=sys.stderr)
return {
"code": -1, "success": False,
"msg": "unknown_error", "error": str(e), "retryable": False,
}
if attempt < MAX_RETRIES:
backoff = (2 ** (attempt - 1)) + random.uniform(0, 0.3)
print(f"[Retry] attempt={attempt}/{MAX_RETRIES} wait={backoff:.2f}s", file=sys.stderr)
time.sleep(backoff)
return last_error_result or {
"code": -1, "success": False,
"msg": "request_failed", "error": "max retries exceeded", "retryable": True,
}
def _print(data: Any) -> None:
"""Pretty-print JSON result."""
print(json.dumps(data, ensure_ascii=False, indent=2))
# ──────────────────────────────────────────────────────────────────────────────
# Paper APIs
# ──────────────────────────────────────────────────────────────────────────────
def paper_search(token: str, title: str, page: int = 1, size: int = 10) -> Any:
"""Paper Search (Free): search by title; returns ID/title/DOI."""
_track_cost("paper_search")
return _request(token, "GET", "/api/paper/search",
params={"title": title, "page": page, "size": size})
def paper_search_pro(token: str, title: str = None, keyword: str = None,
abstract: str = None, author: str = None,
org: str = None, venue: str = None,
order: str = None, page: int = 0, size: int = 10) -> Any:
"""Paper Search Pro (¥0.01/call): multi-condition search."""
_track_cost("paper_search_pro")
params = {"page": page, "size": size}
for k, v in [("title", title), ("keyword", keyword), ("abstract", abstract),
("author", author), ("org", org), ("venue", venue), ("order", order)]:
if v is not None:
params[k] = v
return _request(token, "GET", "/api/paper/search/pro", params=params)
def paper_qa_search(token: str, query: str = None,
use_topic: bool = False,
topic_high: str = None, topic_middle: str = None, topic_low: str = None,
title: list = None, doi: str = None, year: list = None,
sci_flag: bool = False, n_citation_flag: bool = False,
force_citation_sort: bool = False, force_year_sort: bool = False,
author_terms: list = None, org_terms: list = None,
author_id: list = None, org_id: list = None, venue_ids: list = None,
size: int = 10, offset: int = 0) -> Any:
"""Paper QA Search (¥0.05/call): AI-powered Q&A; supports natural language and structured keywords."""
_track_cost("paper_qa_search")
body: dict = {"use_topic": use_topic, "size": size, "offset": offset}
optional = {
"query": query, "topic_high": topic_high, "topic_middle": topic_middle,
"topic_low": topic_low, "title": title, "doi": doi, "year": year,
"author_terms": author_terms, "org_terms": org_terms,
"author_id": author_id, "org_id": org_id, "venue_ids": venue_ids,
}
body.update({k: v for k, v in optional.items() if v})
if sci_flag:
body["sci_flag"] = True
if n_citation_flag:
body["n_citation_flag"] = True
if force_citation_sort:
body["force_citation_sort"] = True
if force_year_sort:
body["force_year_sort"] = True
return _request(token, "POST", "/api/paper/qa/search", body=body)
def paper_info(token: str, ids: list) -> Any:
"""Paper Info (Free): batch-retrieve basic information by ID."""
_track_cost("paper_info")
return _request(token, "POST", "/api/paper/info", body={"ids": ids})
def paper_detail(token: str, paper_id: str) -> Any:
"""Paper Details (¥0.01/call): retrieve complete paper information."""
_track_cost("paper_detail")
return _request(token, "GET", "/api/paper/detail", params={"id": paper_id})
def paper_relation(token: str, paper_id: str) -> Any:
"""Paper Citations (¥0.10/call): retrieve papers cited by this paper."""
_track_cost("paper_relation")
return _request(token, "GET", "/api/paper/relation", params={"id": paper_id})
def paper_list_by_keywords(token: str, keywords: list, page: int = 0, size: int = 10) -> Any:
"""Paper Batch Query (¥0.10/call): retrieve paper abstracts and info via multiple keywords."""
_track_cost("paper_list_by_keywords")
params = {"page": page, "size": size, "keywords": json.dumps(keywords, ensure_ascii=False)}
return _request(token, "GET", "/api/paper/list/citation/by/keywords", params=params)
def paper_detail_by_condition(token: str, year: int, venue_id: str = None) -> Any:
"""Paper Details by Year and Venue (¥0.20/call): year and venue_id must both be provided; providing only year returns null."""
_track_cost("paper_detail_by_condition")
params: dict = {"year": year}
if venue_id:
params["venue_id"] = venue_id
return _request(token, "GET",
"/api/paper/platform/allpubs/more/detail/by/ts/org/venue",
params=params)
# ──────────────────────────────────────────────────────────────────────────────
# Scholar APIs
# ──────────────────────────────────────────────────────────────────────────────
def person_search(token: str, name: str = None, org: str = None,
org_id: list = None, offset: int = 0, size: int = 5) -> Any:
"""Scholar Search (Free): search for scholars by name/institution."""
_track_cost("person_search")
body: dict = {"offset": offset, "size": size}
if name:
body["name"] = name
if org:
body["org"] = org
if org_id:
body["org_id"] = org_id
return _request(token, "POST", "/api/person/search", body=body)
def person_detail(token: str, person_id: str) -> Any:
"""Scholar Details (¥1.00/call): retrieve complete personal information."""
_track_cost("person_detail")
return _request(token, "GET", "/api/person/detail", params={"id": person_id})
def person_figure(token: str, person_id: str) -> Any:
"""Scholar Portrait (¥0.50/call): retrieve research interests, domains, and structured history."""
_track_cost("person_figure")
return _request(token, "GET", "/api/person/figure", params={"id": person_id})
def person_paper_relation(token: str, person_id: str) -> Any:
"""Scholar Papers (¥1.50/call): retrieve list of papers published by a scholar."""
_track_cost("person_paper_relation")
return _request(token, "GET", "/api/person/paper/relation", params={"id": person_id})
def person_patent_relation(token: str, person_id: str) -> Any:
"""Scholar Patents (¥1.50/call): retrieve a scholar's patent list."""
_track_cost("person_patent_relation")
return _request(token, "GET", "/api/person/patent/relation", params={"id": person_id})
def person_project(token: str, person_id: str) -> Any:
"""Scholar Projects (¥1.50/call): retrieve research projects (funding amount/dates/source)."""
_track_cost("person_project")
return _request(token, "GET", "/api/project/person/v3/open", params={"id": person_id})
# ──────────────────────────────────────────────────────────────────────────────
# Institution APIs
# ──────────────────────────────────────────────────────────────────────────────
def org_search(token: str, orgs: list) -> Any:
"""Org Search (Free): search for institutions by name keyword."""
_track_cost("org_search")
return _request(token, "POST", "/api/organization/search", body={"orgs": orgs})
def org_detail(token: str, ids: list) -> Any:
"""Org Details (¥0.01/call): retrieve institution details by ID."""
_track_cost("org_detail")
return _request(token, "POST", "/api/organization/detail", body={"ids": ids})
def org_person_relation(token: str, org_id: str, offset: int = 0) -> Any:
"""Org Scholars (¥0.50/call): retrieve affiliated scholars (10 per call)."""
_track_cost("org_person_relation")
return _request(token, "GET", "/api/organization/person/relation",
params={"org_id": org_id, "offset": offset})
def org_paper_relation(token: str, org_id: str, offset: int = 0) -> Any:
"""Org Papers (¥0.10/call): retrieve papers published by institution scholars (10 per call)."""
_track_cost("org_paper_relation")
return _request(token, "GET", "/api/organization/paper/relation",
params={"org_id": org_id, "offset": offset})
def org_patent_relation(token: str, org_id: str,
page: int = 1, page_size: int = 100) -> Any:
"""Org Patents (¥0.10/call): retrieve institution patent list with pagination (max page_size 10,000)."""
_track_cost("org_patent_relation")
return _request(token, "GET", "/api/organization/patent/relation",
params={"id": org_id, "page": page, "page_size": page_size})
def org_disambiguate(token: str, org: str) -> Any:
"""Org Disambiguation (¥0.01/call): retrieve the normalized institution name."""
_track_cost("org_disambiguate")
return _request(token, "POST", "/api/organization/na", body={"org": org})
def org_disambiguate_pro(token: str, org: str) -> Any:
"""Org Disambiguation Pro (¥0.05/call): extract primary and secondary institution IDs."""
_track_cost("org_disambiguate_pro")
return _request(token, "POST", "/api/organization/na/pro", body={"org": org})
# ──────────────────────────────────────────────────────────────────────────────
# Journal APIs
# ──────────────────────────────────────────────────────────────────────────────
def venue_search(token: str, name: str) -> Any:
"""Venue Search (Free): search for journal ID and standard name by name."""
_track_cost("venue_search")
return _request(token, "POST", "/api/venue/search", body={"name": name})
def venue_detail(token: str, venue_id: str) -> Any:
"""Venue Details (¥0.20/call): retrieve ISSN, abbreviation, type, etc."""
_track_cost("venue_detail")
return _request(token, "POST", "/api/venue/detail", body={"id": venue_id})
def venue_paper_relation(token: str, venue_id: str, offset: int = 0,
limit: int = 20, year: Optional[int] = None) -> Any:
"""Venue Papers (¥0.10/call): retrieve journal paper list (supports year filtering)."""
_track_cost("venue_paper_relation")
body: dict = {"id": venue_id, "offset": offset, "limit": limit}
if year is not None:
body["year"] = year
return _request(token, "POST", "/api/venue/paper/relation", body=body)
# ──────────────────────────────────────────────────────────────────────────────
# Patent APIs
# ──────────────────────────────────────────────────────────────────────────────
def patent_search(token: str, query: str, page: int = 0, size: int = 10) -> Any:
"""Patent Search (Free): search patents by name/keyword."""
_track_cost("patent_search")
return _request(token, "POST", "/api/patent/search",
body={"query": query, "page": page, "size": size})
def patent_info(token: str, patent_id: str) -> Any:
"""Patent Info (Free): retrieve basic patent information (title/patent number/inventor)."""
_track_cost("patent_info")
return _request(token, "GET", "/api/patent/info", params={"id": patent_id})
def patent_detail(token: str, patent_id: str) -> Any:
"""Patent Details (¥0.01/call): retrieve complete patent information (abstract/filing date/IPC, etc.)."""
_track_cost("patent_detail")
return _request(token, "GET", "/api/patent/detail", params={"id": patent_id})
# ──────────────────────────────────────────────────────────────────────────────
# Combined Workflows
# ──────────────────────────────────────────────────────────────────────────────
def workflow_scholar_profile(token: str, name: str) -> dict:
"""
Workflow 1: Scholar Profile
Search scholar → details + portrait + papers + patents + projects
"""
print(f"[1/6] Searching scholar: {name}", file=sys.stderr)
search_result = person_search(token, name=name, size=5)
if not search_result or not search_result.get("data"):
return {"error": f"Scholar not found: {name}"}
candidates = search_result["data"]
scholar = candidates[0]
person_id = scholar.get("id") or scholar.get("_id")
print(f" Found: {scholar.get('name')} ({scholar.get('org')}), ID={person_id}", file=sys.stderr)
result = {
"source_api_chain": [
"person_search",
"person_detail",
"person_figure",
"person_paper_relation",
"person_patent_relation",
"person_project",
],
"search_candidates": candidates[:3],
"selected": {
"id": person_id,
"name": scholar.get("name"),
"name_zh": scholar.get("name_zh"),
"org": scholar.get("org"),
"interests": scholar.get("interests"),
"n_citation": scholar.get("n_citation"),
}
}
print("[2/6] Fetching scholar details (parallel)...", file=sys.stderr)
tasks = {
"detail": lambda: person_detail(token, person_id),
"figure": lambda: person_figure(token, person_id),
"papers": lambda: person_paper_relation(token, person_id),
"patents": lambda: person_patent_relation(token, person_id),
"projects": lambda: person_project(token, person_id),
}
with ThreadPoolExecutor(max_workers=5) as pool:
futures = {pool.submit(fn): key for key, fn in tasks.items()}
for future in as_completed(futures):
key = futures[future]
try:
resp = future.result()
except Exception as e:
print(f" [{key}] failed: {e}", file=sys.stderr)
continue
data = resp.get("data") if resp else None
if not data:
continue
if key == "detail":
result["detail"] = data
elif key == "figure":
result["figure"] = data
elif key == "papers":
result["papers"] = data[:20]
result["papers_total"] = resp.get("total", len(data))
elif key == "patents":
result["patents"] = data[:10]
elif key == "projects":
result["projects"] = data[:10]
return result
def workflow_paper_deep_dive(token: str, title: str = None, keyword: str = None,
author: str = None, order: str = "n_citation") -> dict:
"""
Workflow 2: Paper Deep Dive
Search paper → details + citation chain + basic info of cited papers
"""
print(f"[1/4] Searching paper: title={title}, keyword={keyword}", file=sys.stderr)
if keyword or author:
search_result = paper_search_pro(token, title=title, keyword=keyword,
author=author, order=order, size=5)
search_api = "paper_search_pro"
else:
search_result = paper_search(token, title=title or keyword, size=5)
search_api = "paper_search"
if not search_result or not search_result.get("data"):
# Fall back to pro search when title search yields no results to improve recall
print(" Title search returned no results; falling back to paper_search_pro...", file=sys.stderr)
search_result = paper_search_pro(token, title=title, keyword=title,
author=author, order=order, size=5)
search_api = "paper_search_pro(fallback)"
if not search_result or not search_result.get("data"):
return {"error": "No relevant papers found"}
papers = search_result["data"]
top_paper = papers[0]
paper_id = top_paper.get("id") or top_paper.get("_id")
print(f" Found: {top_paper.get('title')[:60]}, ID={paper_id}", file=sys.stderr)
result = {
"source_api_chain": [
search_api,
"paper_detail",
"paper_relation",
"paper_info",
],
"search_candidates": papers[:5],
"selected_id": paper_id,
"selected_title": top_paper.get("title"),
}
print("[2/4] Fetching paper details...", file=sys.stderr)
detail = paper_detail(token, paper_id)
if detail and detail.get("data"):
result["detail"] = detail["data"]
print("[3/4] Fetching citation relationships...", file=sys.stderr)
relation = paper_relation(token, paper_id)
if relation and relation.get("data"):
# data structure: [{"_id": "<paper_id>", "cited": [{...}, ...]}]
# the outer array wraps each paper; the actual citation list is in the cited field
all_cited = []
for item in relation["data"]:
all_cited.extend(item.get("cited") or [])
result["citations_count"] = len(all_cited)
result["citations_preview"] = all_cited[:10]
cited_ids = [c.get("_id") or c.get("id") for c in all_cited[:20]
if c.get("_id") or c.get("id")]
if cited_ids:
print(f"[4/4] Batch-fetching basic info for {len(cited_ids)} cited papers...", file=sys.stderr)
info = paper_info(token, cited_ids)
if info and info.get("data"):
result["cited_papers_info"] = info["data"]
else:
print("[4/4] Skipping (no cited IDs)", file=sys.stderr)
else:
print("[4/4] Skipping (no citation data)", file=sys.stderr)
return result
def workflow_org_analysis(token: str, org: str) -> dict:
"""
Workflow 3: Org Analysis
Org disambiguation pro → details + scholars + papers + patents
"""
print(f"[1/5] Disambiguating org: {org}", file=sys.stderr)
disamb = org_disambiguate_pro(token, org)
org_id = None
if disamb and disamb.get("data"):
data = disamb["data"]
if isinstance(data, list) and data:
first = data[0]
org_id = first.get("一级ID") or first.get("二级ID")
elif isinstance(data, dict):
org_id = data.get("一级ID") or data.get("二级ID")
if not org_id:
print(" Disambiguation pro returned no ID; trying org search...", file=sys.stderr)
search_r = org_search(token, [org])
if search_r and search_r.get("data"):
orgs = search_r["data"]
org_id = orgs[0].get("org_id") if orgs else None
if not org_id:
return {"error": f"Could not find org ID: {org}"}
print(f" Org ID: {org_id}", file=sys.stderr)
result = {
"source_api_chain": [
"org_disambiguate_pro",
"org_detail",
"org_person_relation",
"org_paper_relation",
"org_patent_relation",
],
"org_query": org,
"org_id": org_id,
"disambiguate": disamb,
}
print("[2/5] Fetching org data (parallel)...", file=sys.stderr)
tasks = {
"detail": lambda: org_detail(token, [org_id]),
"scholars": lambda: org_person_relation(token, org_id, offset=0),
"papers": lambda: org_paper_relation(token, org_id, offset=0),
"patents": lambda: org_patent_relation(token, org_id, page=1, page_size=100),
}
with ThreadPoolExecutor(max_workers=4) as pool:
futures = {pool.submit(fn): key for key, fn in tasks.items()}
for future in as_completed(futures):
key = futures[future]
try:
resp = future.result()
except Exception as e:
print(f" [{key}] failed: {e}", file=sys.stderr)
continue
data = resp.get("data") if resp else None
if not data:
continue
if key == "detail":
result["detail"] = data
elif key == "scholars":
result["scholars"] = data
result["scholars_total"] = resp.get("total", len(data))
elif key == "papers":
result["papers"] = data
result["papers_total"] = resp.get("total", len(data))
elif key == "patents":
result["patents"] = data
result["patents_total"] = resp.get("total", len(data))
return result
def workflow_venue_papers(token: str, venue: str, year: Optional[int] = None,
limit: int = 20) -> dict:
"""
Workflow 4: Venue Papers
Venue search → venue details + papers by year
"""
print(f"[1/3] Searching venue: {venue}", file=sys.stderr)
search_result = venue_search(token, venue)
if not search_result or not search_result.get("data"):
return {"error": f"Venue not found: {venue}"}
venues = search_result["data"]
top_venue = venues[0]
venue_id = top_venue.get("id")
print(f" Found: {top_venue.get('name_en')}, ID={venue_id}", file=sys.stderr)
result = {
"source_api_chain": [
"venue_search",
"venue_detail",
"venue_paper_relation",
],
"search_candidates": venues[:3],
"venue_id": venue_id,
}
print("[2/3] Fetching venue details...", file=sys.stderr)
detail = venue_detail(token, venue_id)
if detail and detail.get("data"):
result["venue_detail"] = detail["data"]
print(f"[3/3] Fetching venue papers (year={year}, limit={limit})...", file=sys.stderr)
papers = venue_paper_relation(token, venue_id, year=year, limit=limit)
if papers and papers.get("data"):
result["papers"] = papers["data"]
result["papers_total"] = papers.get("total", len(papers["data"]))
return result
def workflow_paper_qa(token: str, query: str = None,
topic_high: str = None, topic_middle: str = None,
sci_flag: bool = False, sort_citation: bool = False, sort_year: bool = False,
author_id: list = None, org_id: list = None, venue_ids: list = None,
size: int = 10) -> dict:
"""
Workflow 5: Paper QA Search
Use AI-powered paper Q&A search API
"""
use_topic = topic_high is not None
print(f"[1/1] Academic Q&A search: query={query}, use_topic={use_topic}", file=sys.stderr)
qa_result = paper_qa_search(
token, query=query, use_topic=use_topic,
topic_high=topic_high, topic_middle=topic_middle,
sci_flag=sci_flag, force_citation_sort=sort_citation,
force_year_sort=sort_year,
author_id=author_id, org_id=org_id, venue_ids=venue_ids,
size=size
)
if qa_result and qa_result.get("code") == 200 and qa_result.get("data"):
qa_result["source_api_chain"] = ["paper_qa_search"]
qa_result["route"] = "paper_qa_search"
return qa_result
# Fall back to pro search when query mode yields no results
if query:
print(" paper_qa_search returned no results; falling back to paper_search_pro...", file=sys.stderr)
fallback = paper_search_pro(token, keyword=query, order="n_citation", size=size)
data = (fallback or {}).get("data") or []
return {
"code": 200 if data else (qa_result or {}).get("code", -1),
"success": bool(data),
"msg": "" if data else "no data",
"data": data,
"total": (fallback or {}).get("total", len(data)),
"route": "paper_qa_search -> paper_search_pro",
"source_api_chain": ["paper_qa_search", "paper_search_pro"],
"primary_result": qa_result,
}
if isinstance(qa_result, dict):
qa_result["source_api_chain"] = ["paper_qa_search"]
qa_result["route"] = "paper_qa_search"
return qa_result
def workflow_patent_search(token: str, query: str, page: int = 0, size: int = 10) -> dict:
"""
Workflow 6: Patent Search and Details
Patent search → retrieve details for each patent
"""
print(f"[1/2] Searching patents: {query}", file=sys.stderr)
search_result = patent_search(token, query, page=page, size=size)
if not search_result or not search_result.get("data"):
return {"error": f"No patents found: {query}"}
patents = search_result["data"]
result = {
"source_api_chain": ["patent_search", "patent_detail"],
"search_results": patents,
"total": len(patents),
}
top_patents = [p for p in patents[:3] if p.get("id")]
print(f"[2/2] Fetching details for {len(top_patents)} patents (parallel)...", file=sys.stderr)
details = []
with ThreadPoolExecutor(max_workers=3) as pool:
futs = {pool.submit(patent_detail, token, p["id"]): p["id"] for p in top_patents}
for fut in as_completed(futs):
try:
d = fut.result()
if d and d.get("data"):
details.append(d["data"])
except Exception as e:
print(f" [patent_detail] failed: {e}", file=sys.stderr)
result["details"] = details
return result
def workflow_scholar_patents(token: str, name: str) -> dict:
"""
Retrieve patent list + individual patent details for a scholar by name
"""
print(f"[1/3] Searching scholar: {name}", file=sys.stderr)
search_result = person_search(token, name=name, size=3)
if not search_result or not search_result.get("data"):
return {"error": f"Scholar not found: {name}"}
scholar = search_result["data"][0]
person_id = scholar.get("id")
print(f" Found: {scholar.get('name')}, ID={person_id}", file=sys.stderr)
result = {"scholar": scholar}
print("[2/3] Fetching scholar patent list...", file=sys.stderr)
patents = person_patent_relation(token, person_id)
if not patents or not patents.get("data"):
return {**result, "patents": [], "error": "No patent data for this scholar"}
patent_list = patents["data"]
result["patents_list"] = patent_list
top_patents = [p for p in patent_list[:3] if p.get("patent_id")]
print(f"[3/3] Fetching details for {len(top_patents)} patents (parallel)...", file=sys.stderr)
details = []
with ThreadPoolExecutor(max_workers=3) as pool:
futs = {pool.submit(patent_detail, token, p["patent_id"]): p["patent_id"]
for p in top_patents}
for fut in as_completed(futs):
try:
d = fut.result()
if d and d.get("data"):
details.append(d["data"])
except Exception as e:
print(f" [patent_detail] failed: {e}", file=sys.stderr)
result["patent_details"] = details
return result
# ──────────────────────────────────────────────────────────────────────────────
# Command-Line Entry Point
# ──────────────────────────────────────────────────────────────────────────────
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
description="AMiner Open Platform Academic Data Query Client",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Scholar profile analysis
python aminer_client.py --token <TOKEN> --action scholar_profile --name "Andrew Ng"
# Paper deep dive
python aminer_client.py --token <TOKEN> --action paper_deep_dive --title "BERT"
python aminer_client.py --token <TOKEN> --action paper_deep_dive --keyword "large language model" --author "Hinton"
# Institution research capability analysis
python aminer_client.py --token <TOKEN> --action org_analysis --org "Tsinghua University"
# Journal paper monitoring
python aminer_client.py --token <TOKEN> --action venue_papers --venue "NeurIPS" --year 2023
# Academic Q&A
python aminer_client.py --token <TOKEN> --action paper_qa --query "deep learning for protein structure"
python aminer_client.py --token <TOKEN> --action paper_qa \\
--topic_high '[["transformer","self-attention"],["protein folding"]]' \\
--sci_flag --sort_citation
# Patent search
python aminer_client.py --token <TOKEN> --action patent_search --query "quantum computing chip"
# Scholar patents
python aminer_client.py --token <TOKEN> --action scholar_patents --name "Shou-Cheng Zhang"
# Direct single API call
python aminer_client.py --token <TOKEN> --action raw \\
--api paper_search --params '{"title":"BERT","page":0,"size":5}'
Console (Generate Token): https://open.aminer.cn/open/board?tab=control
Docs: https://open.aminer.cn/open/docs
"""
)
p.add_argument(
"--token",
default=None,
help=(
"AMiner API Token. If not provided, reads from the environment variable AMINER_API_KEY by default; "
"or go to https://open.aminer.cn/open/board?tab=control to generate one."
),
)
p.add_argument("--action", required=True,
choices=["scholar_profile", "paper_deep_dive", "org_analysis",
"venue_papers", "paper_qa", "patent_search",
"scholar_patents", "raw"],
help="Action to perform")
# General parameters
p.add_argument("--name", help="Scholar name")
p.add_argument("--title", help="Paper title")
p.add_argument("--keyword", help="Keyword")
p.add_argument("--author", help="Author name")
p.add_argument("--org", help="Institution name")
p.add_argument("--venue", help="Journal name")
p.add_argument("--query", help="Query string (natural language Q&A or patent search)")
p.add_argument("--year", type=int, help="Year filter")
p.add_argument("--size", type=int, default=10, help="Number of results to return")
p.add_argument("--page", type=int, default=0, help="Page number")
p.add_argument("--page_size", type=int, default=100,
help="Org patent pagination size (max 10,000)")
p.add_argument("--order", default="n_citation",
choices=["n_citation", "year"], help="Sort order")
# Paper QA specific
p.add_argument("--topic_high", help="Required keyword array (JSON string; outer AND, inner OR)")
p.add_argument("--topic_middle", help="Strongly boosted keywords (same format as topic_high)")
p.add_argument("--sci_flag", action="store_true", help="Return SCI papers only")
p.add_argument("--sort_citation", action="store_true", help="Sort by citation count")
p.add_argument("--sort_year", action="store_true", help="Sort by year (most recent first)")
p.add_argument("--author_id", help="Author ID filter; accepts single ID or JSON array string")
p.add_argument("--org_id", help="Institution ID filter; accepts single ID or JSON array string")
p.add_argument("--venue_ids", help="Conference/journal ID filter; accepts JSON array string")
# Raw mode
p.add_argument("--api", help="[raw mode] API function name, e.g. paper_search")
p.add_argument("--params", help="[raw mode] Parameter dictionary in JSON format")
# Dry run
p.add_argument("--dry-run", action="store_true", dest="dry_run",
help="Preview the API call chain and estimated cost without sending requests")
return p
WORKFLOW_DRY_RUN_INFO = {
"scholar_profile": [
("person_search", 0), ("person_detail", 1.00), ("person_figure", 0.50),
("person_paper_relation", 1.50), ("person_patent_relation", 1.50), ("person_project", 1.50),
],
"paper_deep_dive": [
("paper_search", 0), ("paper_detail", 0.01), ("paper_relation", 0.10), ("paper_info", 0),
],
"org_analysis": [
("org_disambiguate_pro", 0.05), ("org_detail", 0.01),
("org_person_relation", 0.50), ("org_paper_relation", 0.10), ("org_patent_relation", 0.10),
],
"venue_papers": [
("venue_search", 0), ("venue_detail", 0.20), ("venue_paper_relation", 0.10),
],
"paper_qa": [("paper_qa_search", 0.05)],
"patent_search": [("patent_search", 0), ("patent_detail", 0.01)],
"scholar_patents": [
("person_search", 0), ("person_patent_relation", 1.50), ("patent_detail", 0.01),
],
}
def main():
parser = build_parser()
args = parser.parse_args()
token = (args.token or os.getenv("AMINER_API_KEY") or "").strip()
if args.dry_run:
info = WORKFLOW_DRY_RUN_INFO.get(args.action, [])
if not info:
print(f"[Dry Run] No preview available for action '{args.action}'.")
else:
total = sum(p for _, p in info)
print(f"[Dry Run] Action: {args.action}")
for i, (api, price) in enumerate(info, 1):
label = "Free" if price == 0 else f"¥{price:.2f}"
print(f" {i}. {api} ({label})")
print(f" Estimated total: ¥{total:.2f}")
return
if not token or not token.strip():
parser.error(
"Missing --token; cannot call AMiner API. Please go to "
"https://open.aminer.cn/open/board?tab=control to generate a token first."
)
def _parse_id_filter(value: Optional[str]) -> Optional[list]:
if not value:
return None
# Accepts a single ID string or a JSON array string
try:
parsed = json.loads(value)
if isinstance(parsed, list):
return parsed
if isinstance(parsed, str) and parsed.strip():
return [parsed.strip()]
except Exception:
pass
return [value.strip()] if value.strip() else None
if args.action == "scholar_profile":
if not args.name:
parser.error("--action scholar_profile requires --name")
result = workflow_scholar_profile(token, args.name)
elif args.action == "paper_deep_dive":
if not args.title and not args.keyword:
parser.error("--action paper_deep_dive requires --title or --keyword")
result = workflow_paper_deep_dive(
token, title=args.title, keyword=args.keyword,
author=args.author, order=args.order
)
elif args.action == "org_analysis":
if not args.org:
parser.error("--action org_analysis requires --org")
result = workflow_org_analysis(token, args.org)
elif args.action == "venue_papers":
if not args.venue:
parser.error("--action venue_papers requires --venue")
result = workflow_venue_papers(token, args.venue, year=args.year, limit=args.size)
elif args.action == "paper_qa":
if not args.query and not args.topic_high:
parser.error("--action paper_qa requires --query or --topic_high")
if args.sort_citation and args.sort_year:
parser.error("--sort_citation and --sort_year cannot both be enabled")
author_id_filter = _parse_id_filter(args.author_id)
org_id_filter = _parse_id_filter(args.org_id)
venue_ids_filter = _parse_id_filter(args.venue_ids)
result = workflow_paper_qa(
token, query=args.query,
topic_high=args.topic_high, topic_middle=args.topic_middle,
sci_flag=args.sci_flag, sort_citation=args.sort_citation, sort_year=args.sort_year,
author_id=author_id_filter, org_id=org_id_filter, venue_ids=venue_ids_filter,
size=args.size
)
elif args.action == "patent_search":
if not args.query:
parser.error("--action patent_search requires --query")
result = workflow_patent_search(token, args.query, page=args.page, size=args.size)
elif args.action == "scholar_patents":
if not args.name:
parser.error("--action scholar_patents requires --name")
result = workflow_scholar_patents(token, args.name)
elif args.action == "raw":
if not args.api:
parser.error("--action raw requires --api (API function name)")
RAW_API_ALLOWLIST = {
"paper_search": paper_search,
"paper_search_pro": paper_search_pro,
"paper_qa_search": paper_qa_search,
"paper_info": paper_info,
"paper_detail": paper_detail,
"paper_relation": paper_relation,
"paper_list_by_keywords": paper_list_by_keywords,
"paper_detail_by_condition": paper_detail_by_condition,
"person_search": person_search,
"person_detail": person_detail,
"person_figure": person_figure,
"person_paper_relation": person_paper_relation,
"person_patent_relation": person_patent_relation,
"person_project": person_project,
"org_search": org_search,
"org_detail": org_detail,
"org_person_relation": org_person_relation,
"org_paper_relation": org_paper_relation,
"org_patent_relation": org_patent_relation,
"org_disambiguate": org_disambiguate,
"org_disambiguate_pro": org_disambiguate_pro,
"venue_search": venue_search,
"venue_detail": venue_detail,
"venue_paper_relation": venue_paper_relation,
"patent_search": patent_search,
"patent_info": patent_info,
"patent_detail": patent_detail,
}
fn = RAW_API_ALLOWLIST.get(args.api)
if fn is None:
allowed = ", ".join(sorted(RAW_API_ALLOWLIST.keys()))
parser.error(f"API function not found: {args.api}. Available APIs: {allowed}")
kwargs = json.loads(args.params) if args.params else {}
result = fn(token, **kwargs)
else:
parser.print_help()
sys.exit(1)
_print(result)
cost = get_cost_summary()
if cost["calls"] > 0:
parts = [f"{k}: ¥{v:.2f}" if v > 0 else f"{k}: Free"
for k, v in sorted(cost["breakdown"].items())]
print(f"\n[Cost] ¥{cost['total']:.2f} total, {cost['calls']} API calls "
f"({', '.join(parts)})", file=sys.stderr)
if __name__ == "__main__":
main()