
Korean Patent Search
- 3.3k installs
- 6.5k repo stars
- Updated July 27, 2026
- nomadamas/k-skill
korean-patent-search is an agent skill that queries Korean patents through KIPRIS Plus Open API keyword search and application-number bibliography lookup with JSON output.
About
korean-patent-search queries Korean patent and utility-model publications through the official KIPRIS Plus Open API, shipping a python3 helper at scripts/patent_search.py. Version one supports keyword search via getWordSearch and bibliography detail lookup via getBibliographyDetailInfoSearch, returning structured JSON parsed from XML header, body, and items fields. Prerequisites are internet access, python3, and a KIPRIS Plus ServiceKey supplied through KIPRIS_PLUS_API_KEY or --service-key, with percent-encoded portal keys normalized by the helper. Keyword mode accepts --query with optional --year, --page-no, --num-rows, and patent or utility exclusion flags. Detail mode requires --application-number to fetch title, applicant, abstract, and publication or registration metadata. Response policy uses only official API responses, surfaces API error codes plainly, and includes application number and invention title in every result. CLI examples search terms like battery patents or fetch application 1020240001234 details.
- Calls KIPRIS Plus getWordSearch and getBibliographyDetailInfoSearch endpoints via patent_search.py.
- Requires KIPRIS_PLUS_API_KEY or --service-key with percent-encoded key normalization support.
- Keyword search supports year, pagination, row limits, and patent versus utility exclusion flags.
- Parses XML responses into JSON with application number, title, applicant, abstract, and dates.
- Surfaces API error codes directly and guides users when ServiceKey is missing.
Korean Patent Search by the numbers
- 3,321 all-time installs (skills.sh)
- +135 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #138 of 2,742 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
korean-patent-search capabilities & compatibility
- Capabilities
- kipris plus keyword patent search · application number bibliography detail lookup · xml to json response normalization · servicekey validation and error surfacing
- Use cases
- research · web search
What korean-patent-search says it does
키워드 검색 (`getWordSearch`)
python3 scripts/patent_search.py --query "배터리"
npx skills add https://github.com/nomadamas/k-skill --skill korean-patent-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.3k |
|---|---|
| repo stars | ★ 6.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | nomadamas/k-skill ↗ |
How do I search Korean patent publications by keyword or application number and get structured JSON from KIPRIS Plus?
Search Korean patent and utility-model publications via KIPRIS Plus Open API keyword and application-number detail lookup with JSON output.
Who is it for?
Developers researching Korean patents who have a KIPRIS Plus ServiceKey and need CLI-driven JSON results.
Skip if: Skip for non-Korean jurisdictions, offline patent archives, or workflows without a valid KIPRIS Plus API key.
When should I use this skill?
User asks for Korean patent keyword search, KIPRIS API JSON output, or bibliography detail by application number.
What you get
JSON results with application number, invention title, applicant, abstract, and publication metadata from official KIPRIS Plus API responses.
- Structured patent search JSON
- Bibliography detail JSON
By the numbers
- Default 10 rows per search page (DEFAULT_NUM_ROWS)
- 30-second HTTP request timeout (DEFAULT_TIMEOUT)
Files
한국 특허 정보 검색
What this skill does
KIPRIS Plus(키프리스 플러스) 공식 Open API로 한국 특허/실용신안 공개·공고 데이터를 검색한다.
v1 범위:
- 키워드 검색 (
getWordSearch) - 출원번호 기준 서지 상세 조회 (
getBibliographyDetailInfoSearch) - 구조화된 JSON 출력
- 표준
python3helper 동봉
When to use
- "배터리 관련 한국 특허 찾아줘"
- "출원번호 1020240001234 특허 요약 보여줘"
- "KIPRIS API로 특허 검색 결과를 JSON으로 받고 싶어"
- "출원인/IPC/초록까지 포함한 한국 특허 검색 결과가 필요해"
Prerequisites
- 인터넷 연결
python3- KIPRIS Plus에서 발급받은 API 키
- helper 환경변수:
KIPRIS_PLUS_API_KEY - 실제 요청 쿼리 파라미터명:
ServiceKey - 설치된 skill payload 안에
scripts/patent_search.pyhelper 포함
Inputs
- 키워드 검색
- 필수:
--query - 선택:
--year - 선택:
--page-no - 선택:
--num-rows - 선택:
--exclude-patent - 선택:
--exclude-utility - 상세 조회
- 필수:
--application-number
Workflow
1. KIPRIS_PLUS_API_KEY 또는 --service-key 로 ServiceKey를 확보한다. 공공데이터포털에서 복사한 percent-encoded 값도 helper가 한 번 정규화해서 그대로 받을 수 있다. 2. 키워드 검색이면 getWordSearch endpoint를 호출한다. 3. 출원번호 상세 조회면 getBibliographyDetailInfoSearch endpoint를 호출한다. 4. XML 응답의 header/body/items 구조를 파싱한다. 5. 출원번호, 발명의명칭, 출원인, 초록, 공개/공고/등록 메타데이터를 JSON으로 정리한다.
CLI examples
export KIPRIS_PLUS_API_KEY=your-service-key
python3 scripts/patent_search.py --query "배터리"
python3 scripts/patent_search.py --query "배터리" --year 2024 --num-rows 5
python3 scripts/patent_search.py --application-number 1020240001234Response policy
- 공식 KIPRIS Plus Open API 응답만 사용한다.
- 키가 없으면
KIPRIS_PLUS_API_KEY또는--service-key를 정확히 안내한다. - 검색 결과는 최소한 출원번호, 발명의명칭, 출원일자, 출원인, 초록을 포함해 정리한다.
- 상세 조회는
getBibliographyDetailInfoSearch기준으로 공개/공고/등록 메타데이터를 함께 정리한다. - API 에러 코드는 숨기지 말고 그대로 surfaced 한다.
Done when
- 유효한 ServiceKey로
getWordSearch또는getBibliographyDetailInfoSearch호출이 가능하다. - helper가 JSON을 출력한다.
- 에러 시
KIPRIS_PLUS_API_KEY/ServiceKey관련 안내가 분명하다. - 응답에 출원번호와 발명의명칭이 포함된다.
Notes
- KIPRIS Plus 포털:
https://plus.kipris.or.kr/portal/data/service/List.do?subTab=SC001&entYn=N&menuNo=200100 - 공공데이터포털 문서:
https://www.data.go.kr/data/15058788/openapi.do - v1 helper는
getWordSearch,getBibliographyDetailInfoSearch두 operation에 집중한다. - 공공데이터포털 안내 기준으로 개발계정은 자동승인, 운영계정은 별도 심의 대상이다.
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from dataclasses import asdict, dataclass
from html.parser import HTMLParser
from typing import Callable
SERVICE_KEY_ENV_VAR = "KIPRIS_PLUS_API_KEY"
DEFAULT_TIMEOUT = 30
DEFAULT_NUM_ROWS = 10
DEFAULT_PAGE_NO = 1
BASE_API_URL = "https://plus.kipris.or.kr/kipo-api/kipi/patUtiModInfoSearchSevice"
SEARCH_OPERATION = "getWordSearch"
DETAIL_OPERATION = "getBibliographyDetailInfoSearch"
DEFAULT_HEADERS = {
"Accept": "application/xml,text/xml;q=0.9,*/*;q=0.8",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
),
}
@dataclass(frozen=True)
class PatentSearchResult:
index_no: int | None
application_number: str
invention_title: str | None
register_status: str | None
application_date: str | None
open_number: str | None
open_date: str | None
publication_number: str | None
publication_date: str | None
register_number: str | None
register_date: str | None
ipc_number: str | None
abstract_text: str | None
applicant_name: str | None
drawing: str | None
big_drawing: str | None
@dataclass(frozen=True)
class PatentSearchResponse:
query: str
page_no: int
num_of_rows: int
total_count: int
items: list[PatentSearchResult]
@dataclass(frozen=True)
class PatentDetail:
application_number: str
invention_title: str | None
register_status: str | None
application_date: str | None
open_number: str | None
open_date: str | None
publication_number: str | None
publication_date: str | None
register_number: str | None
register_date: str | None
ipc_number: str | None
abstract_text: str | None
applicant_name: str | None
drawing: str | None
big_drawing: str | None
@dataclass
class XmlNode:
tag: str
children: list["XmlNode"]
text_chunks: list[str]
@property
def text(self) -> str:
return "".join(self.text_chunks)
class XmlNodeBuilder(HTMLParser):
def __init__(self) -> None:
super().__init__(convert_charrefs=True)
self.root: XmlNode | None = None
self.stack: list[XmlNode] = []
def handle_starttag(self, tag: str, attrs) -> None: # type: ignore[override]
node = XmlNode(tag=tag, children=[], text_chunks=[])
if self.stack:
self.stack[-1].children.append(node)
else:
self.root = node
self.stack.append(node)
def handle_endtag(self, tag: str) -> None: # type: ignore[override]
if self.stack:
self.stack.pop()
def handle_data(self, data: str) -> None: # type: ignore[override]
if self.stack:
self.stack[-1].text_chunks.append(data)
def clean_text(value: str | None) -> str | None:
if value is None:
return None
cleaned = " ".join(value.split()).strip()
return cleaned or None
def parse_positive_int(raw_value: str) -> int:
value = int(raw_value)
if value <= 0:
raise argparse.ArgumentTypeError("must be a positive integer")
return value
def resolve_service_key(explicit_key: str | None = None) -> str:
candidate = clean_text(explicit_key) or clean_text(os.getenv(SERVICE_KEY_ENV_VAR))
if candidate:
return urllib.parse.unquote(candidate)
raise ValueError(
f"missing {SERVICE_KEY_ENV_VAR}. Export {SERVICE_KEY_ENV_VAR} or pass --service-key "
"(mapped to the KIPRIS Plus ServiceKey query parameter)."
)
def build_operation_url(operation: str) -> str:
return f"{BASE_API_URL}/{operation}"
def build_search_params(
*,
query: str,
year: int | None = None,
page_no: int = DEFAULT_PAGE_NO,
num_of_rows: int = DEFAULT_NUM_ROWS,
patent: bool = True,
utility: bool = True,
service_key: str,
) -> dict[str, str]:
if not patent and not utility:
raise ValueError("At least one of patent or utility must remain enabled for keyword search.")
params = {
"word": query,
"patent": "true" if patent else "false",
"utility": "true" if utility else "false",
"pageNo": str(page_no),
"numOfRows": str(num_of_rows),
"ServiceKey": urllib.parse.unquote(service_key),
}
if year is not None:
params["year"] = str(year)
return params
def build_detail_params(*, application_number: str, service_key: str) -> dict[str, str]:
return {"applicationNumber": application_number, "ServiceKey": urllib.parse.unquote(service_key)}
def fetch_xml(url: str, params: dict[str, str], timeout: int = DEFAULT_TIMEOUT) -> str:
request_url = f"{url}?{urllib.parse.urlencode(params)}"
request = urllib.request.Request(request_url, headers=DEFAULT_HEADERS)
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"KIPRIS Plus HTTP {exc.code}: {body or exc.reason}") from exc
except urllib.error.URLError as exc:
raise RuntimeError(f"Failed to reach KIPRIS Plus API: {exc.reason}") from exc
def normalize_tag(tag_name: str) -> str:
return tag_name.casefold()
def iter_children(element: ET.Element | XmlNode | None) -> list[ET.Element | XmlNode]:
if element is None:
return []
if isinstance(element, XmlNode):
return element.children
return list(element)
def find_child(element: ET.Element | XmlNode | None, tag_name: str) -> ET.Element | XmlNode | None:
normalized_tag = normalize_tag(tag_name)
for child in iter_children(element):
if normalize_tag(child.tag) == normalized_tag:
return child
return None
def find_children(element: ET.Element | XmlNode | None, tag_name: str) -> list[ET.Element | XmlNode]:
normalized_tag = normalize_tag(tag_name)
return [child for child in iter_children(element) if normalize_tag(child.tag) == normalized_tag]
def parse_xml_with_fallback(xml_text: str) -> XmlNode:
parser = XmlNodeBuilder()
try:
parser.feed(xml_text)
parser.close()
except Exception as exc: # pragma: no cover - defensive fallback guard
raise RuntimeError(f"Failed to parse KIPRIS Plus XML response: {exc}") from exc
if parser.root is None:
raise RuntimeError("Failed to parse KIPRIS Plus XML response: empty document")
return parser.root
def get_child_text(element: ET.Element | XmlNode | None, tag_name: str) -> str | None:
child = find_child(element, tag_name)
return clean_text(child.text if child is not None else None)
def parse_int(value: str | None) -> int | None:
if value is None:
return None
return int(value)
def parse_xml_response(xml_text: str) -> ET.Element | XmlNode:
try:
root = ET.fromstring(xml_text)
except (ET.ParseError, ImportError):
root = parse_xml_with_fallback(xml_text)
header = find_child(root, "header")
result_code = get_child_text(header, "resultCode")
result_msg = get_child_text(header, "resultMsg")
if result_code and result_code != "00":
raise RuntimeError(result_msg or f"KIPRIS Plus API error code {result_code}")
return root
def parse_patent_item(item: ET.Element | XmlNode) -> PatentSearchResult:
application_number = get_child_text(item, "applicationNumber")
if not application_number:
raise RuntimeError("KIPRIS Plus response item is missing applicationNumber")
return PatentSearchResult(
index_no=parse_int(get_child_text(item, "indexNo")),
application_number=application_number,
invention_title=get_child_text(item, "inventionTitle"),
register_status=get_child_text(item, "registerStatus"),
application_date=get_child_text(item, "applicationDate"),
open_number=get_child_text(item, "openNumber"),
open_date=get_child_text(item, "openDate"),
publication_number=get_child_text(item, "publicationNumber"),
publication_date=get_child_text(item, "publicationDate"),
register_number=get_child_text(item, "registerNumber"),
register_date=get_child_text(item, "registerDate"),
ipc_number=get_child_text(item, "ipcNumber"),
abstract_text=get_child_text(item, "astrtCont"),
applicant_name=get_child_text(item, "applicantName"),
drawing=get_child_text(item, "drawing"),
big_drawing=get_child_text(item, "bigDrawing"),
)
def parse_patent_search_response(xml_text: str, *, query: str) -> PatentSearchResponse:
root = parse_xml_response(xml_text)
body = find_child(root, "body")
items_parent = find_child(body, "items")
item_elements = find_children(items_parent, "item")
items = [parse_patent_item(item) for item in item_elements]
return PatentSearchResponse(
query=query,
page_no=parse_int(get_child_text(body, "pageNo")) or DEFAULT_PAGE_NO,
num_of_rows=parse_int(get_child_text(body, "numOfRows")) or len(items),
total_count=parse_int(get_child_text(body, "totalCount")) or len(items),
items=items,
)
def parse_patent_detail_response(xml_text: str) -> PatentDetail:
root = parse_xml_response(xml_text)
body = find_child(root, "body")
item = find_child(body, "item")
if item is None and body is not None:
items_parent = find_child(body, "items")
item = find_child(items_parent, "item")
if item is None:
raise RuntimeError("KIPRIS Plus detail response did not include an item payload")
search_item = parse_patent_item(item)
return PatentDetail(
application_number=search_item.application_number,
invention_title=search_item.invention_title,
register_status=search_item.register_status,
application_date=search_item.application_date,
open_number=search_item.open_number,
open_date=search_item.open_date,
publication_number=search_item.publication_number,
publication_date=search_item.publication_date,
register_number=search_item.register_number,
register_date=search_item.register_date,
ipc_number=search_item.ipc_number,
abstract_text=search_item.abstract_text,
applicant_name=search_item.applicant_name,
drawing=search_item.drawing,
big_drawing=search_item.big_drawing,
)
def search_patents(
query: str,
*,
year: int | None = None,
page_no: int = DEFAULT_PAGE_NO,
num_of_rows: int = DEFAULT_NUM_ROWS,
patent: bool = True,
utility: bool = True,
service_key: str | None = None,
fetcher: Callable[[str, dict[str, str], int], str] = fetch_xml,
timeout: int = DEFAULT_TIMEOUT,
) -> PatentSearchResponse:
key = resolve_service_key(service_key)
xml_text = fetcher(
build_operation_url(SEARCH_OPERATION),
build_search_params(
query=query,
year=year,
page_no=page_no,
num_of_rows=num_of_rows,
patent=patent,
utility=utility,
service_key=key,
),
timeout,
)
return parse_patent_search_response(xml_text, query=query)
def get_patent_detail(
application_number: str,
*,
service_key: str | None = None,
fetcher: Callable[[str, dict[str, str], int], str] = fetch_xml,
timeout: int = DEFAULT_TIMEOUT,
) -> PatentDetail:
key = resolve_service_key(service_key)
xml_text = fetcher(
build_operation_url(DETAIL_OPERATION),
build_detail_params(application_number=application_number, service_key=key),
timeout,
)
return parse_patent_detail_response(xml_text)
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Search Korean patent information via the official KIPRIS Plus Open API."
)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--query", help="Keyword for KIPRIS getWordSearch")
mode.add_argument("--application-number", help="Application number for bibliography detail lookup")
parser.add_argument("--year", type=parse_positive_int, help="Optional year filter for keyword search")
parser.add_argument("--page-no", type=parse_positive_int, default=DEFAULT_PAGE_NO, help="Response page number")
parser.add_argument("--num-rows", type=parse_positive_int, default=DEFAULT_NUM_ROWS, help="Rows per page")
parser.add_argument("--service-key", help=f"KIPRIS Plus ServiceKey (defaults to ${SERVICE_KEY_ENV_VAR})")
parser.add_argument("--exclude-patent", action="store_true", help="Exclude patent results from keyword search")
parser.add_argument("--exclude-utility", action="store_true", help="Exclude utility-model results from keyword search")
parser.add_argument("--timeout", type=parse_positive_int, default=DEFAULT_TIMEOUT, help="HTTP timeout seconds")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
try:
if args.query:
payload = search_patents(
args.query,
year=args.year,
page_no=args.page_no,
num_of_rows=args.num_rows,
patent=not args.exclude_patent,
utility=not args.exclude_utility,
service_key=args.service_key,
timeout=args.timeout,
)
else:
payload = get_patent_detail(
args.application_number,
service_key=args.service_key,
timeout=args.timeout,
)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 2
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 1
print(json.dumps(asdict(payload), ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__": # pragma: no cover
raise SystemExit(main())
Related skills
How it compares
Official KIPRIS Plus Korean patent API helper, not a global patent database browser.
FAQ
Which KIPRIS Plus operations does v1 support?
getWordSearch for keyword search and getBibliographyDetailInfoSearch for application-number detail lookup.
What credentials are required?
A KIPRIS Plus ServiceKey via KIPRIS_PLUS_API_KEY environment variable or --service-key flag.
Is Korean Patent Search safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.