{
  "markdown": "# Korea Data Suite\n\n**English** | [한국어](README.ko.md)\n\nClean, developer-friendly REST APIs for Korean public data.\nKorean government open data is powerful but hard to consume — Korean-only docs,\nXML responses, legacy auth. This suite normalizes it into simple JSON APIs.\n\n**▶ [Try it on RapidAPI](https://rapidapi.com/dch0202/api/korea-real-estate-holidays) — free tier, no setup.** Hosted and auto-updated; same code as this repo. For AI agents, it's on the [MCP Registry](https://modelcontextprotocol.io) — `uvx korea-data-mcp`.\n\n## APIs\n\n| API | Status | Description |\n|-----|--------|-------------|\n| Holidays & Business Days | ✅ v1 | Korean public holidays (incl. substitute & temporary holidays) and business-day calculations |\n| Real Estate Transactions | ✅ v1 | Normalized MOLIT real transaction prices (apartment/officetel/land, sale & rent) — nationwide (261 sigungu) |\n| Address Toolkit | 🚧 planned | Road/lot address conversion, romanization |\n| Business Registration | 🚧 planned | BRN validation & enrichment |\n\n## Get started\n\n**Hosted (recommended)** — a maintained instance with a **free tier** and no setup:\n\n→ **[Subscribe on RapidAPI](https://rapidapi.com/dch0202/api/korea-real-estate-holidays)**, grab your key, and call any endpoint. RapidAPI injects the key for you — copy a ready-made snippet from its Code Snippets panel.\n\n| | RapidAPI (hosted) | Self-host |\n|---|---|---|\n| Setup | API key in seconds | data.go.kr key + server + cron |\n| Data refresh | automatic (we run the sync) | you manage the scheduler |\n| Cost | free tier, then paid | free (your own infra) |\n\nBoth run the **exact same code** (this repo). Pick RapidAPI if you'd rather not operate data pipelines; self-host if you want full control.\n\n### Self-host\n\n```bash\nuv sync\nuv run uvicorn app.main:app --port 8642\ncurl \"http://127.0.0.1:8642/v1/health\"\n```\n\n## Holidays & Business Days API\n\n```bash\n# All holidays in a year (or a month)\ncurl \"http://127.0.0.1:8642/v1/holidays?year=2026\" -H \"X-API-Key: <key>\"\n\n# Is a given date a holiday / business day?\ncurl \"http://127.0.0.1:8642/v1/holidays/check?date=2026-03-02\" -H \"X-API-Key: <key>\"\n\n# Add N business days (skips weekends & holidays)\ncurl \"http://127.0.0.1:8642/v1/business-days/add?date=2026-12-31&days=1\" -H \"X-API-Key: <key>\"\n\n# Count business days in a range (inclusive)\ncurl \"http://127.0.0.1:8642/v1/business-days/count?start=2026-09-21&end=2026-09-27\" -H \"X-API-Key: <key>\"\n```\n\nCovers official public holidays, **substitute holidays** (대체공휴일),\n**temporary holidays** (임시공휴일), and election days — the cases most\nglobal holiday APIs get wrong for Korea.\n\n## Real Estate Transactions API\n\nNormalized MOLIT (Ministry of Land) real transaction prices — apartment,\nofficetel, and land; sale, jeonse, and monthly-rent — as clean English JSON\nwith cursor pagination.\n\n```bash\n# Real transaction prices (apartment sales in Gangnam-gu)\ncurl \"http://127.0.0.1:8642/v1/realestate/transactions?region=11680&property_type=apartment&trade_type=sale\" -H \"X-API-Key: <key>\"\n\n# Filter by date range + paginate with the returned cursor\ncurl \"http://127.0.0.1:8642/v1/realestate/transactions?region=11680&date_from=2026-01-01&limit=50&cursor=<next_cursor>\" -H \"X-API-Key: <key>\"\n\n# Region codes (LAWD 5-digit)\ncurl \"http://127.0.0.1:8642/v1/realestate/regions\" -H \"X-API-Key: <key>\"\n```\n\nDaily sync ingests the current + previous month; use the backfill CLI for history:\n\n```bash\nuv run python scripts/backfill.py --from 2025-01 --to 2025-12 --regions 11680,11650\n```\n\n## Configuration\n\nEnvironment variables (prefix `KDS_`, `.env` supported):\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `KDS_DEV_MODE` | `false` | Skip API-key auth (local dev) |\n| `KDS_API_KEYS` | — | Comma-separated accepted API keys |\n| `KDS_PROXY_SECRETS` | — | Comma-separated marketplace proxy secrets |\n| `KDS_DB_PATH` | `data/kds.db` | SQLite path |\n| `KDS_DATA_GO_KR_KEY` | — | data.go.kr service key (optional; enables holiday + real-estate sync) |\n| `KDS_ENABLE_SCHEDULER` | `true` | Holiday (weekly) + real-estate (daily) sync scheduler |\n| `KDS_RE_REGIONS` | all 261 nationwide sigungu | Comma LAWD codes to sync (subset override) |\n| `KDS_RE_DATASETS` | all | Comma dataset keys (apt_trade, apt_rent, offi_trade, offi_rent, land_trade) |\n\n## Data sources & attribution\n\n- Holiday data: KASI Special Day Information (한국천문연구원 특일정보),\n  via [Korea Public Data Portal (data.go.kr)](https://www.data.go.kr/) — KOGL Type 1.\n  Ships with bundled seed data (2025–2027); refreshed weekly when a service key is configured.\n- Real transaction data: MOLIT 실거래가 공개시스템 (국토교통부),\n  via [Korea Public Data Portal (data.go.kr)](https://www.data.go.kr/) — KOGL Type 1.\n\n## Run as a daemon (macOS)\n\n```bash\n# Install & start (auto-restart on crash, start at login)\n./scripts/install-daemon.sh\n\n# With Cloudflare Tunnel (after one-time `cloudflared tunnel login/create`)\n./scripts/install-daemon.sh --with-tunnel\n\n# Logs\ntail -f ~/Library/Logs/kds/api.out.log\n\n# Uninstall\nlaunchctl bootout \"gui/$(id -u)\" ~/Library/LaunchAgents/com.choiyounggi.kds-api.plist\nrm ~/Library/LaunchAgents/com.choiyounggi.kds-api.plist\n```\n\nTo keep the machine awake for serving, disable system sleep\n(`sudo pmset -a sleep 0`) or use a dedicated always-on machine.\nSee `deploy/cloudflared.example.yml` for exposing the API via Cloudflare Tunnel\nwithout opening ports.\n\n### Handling concurrent traffic\n\nThe read path and the write path are separated so traffic scales independently:\n\n- **SQLite in WAL mode** (set once at init) + `busy_timeout` — readers never block\n  the daily writer and vice-versa, and multiple read workers can run concurrently.\n- **API process is read-only, multi-worker.** `scripts/run.sh` runs uvicorn with\n  `--workers ${KDS_WORKERS:-2}` and `KDS_ENABLE_SCHEDULER=false`. Each worker is a\n  separate process (separate GIL); WAL lets them all read at once. Raise\n  `KDS_WORKERS` to scale reads with cores.\n- **The daily ingest runs as its own process** (`com.choiyounggi.kds-sync`,\n  04:00) via `scripts/sync.py` — never inside the API server, so a multi-thousand-row\n  batch never competes with request handling for the GIL.\n- **Edge caching** (optional): responses carry `Cache-Control: no-store` for\n  security. The real-estate data is public and changes at most daily — if origin\n  load grows, serve it with a short `Cache-Control: public, max-age=...` and let\n  the CDN absorb reads.\n\n### Security checklist before exposing externally\n\nThe app is hardened at the code layer (API-key auth fail-closed, parameterized\nSQL, strict input validation, security headers on every response including 5xx,\ndocs/schema off by default, sanitized errors). The following are **edge/deploy\nresponsibilities** that must be in place before opening the tunnel:\n\n- **Never set `KDS_DEV_MODE=true` in production** — it disables all auth. The\n  app logs a warning at startup if it is on.\n- **Cloudflare rate limiting + WAF** on the tunnel hostname — the app has no\n  app-layer rate limit by design (edge responsibility).\n- **HSTS + TLS** are terminated at the Cloudflare edge; confirm HSTS is enabled\n  there (the origin serves plain HTTP on `127.0.0.1` only).\n- Keep `KDS_ENABLE_DOCS` unset (or `false`) in production; set `true` only to\n  serve `/docs` `/openapi.json` at the origin.\n\n## SEO marketing site (programmatic)\n\nA static, SEO-optimized marketing site is generated **from the live DB** by\n`scripts/gen_site.py`. For every region that has real transaction data it emits a\nKorean landing page (the query users actually type — \"강남구 아파트 실거래가 API\" — backed\nby real MOLIT stats, a working `curl` example, and a signup CTA), plus a holidays\npillar page, a home page, `sitemap.xml`, and `robots.txt`.\n\n**Quality gate (important):** a region is only published if it has at least\n`MIN_SALE_ROWS` (30) apartment-sale rows. Regions without enough data are skipped —\nthis deliberately avoids thin/doorway pages, which search engines penalize.\n\n```bash\n# generate into site/dist (reads data/kds.db)\nuv run python scripts/gen_site.py --out site/dist\n```\n\nConfig is env-driven so the same generator works for any domain (put these in\n`deploy/site.env`, gitignored — copy `deploy/site.env.example`):\n\n| Env | Meaning |\n|-----|---------|\n| `KDS_SITE_URL` | canonical/sitemap base, e.g. `https://korea-data.cloud` |\n| `KDS_API_ORIGIN` | origin shown in the on-page `curl` examples, e.g. `https://api.korea-data.cloud` |\n| `KDS_CTA_URL` | signup call-to-action (RapidAPI / Zyla / Postman listing) |\n| `KDS_SITE_DIR` | output dir the app serves (default `site/dist`) |\n\n### Serving — the API app serves it\n\nThe FastAPI app serves `site/dist` at **all non-API paths** (`app.mount(\"/\")`),\nwhile `/v1/*` stays the JSON API. The two get different response headers: the API\nkeeps its locked-down `default-src 'none'` CSP + `no-store`; the site gets an\nHTML-renderable CSP (`script-src 'none'`, inline styles allowed) + `public` cache.\nFiles are read from disk per request, so **regenerating the site goes live with no\napp restart** — only a code change needs a restart.\n\nThe site is served on the **same host as the API** (`api.korea-data.cloud`) — the\nAPI lives under `/v1`, the site everywhere else — so no new tunnel hostname or DNS\nis needed. One-time on the serving host:\n\n```bash\ncp deploy/site.env.example deploy/site.env    # KDS_SITE_URL == KDS_API_ORIGIN == https://api.korea-data.cloud\nuv run python scripts/gen_site.py --out site/dist   # generate once\n# restart the API app so this integration (new code) takes effect — the site is\n# then live at https://api.korea-data.cloud/ , /holidays/ , /realestate/... .\n```\n\nSubmit `https://api.korea-data.cloud/sitemap.xml` once in Google Search Console.\n\n> Want the site on a bare `korea-data.cloud` / `www` later? Add an ingress rule\n> pointing that hostname at the same `http://127.0.0.1:8642`, route its DNS, and\n> switch `KDS_SITE_URL` to it. Not required — the api host works for SEO today.\n\n> First run needs history: the daily sync only ingests the current month. To give\n> pages real depth, backfill once —\n> `uv run python scripts/backfill.py --from 2025-07 --to 2026-06 --regions <codes> --datasets apt_trade,apt_rent`.\n\n### Automate (macOS daemon)\n\n`deploy/com.choiyounggi.kds-site.plist` regenerates the site daily at 04:30 (right\nafter the 04:00 sync) via `scripts/publish_site.sh`. Because the app serves from\ndisk, the refreshed pages are live immediately — no restart, no external deploy:\n\n```bash\ncp deploy/com.choiyounggi.kds-site.plist ~/Library/LaunchAgents/\nlaunchctl bootstrap \"gui/$(id -u)\" ~/Library/LaunchAgents/com.choiyounggi.kds-site.plist\ntail -f ~/Library/Logs/kds/site.out.log\n```\n\n> Uptime note: since the site is served by the local app (not a CDN), its SEO\n> availability tracks the machine — keep it awake for serving (`pmset`, as the API\n> already requires). If always-on hosting is wanted later, the same `site/dist` can\n> be pushed to Cloudflare Pages instead.\n\n## MCP server (AI-agent access)\n\nThe API is also packaged as a standalone [Model Context Protocol](https://modelcontextprotocol.io)\nserver — [`packages/korea-data-mcp/`](./packages/korea-data-mcp) — so AI agents\n(Claude Desktop/Code, Cursor, …) can discover and call the endpoints directly. It\nis its own minimal package (deps: `mcp`, `httpx`) and is what gets published to\nPyPI / listed in the MCP Registry.\n\nQuick add (before PyPI, straight from this repo — users bring their own key):\n\n```bash\nclaude mcp add korea-data-suite --env KDS_API_KEY=<key> \\\n  -- uvx --from \"git+https://github.com/choiyounggi/korea-data-suite#subdirectory=packages/korea-data-mcp\" korea-data-mcp\n```\n\nSee [`packages/korea-data-mcp/README.md`](./packages/korea-data-mcp/README.md)\nfor tools, client config, and `uvx korea-data-mcp` (once on PyPI).\n\n## License\n\nMIT © choiyounggi\n",
  "bytes": 11816,
  "sha": "e70c09f340b0dbc79668b1ab956e1b15a55a54835d77b1be4eb4ee695d27c207",
  "repo_slug": "choiyounggi/korea-data-suite",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_choiyounggi_korea_data_mcp_240338a4/readme"
}