* feat(kstartup-search): 창업진흥원 K-Startup 조회 스킬과 프록시 라우트 추가
공공데이터포털 dataset 15125364 (창업진흥원_K-Startup(사업소개,사업공고,콘텐츠 등)_조회서비스) 의
4개 endpoint 를 k-skill-proxy 경유로 조회하는 스킬을 추가한다.
- 신규 라우트: GET /v1/kstartup/{business-info,announcements,contents,statistics}
- 각각 getBusinessInformation01/getAnnouncementInformation01/getContentInformation01/
getStatisticalInformation01 으로 중계
- ServiceKey 는 서버 측 DATA_GO_KR_API_KEY 로 주입, returnType=json 강제
- 정상 응답만 캐시, data.go.kr 에러 envelope (resultCode != "00", errMsg 등) 은 캐시 우회
- helper: kstartup-search/scripts/run_kstartup.py (stdlib only)
- 일반 조회는 hosted proxy 사용 → 사용자 키 불필요
- --direct 옵션은 사용자가 본인 KSKILL_KSTARTUP_API_KEY (혹은 DATA_GO_KR_API_KEY) 로
upstream 직접 호출 + --dry-run 시 키 redact
- 입력 검증: page/perPage 정수·범위, YYYYMMDD 날짜 + 시작일 ≤ 종료일, Y/N 대문자화,
텍스트 필드 길이 상한, biz_yr 4자리
- 테스트: k-skill-proxy 서버 테스트 10건 신규 (normalizer, 라우트, 캐시 분리,
returnType=json 강제, 503/400/502, 키 누수 회귀), Python unittest 13건
- 문서: SKILL.md, docs/features/kstartup-search.md, README 표/리스트,
docs/sources.md, .changeset/kstartup-search.md (k-skill-proxy minor)
* docs(kstartup-search): docs/setup·security·k-skill-setup·proxy README 에 K-Startup 항목 추가
seoul-density · KOSIS · NTS 선례와 동일한 위치·문구로 다음을 보강한다.
- docs/setup.md: dotenv 예시에 KSKILL_KSTARTUP_API_KEY 추가, credential 표에 K-Startup 행 추가, "다음에 볼 문서" 리스트 추가
- docs/security-and-secrets.md: standard variable names 에 KSKILL_KSTARTUP_API_KEY 추가, hosted proxy 사용 스킬 목록·proxy 운영 prose 에 K-Startup 추가, dotenv 예시 추가
- k-skill-setup/SKILL.md: credential resolution prose 와 시크릿 요약 표에 K-Startup 안내 추가
- packages/k-skill-proxy/README.md: 라우트 목록에 /v1/kstartup/{business-info,announcements,contents,statistics} 추가
- docs/features/k-skill-proxy.md: 라우트 목록에 같은 4개 추가
* fix(kstartup-search): strict calendar-date validation in Python helper
validate_yyyymmdd() previously only checked month in [1,12] and day in [1,31],
which accepted impossible dates like 20240230 or 20240431 in --direct mode.
The proxy-side normalizer in packages/k-skill-proxy/src/kstartup.js already
uses Date.UTC() to reject such inputs, so this aligns the --direct path with
the proxy path and eliminates validator drift.
Uses datetime.date(year, month, day) and raises HelperError on ValueError.
Adds regression test covering impossible calendar dates (Feb 30, Apr 31,
month 13, day 0) and the leap-year boundary (2024-02-29 valid, 2023-02-29
not).
---------
Co-authored-by: Jeffrey (Dongkyu) Kim <vkehfdl1@gmail.com>
12 KiB
k-skill 프록시 서버 가이드
이 기능으로 할 수 있는 일
- AirKorea 같은 무료/공공 API key를 서버에만 보관
k-skill클라이언트는 프록시만 호출- 캐시, 인증, rate limit, 로깅을 한곳에서 통제
기본 구조
client/skill -> k-skill-proxy -> upstream public API
현재 기본 엔드포인트는 아래와 같습니다.
GET /healthGET /v1/fine-dust/reportGET /v1/korea-weather/forecastGET /v1/seoul-subway/arrivalGET /v1/seoul-density/citydata(서울 실시간 도시데이터 핫스팟 혼잡도/추정 인구,SEOUL_OPEN_API_KEY)GET /v1/han-river/water-levelGET /v1/household-waste/info(생활쓰레기 배출정보,DATA_GO_KR_API_KEY; 쿼리pageNo·numOfRows필수, 값1·100)GET /v1/mfds/drug-safety/lookup(식약처 의약품개요정보 + 안전상비의약품 정보,DATA_GO_KR_API_KEY)GET /v1/mfds/food-safety/search(식약처 부적합 식품 + 식품안전나라 회수 정보,DATA_GO_KR_API_KEY, 선택적FOODSAFETYKOREA_API_KEY)GET /v1/korean-stock/searchGET /v1/korean-stock/base-infoGET /v1/korean-stock/trade-infoGET /v1/naver-shopping/search(네이버 검색 Open API 쇼핑 검색 우선, 키가 없으면 공개 BFF JSON 기반 상품/가격 후보 조회)GET /v1/opinet/aroundGET /v1/opinet/detailGET /v1/neis/school-search(나이스 학교기본정보,KEDU_INFO_KEY)GET /v1/neis/school-meal(나이스 급식식단정보,KEDU_INFO_KEY)GET /v1/data4library/library-search(도서관 정보나루 정보공개 도서관 조회,DATA4LIBRARY_AUTH_KEY)GET /v1/data4library/book-search(도서관 정보나루 도서 검색,DATA4LIBRARY_AUTH_KEY)GET /v1/data4library/book-detail(도서관 정보나루 도서 상세 조회,DATA4LIBRARY_AUTH_KEY)GET /v1/data4library/libraries-by-book(도서 소장 도서관 조회,DATA4LIBRARY_AUTH_KEY)GET /v1/data4library/book-exists(도서관별 도서 소장여부,DATA4LIBRARY_AUTH_KEY)GET /v1/kstartup/business-info(창업진흥원 K-Startup 통합공고 지원사업 정보,DATA_GO_KR_API_KEY)GET /v1/kstartup/announcements(창업진흥원 K-Startup 지원사업 공고 정보,DATA_GO_KR_API_KEY)GET /v1/kstartup/contents(창업진흥원 K-Startup 창업 콘텐츠 정보,DATA_GO_KR_API_KEY)GET /v1/kstartup/statistics(창업진흥원 K-Startup 통계보고서 정보,DATA_GO_KR_API_KEY)GET /B552584/:service/:operation(허용된 AirKorea route passthrough)
권장 환경변수
클라이언트(스킬) 쪽:
- 일반 hosted client는
KSKILL_PROXY_BASE_URL을 unset/empty로 비워 두면 hostedhttps://k-skill-proxy.nomadamas.org를 기본값으로 사용합니다. KSKILL_PROXY_BASE_URL=https://your-proxy.example.com은 self-host 또는 alternate proxy를 명시적으로 쓰는 경우에만 설정하는 override 예시입니다.
프록시 서버 쪽:
AIR_KOREA_OPEN_API_KEY=...KMA_OPEN_API_KEY=...SEOUL_OPEN_API_KEY=...HRFCO_OPEN_API_KEY=...OPINET_API_KEY=...DATA_GO_KR_API_KEY=...FOODSAFETYKOREA_API_KEY=...(선택: 식품안전나라 회수 live 결과, 없으면 sample fallback)KEDU_INFO_KEY=...(나이스 교육정보 개방 포털 Open API 인증키)DATA4LIBRARY_AUTH_KEY=...(도서관 정보나루 Open API 인증키)KRX_API_KEY=...NAVER_SEARCH_CLIENT_ID=...,NAVER_SEARCH_CLIENT_SECRET=...(선택: 네이버 검색 Open API 쇼핑 검색)KSKILL_PROXY_PORT=4020
프로덕션 배포 구조
프로덕션 proxy 서버는 개발 repo와 분리된 별도 clone으로 운영한다.
- 배포 디렉토리:
~/.local/share/k-skill-proxy(main 브랜치 단독 clone) - PM2 프로세스:
k-skill-proxy - Cloudflare Tunnel ingress:
k-skill-proxy.nomadamas.org -> http://localhost:4020
자동 배포 (cron)
~/.local/share/k-skill-proxy/scripts/auto-update-proxy.sh가 매시 정각에 실행된다.
0 * * * * PATH=/usr/bin:/opt/homebrew/bin:/opt/homebrew/lib/node_modules/.bin:$PATH ~/.local/share/k-skill-proxy/scripts/auto-update-proxy.sh >> /tmp/k-skill-proxy-update.log 2>&1
동작 순서:
git fetch origin main- local SHA == remote SHA 이면 종료 (up-to-date)
git pull --ff-onlypackage-lock.json변경 시npm cipm2 restart k-skill-proxy --update-env
따라서 main에 merge되어야 프로덕션에 반영된다. dev 브랜치 변경은 프로덕션에 영향 없음.
로그: /tmp/k-skill-proxy-update.log
초기 설정 (PM2 + cloudflared)
pm2 start ecosystem.config.cjspm2 savepm2 startup출력대로 launchd 등록- Cloudflare Tunnel ingress 에
k-skill-proxy.nomadamas.org -> http://localhost:4020추가
기본 공개 정책
- 이 프록시는 무료 API만 붙인다.
- 기본값은 무인증 공개 endpoint 다.
- 대신 read-only / allowlisted endpoint / cache / rate limit 을 유지한다.
- 문제가 생기면 그때 인증이나 더 강한 방어를 덧붙인다.
사용법
추가 client API 레이어는 불필요합니다. 필요한 쿼리를 그대로 프록시에 넣으면 되고, 프록시가 upstream API key 만 서버에서 주입합니다.
요약 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/fine-dust/report' \
--data-urlencode 'regionHint=서울 강남구'
서울 지하철 도착정보 endpoint:
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
curl -fsS --get "${BASE}/v1/seoul-subway/arrival" \
--data-urlencode 'stationName=강남'
서울 실시간 혼잡도 endpoint:
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
curl -fsS --get "${BASE}/v1/seoul-density/citydata" \
--data-urlencode 'area=강남역'
한국 날씨 endpoint:
BASE="${KSKILL_PROXY_BASE_URL:-https://k-skill-proxy.nomadamas.org}"
curl -fsS --get "${BASE}/v1/korea-weather/forecast" \
--data-urlencode 'lat=37.5665' \
--data-urlencode 'lon=126.9780'
한강 수위 정보 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/han-river/water-level' \
--data-urlencode 'stationName=한강대교'
이 endpoint 는 내부적으로 HRFCO waterlevel/info.json 으로 관측소를 찾고, waterlevel/list/10M/{WLOBSCD}.json 으로 최신 10분 수위/유량을 가져옵니다.
Opinet 근처 주유소 가격 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/opinet/around' \
--data-urlencode 'x=313680' \
--data-urlencode 'y=545015' \
--data-urlencode 'radius=1500' \
--data-urlencode 'prodcd=B027'
Opinet 주유소 상세 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/opinet/detail' \
--data-urlencode 'id=A0009905'
나이스 학교 검색·급식 endpoint (학교 급식 식단 스킬에서 사용):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/neis/school-search' \
--data-urlencode 'educationOffice=서울특별시교육청' \
--data-urlencode 'schoolName=미래초등학교'
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/neis/school-meal' \
--data-urlencode 'educationOfficeCode=B10' \
--data-urlencode 'schoolCode=7010123' \
--data-urlencode 'mealDate=20260410'
생활쓰레기 배출정보 endpoint. 쿼리에 pageNo와 numOfRows를 반드시 포함하고, 값은 각각 1, **100**만 허용한다(page_no / num_of_rows 동일). 누락·다른 값·숫자만이 아닌 문자열이면 400(upstream 미호출):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/household-waste/info' \
--data-urlencode 'cond[SGG_NM::LIKE]=강남구' \
--data-urlencode 'pageNo=1' \
--data-urlencode 'numOfRows=100'
의약품 안전 체크 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/mfds/drug-safety/lookup' \
--data-urlencode 'itemName=타이레놀' \
--data-urlencode 'itemName=판콜' \
--data-urlencode 'limit=5'
식품 안전 체크 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/mfds/food-safety/search' \
--data-urlencode 'query=김밥' \
--data-urlencode 'limit=5'
KOSIS 통계 조회 endpoint (KOSIS_API_KEY 필요, caller apiKey는 무시하고 서버 쪽 키를 주입):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/kosis/search' \
--data-urlencode 'q=1인 가구' \
--data-urlencode 'limit=3'
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/kosis/meta' \
--data-urlencode 'tableId=DT_1JC1501' \
--data-urlencode 'metaType=ITM'
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/kosis/data' \
--data-urlencode 'tableId=DT_1JC1501' \
--data-urlencode 'prdSe=Y' \
--data-urlencode 'start=2020' \
--data-urlencode 'end=2023' \
--data-urlencode 'objL1=ALL'
Kakao Local geocoding endpoint (KAKAO_REST_API_KEY 필요, caller apiKey는 무시하고 서버 쪽 키를 주입):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/kakao-local/geocode' \
--data-urlencode 'q=서울역' \
--data-urlencode 'limit=1'
도서관 정보나루 도서 검색 endpoint (DATA4LIBRARY_AUTH_KEY 필요):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/data4library/book-search' \
--data-urlencode 'keyword=역사' \
--data-urlencode 'pageNo=1' \
--data-urlencode 'pageSize=10'
도서 상세/소장 조회 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/data4library/book-detail' \
--data-urlencode 'isbn13=9788971998557' \
--data-urlencode 'loaninfoYN=Y'
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/data4library/libraries-by-book' \
--data-urlencode 'isbn=9788971998557' \
--data-urlencode 'region=11'
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/data4library/book-exists' \
--data-urlencode 'libraryCode=111001' \
--data-urlencode 'isbn13=9788971998557'
프록시는 caller가 넘긴 authKey/format을 무시하고 서버 쪽 DATA4LIBRARY_AUTH_KEY와 format=json을 주입한다.
네이버 쇼핑 가격비교 endpoint (NAVER_SEARCH_CLIENT_ID/NAVER_SEARCH_CLIENT_SECRET이 있으면 공식 Search API 우선):
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/naver-shopping/search' \
--data-urlencode 'q=에어팟 프로 2세대' \
--data-urlencode 'limit=10'
키가 없는 no-key fallback은 search.shopping.naver.com/search/all HTML 페이지 대신
ns-portal.shopping.naver.com/api/v2/shopping-paged-slot?query=<검색어>&source=shp_gui
공개 JSON path를 사용한다. page는 BFF에 전달한 뒤 해당 페이지 카드만 정규화하고, no-key
price_asc/price_dsc/review 정렬은 선택된 BFF 페이지 안에서 로컬 적용한다. BFF에는 날짜
필드가 없어 no-key date 요청은 meta.sort_applied: "unsupported"로 표시한다.
한국 주식 검색 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/korean-stock/search' \
--data-urlencode 'q=삼성전자' \
--data-urlencode 'bas_dd=20260408'
한국 주식 기본정보 endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/korean-stock/base-info' \
--data-urlencode 'market=KOSPI' \
--data-urlencode 'code=005930' \
--data-urlencode 'bas_dd=20260408'
AirKorea passthrough endpoint:
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty' \
--data-urlencode 'returnType=json' \
--data-urlencode 'numOfRows=1' \
--data-urlencode 'pageNo=1' \
--data-urlencode 'stationName=강남구' \
--data-urlencode 'dataTerm=DAILY' \
--data-urlencode 'ver=1.4'
주의할 점
- upstream key는 프록시 서버에서만 관리합니다.
- 한국 주식 route도 사용자에게
KRX_API_KEY를 배포하지 않습니다. - client 쪽에는 upstream API key를 배포하지 않습니다.
- 도서관 정보나루 route도 사용자에게
DATA4LIBRARY_AUTH_KEY를 배포하지 않습니다. - self-host proxy 운영자는 동일 route를 local/self-host URL 로도 검증합니다.