# 한국형 AI 웹 크롤러 API
챗봇·RAG·에이전트에 웹사이트를 읽힐 때, 광고·메뉴·깨진 글자로 가득한 원본 HTML 대신 **LLM이 바로 이해하는 깨끗한 마크다운/JSON**으로 돌려드려요. 한국 웹은 인코딩·구조가 제각각이라 일반 크롤러는 깨지기 쉽지만, 코어핀은 정확히 추출하고 깨진 글자를 자동 복원해요.
출력은 `output_format`(`text`/`markdown`/`html`/`json`)으로 골라 받아요 — RAG 적재엔 `json`(blocks IR), 프롬프트엔 `markdown`. 첨부(`.hwp`·`.pdf` 등)·개인정보 마스킹·기밀 등급 검사도 같은 호출에 묶을 수 있어요(아래 옵션 참고).
- **모델 ID**: `crawl`
- **Base URL**: `https://api.corepin.ai`
- **버전**: `crawl-2026.07`
## 1. 엔드포인트
| Method | Path | 인증 | 단가 | 설명 |
|---|---|:-:|---:|---|
| POST | `/v1/crawl/scrape` | ✓ | 2원 / 페이지 | 웹 페이지 → 본문 + (옵션) HWP/PDF 첨부 자동 파싱 (여러 URL 일괄, 최대 20개) |
| POST | `/v1/crawl/map` | ✓ | 2원 / 사이트 | 사이트에 어떤 페이지들이 있는지 주소 목록을 먼저 훑어보기 (풀크롤 전 미리보기 · sitemap + 1-hop 링크) |
| POST | `/v1/crawl/crawl` | ✓ | 2원 / 페이지 | **풀사이트 자동 크롤 (비동기 잡)** — 시작 URL에서 같은 사이트를 따라가며 여러 페이지. 옛날(자바스크립트) 게시판·로그인 게시판 대응 |
| GET | `/v1/crawl/crawl/{job_id}` | ✓ | — | 크롤 잡 상태·결과 조회 |
| GET | `/v1/crawl/crawl` | ✓ | — | 내 크롤 잡 목록 (최근 50건) |
| DELETE | `/v1/crawl/crawl/{job_id}` | ✓ | — | 크롤 잡 취소 (이미 긁은 페이지만 과금) |
| POST | `/v1/crawl/credentials` | ✓ | 무료 | 인증 크롤용 전용 계정 등록 (아이디·비번 암호화 보관) |
| GET | `/v1/crawl/credentials` | ✓ | — | 등록한 자격증명 목록 (참조 ID·이름만) |
| DELETE | `/v1/crawl/credentials/{ref}` | ✓ | — | 자격증명 폐기 |
| GET | `/v1/crawl/formats` | ✓ | — | 기능·옵션·첨부 지원 형식·제약 |
| GET | `/v1/crawl/version` | ✓ | — | 모델 버전·기능 |
응답 시간: 본문만 단건 0.3–2초 · 여러 URL 일괄 1–10초(동시 4건) · 첨부 파싱은 첨부 1건당 +0.3–5초(문서 형식·페이지 수에 따라, 동시 3건). 풀사이트 크롤(`/v1/crawl/crawl`)은 페이지 수·예의(politeness) 지연에 따라 수 초~수 분이라 **비동기 잡**으로 돌아가요(제출 즉시 `job_id` 반환, 폴링 또는 webhook 으로 완료 확인).
청구는 **성공한 페이지만**이에요. 접속 불가·본문 없음·robots 차단 등으로 실패한 URL 은 청구되지 않아요(아래 §7).
## 2. POST `/v1/crawl/scrape`
요청은 `application/json` 입니다. `url`(단건) 또는 `urls`(여러 건, 최대 20개) 중 최소 하나를 주세요. 두 필드를 함께 줘도 합쳐서 중복 제거 후 처리합니다.
순수 본문 추출 엔진은 `/v1/doc/parse/url` 과 같은 오픈소스 **trafilatura**(Apache-2.0)를 자체 내재화 — 호출당 외부 비용이 없어요. 크롤 제품으로서 여기에 **첨부 자동 파싱**·robots 준수·인코딩 자동 복원을 더했습니다.
**Request 필드**:
| 필드 | 타입 | 기본 | 설명 |
|---|---|---|---|
| `url` | string | — | 스크랩할 단일 웹 페이지 URL (http/https). |
| `urls` | string[] | — | 여러 URL 일괄 (최대 20개). |
| `output_format` | string | `markdown` | 응답 형식. `text` / `markdown` / `html` / `json` 중 선택. |
| `render` | bool | `false` | JS 렌더(Playwright). 스크립트로만 그려지는 SPA 페이지에 사용 — 렌더 성공 페이지당 **5원**(`crawl_render`). 렌더 불가/실패 시 정적 추출로 자동 폴백(₩2)하고, 그 페이지 `warnings` 에 `JS 렌더를 요청했지만 실행하지 못해 정적 추출 결과예요(사유코드)` 가 실려요(아래 「렌더를 못 돌렸을 때」). |
| `render_wait_ms` | int | `0` | 렌더 후 추가 대기(ms, 0~8000) — 지연 로딩 콘텐츠가 채워질 시간. |
| `parse_attachments` | bool | `false` | 페이지에 `.hwp` ·`.hwpx` ·`.pdf` ·`.docx` ·`.xlsx` 등 첨부가 있을 경우 자동 발견·다운로드·파싱해 `attachments[]` 로 함께 반환. 첨부 파싱 페이지당 **+2원**(문서 파서 단가). |
| `max_attachments` | int | `10` | 페이지당 파싱할 첨부 최대 개수 (0~50, 비용 폭증 방어). |
| `max_attachment_pages` | int | `100` | **첨부 1건**이 이 쪽수를 넘으면 파싱하지 않고 건너뛰어요(0~2000, 비용 폭증 방어). 첨부는 페이지당 +2원이라 300쪽짜리 보고서 하나가 600원입니다. 넘친 첨부는 `attachment_page_budget_exceeded` 오류로 `attachments[]` 에 남고 **청구되지 않아요**(파싱 자체를 안 함). `0` = 무제한. 실측상 게시판 첨부는 1~20쪽, 보고서류는 180~330쪽이라 100쪽이면 통상 첨부는 전부 통과합니다 — 300쪽짜리 장문 보고서는 파일을 직접 받아 `/v1/doc/parse` 로 처리하는 편이 낫습니다. PDF 만 파싱 전에 쪽수를 셀 수 있어 HWP·DOCX 에는 적용되지 않아요. |
| `respect_robots` | bool | `false` | true 면 robots.txt Disallow 경로는 `403 blocked_by_robots` 로 거부 (합법 크롤링 증빙). |
| `pii_mask` | bool | `false` | 추출 본문·첨부 자동 개인정보 마스킹. **+5원 / 건** (적용된 본문·첨부마다). |
| `dlp_grade` | bool | `false` | 추출 본문·첨부 자동 보안 등급·유형 분류. **+20원 / 건**. |
| `moderation_check` | bool | `false` | 본문·첨부 유해발화 자동 감지. **+5원 / 건**. |
| `mental_health_check` | bool | `false` | 본문·첨부 정신건강 위험 신호 감지. **+10원 / 건**. 사람이 쓴 문장이 없는 페이지·첨부는 검사를 건너뛰고(`screened: false`) **청구도 안 돼요**. |
| `pii_categories` | string[] | `null` | **`pii_mask=true`일 때만.** 마스킹할 PII 카테고리 화이트리스트 (예: `["kr_rrn","private_phone"]`). 비우면 전체. 코드는 `GET /v1/pii/categories`. |
| `dlp_grades` | string[] | `null` | **`dlp_grade=true`일 때만.** 보고할 보안 등급 화이트리스트. 목록 밖이면 `grade`가 `null`. 비우면 전체. 코드는 `GET /v1/dlp/grades`. |
| `dlp_types` | string[] | `null` | **`dlp_grade=true`일 때만.** 보고할 보안 유형 화이트리스트. 비우면 전체. 코드는 `GET /v1/dlp/types`. |
| `moderation_labels` | string[] | `null` | **`moderation_check=true`일 때만.** 보고할 유해발화 라벨 화이트리스트. 비우면 전체. |
| `mental_health_axes` | string[] | `null` | **`mental_health_check=true`일 때만.** 알림 대상 정신건강 축 화이트리스트. 비우면 전체. 코드는 `GET /v1/mental_health/axes`. |
> **대상 화이트리스트.** 위 5개 필드는 해당 옵션이 켜졌을 때만 의미가 있고, **비우면 전체 대상**으로 동작해요. JSON body 라 **배열**로 넘깁니다(예: `["kr_rrn","private_phone"]`). 모델 추론 자체는 항상 전체 대상으로 수행되므로 **추가 비용은 없어요** — 화이트리스트는 응답에 담길 결과만 좁혀요. 의미·동작은 [통합 문서 파서](/docs/doc)와 동일합니다.
**렌더를 못 돌렸을 때** — `render=true` 는 「되면 렌더, 안 되면 정적 추출」인 best-effort 옵션이라 응답은 성공(200)으로 오되, 렌더가 실제로 실행되지 못했다면 해당 페이지에 다음이 함께 와요. 스크립트로만 본문을 그리는 페이지는 이때 본문이 비어 있을 수 있으니, 호출하는 쪽에서는 `rendered: false` 또는 이 경고를 보고 「이 페이지는 읽지 못했다」로 안내하면 돼요. 이 페이지는 정적 단가(₩2)로만 청구돼요.
- `rendered: false` · `warnings: ["JS 렌더를 요청했지만 실행하지 못해 정적 추출 결과예요(<사유코드>). …"]`
- 사유코드: `render_service_error`(렌더 서비스 오류) · `render_timeout`(시간 초과) · `render_service_unreachable`(렌더 서비스 연결 불가) · `render_circuit_open`(연속 실패로 잠시 건너뜀, 약 1분) · `render_url_blocked`(내부망 등 렌더 불가 주소) · `render_empty`(빈 결과) · `render_disabled`(서버 설정으로 꺼짐) · `render_unavailable`(그 외)
- 풀사이트 크롤(`/v1/crawl/crawl`)도 같은 경고가 페이지별 `warnings` 에 실려요.
**렌더는 됐는데 다 안 그려졌을 때** — 렌더가 실제로 실행됐더라도, 대기가 끝난 순간 **보이는 글자가 적고(300자 미만) 아직 진행 중인 일이 있으면**(끝나지 않은 XHR/fetch 요청, 아직 안 터진 0.5초 이상 타이머) 그 페이지 `warnings` 에 다음이 실려요. 예를 들어 본문을 10초 뒤에 그리는 페이지는 대기 상한(8초) 안에 본문이 없습니다.
- `rendered: true` · `warnings: ["JS 렌더는 실행됐지만 대기 시간 안에 페이지가 다 그려지지 않았을 수 있어요(render_wait_limit: …). 본문이 비어 있거나 일부만 있을 수 있어요. render_wait_ms 를 늘려 보세요(최대 8000)."]`
- 렌더는 실행됐으므로 렌더 단가(₩5)로 청구돼요. 글자가 충분한 페이지는 이 경고를 달지 않아요(캐러셀 같은 타이머가 늘 도는 페이지에서 경고가 남발되지 않게). 대기 상한은 8초라 `render_wait_ms` 를 최대로 올려도 그보다 늦게 그려지는 본문은 받을 수 없어요.
**성공(200)인데 내용이 차단·오류 화면일 때** — 차단 화면·앱 오류 화면은 **HTTP 200 으로 오고 본문만 짧습니다**. 이때는 성공과 구분이 사라지므로 아래 세 신호를 함께 돌려줘요. 셋은 서로 독립이라 하나만 있어도 해당 경고가 붙습니다.
| 신호 | 무엇을 보나 | 경고 사유 코드 |
|---|---|---|
| `status_code` | 문서가 돌려준 HTTP 상태 (403·503 등) | `http_4xx_page` · `http_5xx_page` |
| `render_stats.js_errors` · `console_errors` · `failed_requests` | 렌더 중 페이지가 낸 오류 — **오류가 있고 본문이 짧을 때(300자 미만)** | `render_page_error` |
| `warnings` 의 문구 추정 | 200 + 짧은 본문 + 차단 화면 특유의 문구(`Access Denied` · `Are you a person or a robot?` 등) | `block_page_suspected` |
- **① `status_code`** — 렌더로 가져온 문서가 403·503 등을 돌려주면 본문과 함께 `status_code` 가 오고 `http_4xx_page`/`http_5xx_page` 경고가 붙어요. 본문은 버리지 않습니다(안내문이 유용할 수 있어요). 정적 경로에서 4xx·5xx 면 기존대로 `error` 로 거부되고 `error.status_code` 에 그 값이 실려요.
- **② `render_stats`** — `js_errors`(처리되지 않은 JS 예외) · `console_errors`(console.error — 프레임워크 오류 경계가 삼킨 예외가 여기 옵니다) · `failed_requests`(실패한 XHR·fetch 4xx·5xx·네트워크 실패) · `text_len`. **오류가 하나라도 있고 전달하는 본문이 300자 미만이면** `render_page_error` 경고가 붙어요. 판정 기준은 `text_len`(브라우저가 본 화면 글자, 메뉴·푸터 포함)이 아니라 **`text`/`markdown` 으로 실제 돌려주는 본문**이에요 — 화면 글자와 추출 본문은 크게 달라서(실측: 85자 vs 392자) 화면 기준으로 재면 오경고가 납니다. 본문이 충분하면 광고·애널리틱스 실패는 잡음이라 경고하지 않지만, **개수는 항상 실어요**(0 ≠ 모름). 이미지·폰트·미디어를 일부러 차단하므로 그 실패는 세지 않습니다.
- **③ `block_page_suspected`** — 봇 차단·로봇 확인 화면은 200 이고 JS 오류도 없어 위 둘로 안 잡히는 경우가 있어요(예: 스카이스캐너 "Are you a person or a robot?"). 본문이 **1,500자 미만이면서** 차단 화면 특유의 문구가 있을 때만 붙는 **추정**입니다 — 본문·청구는 바꾸지 않아요. 캡차를 주제로 쓴 긴 글은 걸리지 않습니다.
**iframe 안의 본문도 읽어요**(렌더 경로) — 본문을 `<iframe>` 안에 넣는 사이트(예: 네이버 블로그 `PostView.naver`)는 최상위 문서가 빈 껍데기예요. 렌더러가 **본문이 있는 하위 프레임의 내용을 최상위 문서에 합친 뒤** 추출해요(50자 미만 프레임은 광고·위젯으로 보고 버림). 실측 2026-10-20: 네이버 블로그 글이 이전 `no_content` → 1,373자로 나옵니다. 정적 경로(렌더 없이 긁기)에는 적용되지 않아요 — 그런 사이트는 `render: true` 를 쓰세요.
**렌더가 정적보다 덜 얻었을 때** — 렌더 결과가 정적 추출보다 **짧으면** 정적 쪽을 대신 쓰고 그 페이지를 **정적 단가(₩2)로 청구**해요(예: 앱이 렌더러에서만 죽어 오류 화면만 남는 사이트). `warnings` 에 `render_thinner_than_static` 이 실리고 `rendered: false` 가 돼요. 이 비교는 페이지 오류·대기 상한이 관측된 경우에만 수행하므로(여분 요청 1회) 정상 페이지의 지연·비용은 늘지 않아요.
풀사이트 크롤(`/v1/crawl/crawl`)도 같은 신호를 페이지별로 싣고, 상태 조회의 `done` 응답에 `http_error_pages` · `render_error_pages` 요약이 함께 와요(성공으로 집계됐지만 실제로는 오류 화면인 페이지 수).
**Response 필드** — `results[]` (페이지별) + `meta`. 각 페이지 결과(`CrawlScrapeItem`):
| 필드 | 타입 | 설명 |
|---|---|---|
| `url` | string | 요청한 원본 URL |
| `final_url` | string | 리다이렉트 후 최종 URL |
| `title` | string | 페이지 제목 (추출된 경우) |
| `status_code` | int | **페이지 문서가 돌려준 HTTP 상태코드.** 4xx·5xx 면 차단·오류 화면이 본문으로 온 것일 수 있어요(`warnings` 의 `http_4xx_page`/`http_5xx_page` 참고). 정적 경로는 4xx·5xx 를 `error` 로 거부하므로 이 필드가 붙는 값은 대개 2xx·3xx 이고, **렌더 경로에서 차단 화면이 오면 403 등이 그대로** 실려요. |
| `render_stats` | object | 렌더를 돌렸을 때만 — `{js_errors, console_errors, failed_requests, text_len}`. `js_errors`=처리되지 않은 JS 예외 수, `console_errors`=console.error 횟수(프레임워크 오류 경계가 삼킨 예외 포함), `failed_requests`=**실패한 XHR·fetch**(4xx·5xx·네트워크 실패) 수, `text_len`=대기 종료 시점 보이는 글자 수. **0 은 「오류 없음」, 필드 없음은 「모름」(렌더 안 돌림·옛 렌더 서비스)** — 둘을 구분하세요. |
| `text` | string | 본문 (plain text) |
| `markdown` | string | `output_format=markdown`/`html`/`json`일 때 — 헤더·표·리스트 보존 마크다운 |
| `html` | string | `output_format=html`일 때 |
| `blocks` | array | `output_format=json`일 때 — IR 블록 배열 |
| `pii_applied` | object | `pii_mask=true`일 때 — `{applied, span_count}` |
| `dlp_classification` | object | `dlp_grade=true`일 때 — `{grade, grade_confidence, types}` |
| `moderation` | object | `moderation_check=true`일 때 |
| `mental_health` | object | `mental_health_check=true`일 때 — `{risk_level, recommended_action, flagged_axes, screened}`. `screened: false`는 「검사 대상 아님」(문서 파서 문서의 `screened` 절 참고). |
| `attachments` | array | `parse_attachments=true`일 때 — 발견·파싱한 첨부 목록 (`CrawlAttachmentItem`) |
| `warnings` | array | 사용자 알림 (아래 「성공(200)인데 오류 화면일 때」 참고) |
| `error` | object | 이 URL 처리 실패 시 — `{code, message}`. **가져온 문서가 4xx·5xx 였으면 `status_code` 에 그 값**(예: 403)이 함께 와요 — 우리 API 의 응답 코드와 다릅니다. (실패 URL 은 청구 제외) |
첨부 1건(`CrawlAttachmentItem`):
| 필드 | 타입 | 설명 |
|---|---|---|
| `url` | string | 첨부 다운로드 URL (페이지 기준 절대 경로) |
| `filename` | string | 첨부 파일명 (URL 경로 또는 다운로드 파라미터 힌트에서 추출) |
| `format` | string | 감지된 형식 (`hwp`/`pdf`/`docx` 등) |
| `page_count` | int | 첨부 페이지 수 (첨부 청구 단위 — 페이지당 +2원) |
| `text` · `markdown` · `html` · `blocks` | — | 본문 결과 (본문과 동일하게 `output_format` 따라) |
| `pii_applied` · `dlp_classification` · `moderation` · `mental_health` | object | +α 옵션이 켜졌을 때 — 첨부 본문에 적용된 결과 |
| `needed_ocr` | bool | 스캔 첨부 자동 OCR 발동 여부 |
| `ocr_pages_used` | int | OCR 한 페이지 수 (OCR 페이지당 +2원 별도 청구) |
| `warnings` | array | 첨부 처리 알림 |
| `error` | object | 첨부 1건 실패 시 — `{code, message}` (다른 첨부·본문은 정상 반환) |
**예시 — 단건**:
```bash
curl -X POST https://api.corepin.ai/v1/crawl/scrape \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://ko.wikipedia.org/wiki/삼성전자", "output_format": "markdown"}'
```
**예시 — 게시글 본문 + HWP 첨부 함께 파싱**:
```bash
curl -X POST https://api.corepin.ai/v1/crawl/scrape \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.example.go.kr/board/view?id=123",
"output_format": "markdown",
"parse_attachments": true,
"max_attachments": 5
}'
```
```json
{
"results": [
{
"url": "https://www.example.go.kr/board/view?id=123",
"final_url": "https://www.example.go.kr/board/view?id=123",
"title": "○○ 사업 공고",
"text": "공고번호: 2026061234 / 담당부서: ○○과 / ...",
"markdown": "# ○○ 사업 공고\n\n| 항목 | 내용 |\n|---|---|\n| 공고번호 | 2026061234 |\n| 접수마감 | 2026-07-03 18:00 |",
"attachments": [
{
"url": "https://www.example.go.kr/cmm/fms/FileDown.do?atchFileId=FILE_000123&fileName=보고서.hwp",
"filename": "보고서.hwp",
"format": "hwp",
"page_count": 12,
"text": "Ⅰ. 일반사항\n1. 사업명: ○○ 사업 ...",
"markdown": "## Ⅰ. 일반사항\n\n1. 사업명: ○○ 사업\n\n| 구분 | 내용 |\n|---|---|\n| 추정가격 | 1,250,000,000원 |"
},
{
"url": "https://www.example.go.kr/cmm/fms/FileDown.do?atchFileId=FILE_000123&fileName=과업지시서.pdf",
"filename": "과업지시서.pdf",
"format": "pdf",
"page_count": 8,
"text": "제1장 과업 개요 ...",
"markdown": "## 제1장 과업 개요\n\n..."
}
]
}
],
"meta": {
"model_id": "crawl",
"model_version": "crawl-2026.06",
"processing_time_ms": 3412.8,
"request_id": "abc...",
"url_count": 1,
"success_count": 1,
"rendered_count": 0,
"attachment_count": 2,
"attachment_pages": 20,
"unit_price_krw": 2,
"amount_krw": 42,
"billing_items": [
{"model_id": "crawl", "units": 1, "unit_price_krw": 2, "amount_krw": 2},
{"model_id": "doc", "units": 20, "unit_price_krw": 2, "amount_krw": 40}
],
"quota_remaining": 96
}
}
```
위 예시 청구: 본문 1페이지 × 2원 + 첨부 20페이지 × 2원 = **42원** (`meta.amount_krw`).
**예시 — 여러 URL + 개인정보 자동 마스킹**:
```bash
curl -X POST https://api.corepin.ai/v1/crawl/scrape \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://a.example.go.kr/1", "https://b.example.go.kr/2"],
"pii_mask": true,
"pii_categories": ["kr_rrn", "private_phone"]
}'
```
**`meta` 필드 설명**:
| 필드 | 설명 |
|---|---|
| `model_id` | 항상 `crawl`. |
| `model_version` | 모델 버전 (예: `crawl-2026.06`). |
| `processing_time_ms` | 서버 처리 시간 (ms). |
| `request_id` | 요청 추적 ID. |
| `url_count` | 요청한 URL 수 (중복 제거 후). |
| `success_count` | 본문 추출 성공 페이지 수 — **본체 청구 단위** (페이지당 2원). |
| `rendered_count` | 그 중 JS 렌더로 가져온 페이지 수 (렌더 페이지당 5원). 나머지는 정적 단가. |
| `attachment_count` | 파싱 성공한 첨부 수. |
| `attachment_pages` | 첨부 파싱 총 페이지 수 — **첨부 청구 단위** (doc 단가 페이지당 +2원). |
| `unit_price_krw` | 본체 정적 단가 (2원). |
| `amount_krw` | **이 요청으로 청구된 총액(원)** — 본문(정적 2원·렌더 5원) + 첨부·OCR + 안전 검사(+α)를 합산해요. 청구서와 같은 단가(고객별 단가 포함)이고 실패한 URL 은 들어가지 않아요. 호출하는 쪽의 과금을 코어핀 청구와 대조할 때 쓰세요. |
| `billing_items` | `amount_krw` 의 내역 — 항목마다 `{model_id, units, unit_price_krw, amount_krw}`. `model_id`: `crawl`(정적 본문)·`crawl_render`(렌더 본문)·`doc`(첨부 페이지)·`ocr`·`pii`·`dlp`·`moderation`·`mental_health`. 단위가 0 인 항목은 빠져요. |
| `quota_remaining` | 잔여 호출 수 (후불 계정은 큰 정수). |
페이지 단위 실패 시 그 URL만 `{"url": "...", "error": {...}}`로 표시되고, 나머지는 정상 결과를 받아요. 첨부 1건이 실패해도 그 첨부만 `error`로 표시되고 본문·형제 첨부는 정상 반환돼요.
## 3. POST `/v1/crawl/map`
풀크롤 전에 **무엇이 긁힐지 미리 확인**하는 사이트 URL 발견 엔드포인트예요. robots.txt 의 `Sitemap` 지시문 + 표준 위치(`/sitemap.xml`)를 먼저 보고, 부족하면 시작 페이지의 1-hop 동일 사이트 링크를 모아요. 같은 등록 도메인만(co.kr·go.kr·or.kr 등 2단 SLD 고려), 중복 제거.
요청은 `application/json` 입니다.
**Request 필드**:
| 필드 | 타입 | 기본 | 설명 |
|---|---|---|---|
| `url` | string | (필수) | 사이트 시작 URL (http/https). |
| `max_urls` | int | `200` | 발견할 URL 최대 개수 (1~2,000). |
| `include_subdomains` | bool | `true` | 같은 등록 도메인의 서브도메인 포함. `false` 면 정확히 같은 호스트만. |
| `respect_robots` | bool | `true` | robots.txt Disallow 경로는 결과에서 제외. |
**Response**:
```json
{
"urls": [
"https://www.example.go.kr/notice/12345",
"https://www.example.go.kr/notice/12346",
"https://www.example.go.kr/board/list"
],
"meta": {
"model_id": "crawl",
"model_version": "crawl-2026.06",
"processing_time_ms": 612.4,
"request_id": "def...",
"site": "https://www.example.go.kr",
"url_count": 3,
"source": "sitemap+links",
"robots_blocked": 2,
"unit_price_krw": 2,
"quota_remaining": 95
}
}
```
`meta` 필드: `site`(발견 기준 origin) · `url_count`(반환 URL 수) · `source`(발견 출처 — `sitemap` / `links` / `sitemap+links` / `none`) · `robots_blocked`(`respect_robots=true`로 제외된 URL 수).
```bash
curl -X POST https://api.corepin.ai/v1/crawl/map \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.example.go.kr", "max_urls": 500}'
```
청구: 사이트(잡) 1건당 2원 (반환 URL 수와 무관).
## 4. POST `/v1/crawl/crawl` — 풀사이트 자동 크롤 (비동기 잡)
`scrape` 가 URL 을 하나씩 준다면, `crawl` 은 **시작 URL 하나**만 주면 같은 사이트의 링크를 깊이(`max_depth`)·개수(`max_pages`) 한도 안에서 따라가며 여러 페이지를 자동으로 긁어요. 시간이 걸리는 작업이라 **비동기 잡**으로 돌아가요 — 제출하면 즉시 `job_id` 가 돌아오고, 실제 크롤은 백그라운드에서 진행돼요.
- 같은 등록 도메인만 따라가요(`include_subdomains` 로 서브도메인 포함 여부 선택). `include_paths`/`exclude_paths` 로 경로를 제한할 수 있어요.
- robots.txt 준수(`respect_robots`, 기본 ON)·도메인 예의(politeness, `crawl_delay_s`)·본문 중복 제거(`dedup_content`)가 기본 적용돼요.
- 페이지가 아닌 **리소스 주소는 따라가지 않아요** — 스타일시트·스크립트·이미지·폰트·음원·영상·압축 파일(`.css`·`.js`·`.png`·`.woff2`·`.mp4`·`.zip` 등)과 `<script>`·`<img>`·`<link rel="stylesheet">` 가 가리키는 주소는 `max_pages` 칸을 쓰지 않아요. PDF·HWP·Office 같은 첨부는 `parse_attachments` 로 따로 다뤄요. `/v1/crawl/map` 도 같은 기준이에요.
- 본문 추출 성공 페이지 1건당 **2원**(동기 `scrape` 와 같은 단가). 첨부·안전 검사(+α) 단가도 동일해요. **취소·실패한 페이지는 과금되지 않아요.**
- 폴링 권장 간격 5초. 잡 결과는 24시간 보관돼요.
요청은 `application/json` 입니다. `scrape` 의 모든 옵션(`output_format`·`parse_attachments`·`pii_mask`·`dlp_grade`·`moderation_check`·`mental_health_check` 및 화이트리스트)에 더해 아래 필드를 씁니다.
| 필드 | 타입 | 기본 | 설명 |
|---|---|---|---|
| `url` | string | (필수) | 크롤을 시작할 사이트 URL (http/https). |
| `max_pages` | int | `100` | 크롤할 페이지 최대 개수 (1~2,000, 비용·부하 방어). |
| `max_depth` | int | `3` | 시작 페이지에서 따라갈 링크 깊이 (0~10). 시작 페이지가 깊이 0이고, 거기서 링크 한 번 거치면 1이에요. **0이면 링크를 따라가지 않고 시작 페이지만** 긁어요. `paginate` 로 넣은 목록 페이지도 모두 깊이 0이에요(§4.1). |
| `include_paths` | string[] | `null` | 이 경로 패턴에 맞는 URL만 크롤 (예: `["/board", "/notice/*"]`). 비우면 전체. 패턴에 `?`·`=`·`&` 가 있으면 **쿼리까지** 비교해요(예: `"*tpmdGrpCd=TKBBS02*"`). 자세한 규칙은 §4.2. |
| `exclude_paths` | string[] | `null` | 이 경로 패턴은 제외 (예: `["/login", "*.zip"]`). 쿼리 비교 규칙은 `include_paths` 와 같아요(§4.2). |
| `include_subdomains` | bool | `true` | 같은 등록 도메인의 서브도메인도 따라갈지. |
| `respect_robots` | bool | `true` | robots.txt Disallow 경로를 건너뛰고 Crawl-delay 를 지켜요. |
| `crawl_delay_s` | float | `0.5` | 같은 도메인 연속 요청 사이 최소 지연(초, 0~10). robots Crawl-delay 가 더 크면 그쪽을 따라요. |
| `dedup_content` | bool | `true` | 본문이 같은 중복 페이지를 결과·과금에서 제외. |
| `link_rules` | object[] | `null` | **옛날 게시판 대응** — 자바스크립트 링크에서 실제 주소를 만드는 규칙. 아래 §4.1. |
| `paginate` | object | `null` | **목록 페이지 자동 넘김** — `?page=1..N` 시드 주입. 아래 §4.1. |
| `credential_ref` | string | `null` | **인증 크롤** — 로그인이 필요한 게시판을 긁을 때, 미리 등록한 전용 계정의 참조 ID. 아래 §5. |
| `webhook_url` | string | `null` | 잡 완료/실패 시 알림 받을 URL (POST, 메타만 — PII 안전). |
**제출 예시**:
```bash
curl -X POST https://api.corepin.ai/v1/crawl/crawl \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.example.go.kr/board/list",
"max_pages": 100,
"max_depth": 2,
"include_paths": ["/board"],
"output_format": "markdown"
}'
```
```json
{
"job_id": "a1b2c3...",
"status": "queued",
"start_url": "https://www.example.go.kr/board/list",
"status_url": "/v1/crawl/crawl/a1b2c3...",
"meta": { "model_id": "crawl", "model_version": "crawl-2026.07", "unit_price_krw": 2, "max_pages": 100 }
}
```
접수 때 `include_paths`/`exclude_paths` 에 **어떤 주소와도 맞을 수 없는 모양**(주소 전체·`#` 조각·정규식, §4.2)이 있으면 잡은 그대로 접수하되 응답에 `warnings` 배열이 붙어요. 같은 문구가 상태 조회 응답에도 남아요.
**상태·결과 조회** — `GET /v1/crawl/crawl/{job_id}`:
```bash
curl https://api.corepin.ai/v1/crawl/crawl/a1b2c3... \
-H "Authorization: Bearer $COREPIN_API_KEY"
```
`status` 는 `queued`→`processing`→`done`(또는 `failed`/`cancelled`). `done` 이면 `results[]` 에 페이지별 결과(§2 의 페이지 항목과 같은 형태)가 담기고, `pages_crawled`·`success_count`·`attachment_pages`·`stopped_reason`·`amount_krw` 요약이 함께 와요. `amount_krw` 는 이 잡으로 **청구된 총액**(본문 정적·렌더 + 첨부·OCR + 안전 검사 +α)이에요 — 동기 `scrape` 의 `meta.amount_krw` 와 같은 계산입니다. `GET /v1/crawl/crawl`(목록)·`DELETE /v1/crawl/crawl/{job_id}`(취소, 이미 긁은 페이지만 과금)도 있어요.
**`done` 인데 결과가 비었을 때** — 잡은 「끝까지 돌았다」는 뜻으로 `done` 이 되고, 한 페이지도 못 얻었다면 이유가 함께 와요.
| `stopped_reason` | 의미 |
|---|---|
| `completed` | 큐를 다 비우고 끝났어요. |
| `max_pages` | `max_pages` 한도에서 멈췄어요. |
| `cancelled` | 취소 요청으로 멈췄어요. |
| `robots_blocked` | 시작 주소부터 robots.txt Disallow 에 걸려 한 페이지도 가져오지 못했어요. 사이트 소유자가 직접 의뢰한 크롤이면 `respect_robots=false` 로 다시 제출하세요. |
| `all_pages_failed` | 가져오려 했지만 본문 성공이 0건이에요. `results[]` 각 항목의 `error` 를 보세요. |
- 페이지가 실패하면 `results[]` 의 그 항목에 `error: {code, message}` 가 담기고, 가능하면 `hint`(대처 안내)·`detail`(서버 내부 오류일 때 예외 종류)도 와요. 이 항목은 청구되지 않아요.
- robots.txt 로 막힌 주소도 `error.code = "blocked_by_robots"` 항목으로 남아요(최대 100개까지 목록, 그 이상은 개수만).
- 상태 조회 응답에는 오류가 있을 때 요약 `failed_pages`(오류 페이지 수)·`error_codes`(코드별 개수, 예 `{"fetch_failed": 3}`)가 붙어요.
### 4.1 옛날(자바스크립트) 게시판 — `link_rules` · `paginate`
옛날 전자정부 게시판(예: 국립공원공단 '고객의 소리')은 글 링크·페이지 이동을 `<a href="javascript:goDetail('TKPMDM045330')">`·`goPage(N)` 폼 submit 같은 **자바스크립트**로 처리해요. 정적 크롤러는 `javascript:` 링크를 못 따라가서 상세 글·다음 페이지로 못 넘어가요. 특정 사이트를 코드에 박지 않고, 작업 설정으로 규칙을 주입해 뚫어요.
- **`link_rules`** — `[{"call": <자바스크립트 함수 이름>, "arg": <몇 번째 인자·기본 1>, "template": <\1 자리에 뽑은 값을 끼울 URL>}]` (최대 5개). 페이지에서 그 함수 호출을 찾아 인자를 뽑고 템플릿에 끼워 실제 주소를 만들어요.
- 정규식은 받지 않아요 — 남이 만든 페이지에서 처리가 폭주해 크롤이 멈출 수 있어서, 패턴은 서버가 만듭니다. 함수 이름만 적으면 따옴표 유무·공백은 알아서 맞춰요.
- **`paginate`** — `{"param": "page", "start": 1, "max": N}`. 목록을 `?page=1..N` 로 자동으로 넘기며 훑어요(자바스크립트 페이징 우회).
- `paginate` 로 넣은 목록 페이지(`?page=1..N`)는 시작 페이지와 같은 **깊이 0** 이에요. 목록에서 링크로 이어진 글 본문은 깊이 1이라, 글까지 받으려면 `max_depth` 를 1 이상으로 주세요. `max_depth: 0` 이면 목록 페이지만 모으고 글은 따라가지 않아요.
- 목록 페이지와 글 모두 `max_pages` 칸을 쓰고 페이지당 단가로 청구돼요. 회원가입·메인처럼 범위 밖 페이지가 칸을 차지하지 않게 하려면 `include_paths`/`exclude_paths` 로도 좁혀 주세요.
```bash
curl -X POST https://api.corepin.ai/v1/crawl/crawl \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.example.go.kr/board/openList.do?menuNo=7020036",
"max_depth": 1,
"respect_robots": false,
"crawl_delay_s": 1.0,
"paginate": {"param": "page", "start": 1, "max": 60},
"link_rules": [{
"call": "goDetail",
"template": "/board/openDtl.do?menuNo=7020036&tpmdId=\\1"
}]
}'
```
> `respect_robots=false` 는 사이트 소유자가 자기 사이트 모니터링을 직접 의뢰하는 등 **크롤 권한이 있을 때만** 쓰세요. 크롤 감사로그는 항상 남아요.
### 4.2 경로 패턴 — `include_paths` · `exclude_paths`
링크를 따라갈 때마다 그 주소를 패턴과 견줘서, `include_paths` 가 있으면 하나라도 맞는 주소만, `exclude_paths` 에 맞는 주소는 건너뛰어요. **시작 URL 과 `paginate` 로 넣은 목록 페이지는 패턴 검사를 받지 않아요.** 정규식이 아니고, 와일드카드는 `*`(0글자 이상) 하나예요.
| 패턴 모양 | 비교 대상 | 일치 방식 |
|---|---|---|
| `/` 로 시작, 쿼리 문자 없음 (`/board`, `/notice/*`) | 경로(쿼리 제외) | 앞에서부터 일치 — `/board` 는 `/board/view` 에도 맞아요. |
| `/` 로 시작하지 않음 (`login`, `*.zip`) | 경로(쿼리 제외) | 경로 어디든 일치. |
| `?`·`=`·`&` 가 하나라도 있음 (`*tpmdGrpCd=TKBBS02*`, `/bbs/list.do?code=A*`) | `경로?쿼리` 전체 — 링크에 적힌 그대로 | `/` 로 시작하면 앞에서부터, 아니면 어디든 일치. 패턴의 `?` 는 글자 그대로예요. |
게시판을 경로가 아니라 **쿼리로 가르는 사이트**(예: `openList.do?tpmdGrpCd=TKBBS02`)에서는 쿼리 패턴으로 범위를 좁혀요. 쿼리 매개변수의 순서는 사이트가 만든 링크 그대로 비교하니, 순서에 흔들리지 않게 앞뒤에 `*` 를 붙인 `*tpmdGrpCd=TKBBS02*` 꼴을 권해요.
```json
{
"url": "https://www.example.go.kr/board/openList.do?tpmdGrpCd=TKBBS02",
"max_depth": 1,
"paginate": {"param": "page", "start": 1, "max": 10},
"link_rules": [{"call": "goDetail", "template": "/board/openDtl.do?menuNo=7020036&tpmdId=\\1"}],
"include_paths": ["/board/openDtl.do", "*tpmdGrpCd=TKBBS02*"]
}
```
위는 「고객의 소리(TKBBS02) 목록 + 상세 글」만 따라가고 다른 게시판(`TKBBS04`·`TKBBS05`)은 건너뛰어요. 다른 게시판만 빼고 싶으면 `"exclude_paths": ["*tpmdGrpCd=TKBBS04*", "*tpmdGrpCd=TKBBS05*"]` 로도 돼요.
**무엇과도 맞을 수 없는 패턴** — 비교 대상은 `경로` 또는 `경로?쿼리` 뿐이라 다음은 항상 빗나가요. 접수는 되지만 응답 `warnings` 로 알려 드려요: ① 주소 전체(`https://…`) — 호스트는 비교하지 않으니 경로로 적어 주세요, ② `#` 조각 — 링크를 따라갈 때 이미 떼어내요, ③ 정규식(`^…`·`…$`·`\`·`|`·`(?`).
## 5. 인증 크롤 (로그인 게시판·비공개 글) — `/v1/crawl/credentials`
로그인해야 보이는 게시판·비밀글은 **전용 계정으로 로그인**해서 수집해요. 계정 정보는 서버에서 **암호화(AES-256-GCM)** 되어 보관되고, 로그인하는 그 순간에만 복호화해 메모리에서만 쓰며 로그에는 남기지 않아요. 크롤 요청엔 계정 정보 대신 **참조 ID(`credential_ref`)** 만 담겨요.
**보안 원칙** — ① 직원 개인 계정이 아닌 **모니터링 전용 계정**으로 받아 권한을 최소화하고, ② 계정 정보는 암호화 보관, ③ 계약 종료 시 `DELETE` 로 폐기, ④ 계정이 밖으로 나가는 것 자체가 곤란하면 **사내망 설치**(같은 API·같은 출력)로 계정이 고객사 안에 머물게 할 수 있어요.
### 5.1 자격증명 등록 — `POST /v1/crawl/credentials`
| 필드 | 타입 | 설명 |
|---|---|---|
| `label` | string | 사람이 알아볼 이름 (예: `"○○ 게시판 모니터링"`). |
| `login_config` | object | 로그인 폼 설정(**비밀 제외**). 아래 표. |
| `username` | string | 로그인 아이디 (암호화 보관). |
| `password` | string | 로그인 비밀번호 (암호화 보관 — 응답·로그에 절대 노출 안 됨). |
`login_config` 필드 (특정 사이트 하드코딩 없이 폼 구조만 주입):
| 키 | 필수 | 설명 |
|---|:-:|---|
| `login_url` | ✓ | 로그인 폼 페이지 URL (GET 해서 세션·CSRF 토큰 확보). |
| `id_field` | ✓ | 아이디 입력 필드 이름 (예: `"id"`). |
| `password_field` | ✓ | 비밀번호 입력 필드 이름 (예: `"password"`). |
| `action_url` | | 로그인 폼 제출(POST) 대상. 비우면 `login_url`. |
| `method` | | `POST`(기본) / `GET`. |
| `extra_fields` | | 함께 보낼 정적 필드 (예: `{"loginType": "I"}`). |
| `include_hidden_fields` | | `true` 면 폼의 hidden input(CSRF 토큰 등)을 자동으로 긁어 함께 제출. |
| `success_contains` | | 로그인 성공 시 나타나는 텍스트 마커 (문자열 또는 배열). 예: `"로그아웃"`. |
| `failure_contains` | | 로그인 실패/로그인 벽 텍스트 마커 (문자열 또는 배열). |
| `verify_url` | | 로그인 후 보호 페이지를 한 번 더 열어 세션 유지를 확인할 URL. |
```bash
curl -X POST https://api.corepin.ai/v1/crawl/credentials \
-H "Authorization: Bearer $COREPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "○○ 게시판 모니터링",
"login_config": {
"login_url": "https://www.example.go.kr/member/login.do",
"action_url": "https://www.example.go.kr/member/loginAction.do",
"id_field": "id",
"password_field": "password",
"extra_fields": {"loginType": "I"},
"include_hidden_fields": true,
"success_contains": "로그아웃",
"failure_contains": ["일치하지", "로그인이 필요"]
},
"username": "monitor_account",
"password": "••••••••"
}'
```
```json
{ "credential_ref": "e4f5a6...", "label": "○○ 게시판 모니터링" }
```
돌려받은 `credential_ref` 를 `/v1/crawl/crawl` 요청의 `credential_ref` 에 넣으면, 워커가 로그인한 상태로 사이트를 크롤해요.
### 5.2 목록·폐기
- `GET /v1/crawl/credentials` — 내 조직이 등록한 자격증명 목록(참조 ID·이름·등록일만, **비밀·설정 상세 제외**).
- `DELETE /v1/crawl/credentials/{credential_ref}` — 폐기(soft delete). 계약 종료·계정 회수 시 사용.
> **자동 로그인 난이도.** 평범한 아이디/비번 폼은 위 설정만으로 그대로 됩니다. 숨은 CSRF 토큰·hidden 필드는 `include_hidden_fields` 로 처리돼요. 다만 **캡차·문자(SMS) OTP·공동인증서·외부 SSO** 가 걸린 로그인은 자동 로그인이 불가능해요 — 이 경우 사람이 1회 로그인한 세션을 인계하는 방식이 필요하니 문의해 주세요.
## 6. GET `/v1/crawl/version`
```bash
curl https://api.corepin.ai/v1/crawl/version \
-H "Authorization: Bearer $COREPIN_API_KEY"
```
모델 ID·버전·이름·릴리스 노트·엔드포인트 목록 반환.
## 7. GET `/v1/crawl/formats`
```bash
curl https://api.corepin.ai/v1/crawl/formats \
-H "Authorization: Bearer $COREPIN_API_KEY"
```
기능 목록 + 일반 크롤러 대비 비교표 + **첨부 자동 파싱 지원 형식**(`hwp`·`hwpx`·`hwpml`·`pdf`·`doc`·`docx`·`xls`·`xlsx`·`pptx`·`csv`·`rtf`) + 출력 형식 + +α 옵션 + 한 호출 최대 URL 수(20) + 페이지당 최대 첨부 수(50) 반환.
## 8. 한국 특화 기능
### 8.1 첨부 자동 파싱
페이지에 `.hwp`·`.pdf`·`.docx`·`.xlsx` 같은 첨부가 걸려 있을 경우 함께 처리해요. `parse_attachments=true` 면 페이지 HTML 의 `href`/`src` 에서 첨부 링크를 발견하고, 다운로드 엔드포인트가 확장자를 URL 에 안 박는 공공 사이트 패턴(`/cmm/fms/FileDown.do?atchFileId=...&fileName=보고서.hwp`)까지 파일명 힌트 파라미터로 알아채요. 발견한 첨부는 한국형 통합 문서 파서에 넘겨 본문과 함께 마크다운/텍스트/JSON 으로 돌려줘요 — 일반 크롤러가 못 푸는 한국 HWP 도 정확히 추출합니다.
- 로그인 벽·오류 페이지가 HTTP 200 + HTML 로 오는 경우(공공 사이트 흔함), 바이너리 첨부인데 본문이 HTML 이면 진짜 파일이 아니라고 판단해 `attachment_not_a_file` 로 깨끗이 표시해요(엉뚱한 파싱 방지).
- 첨부 1건 최대 30MB. 페이지당 최대 첨부 수는 `max_attachments`(기본 10, 상한 50).
- **같은 파일은 한 번만 파싱해요.** 같은 문서가 미러·아카이브 주소로 여러 번 걸려 있으면(예: `hdr.undp.org/…/report.pdf` 와 `web.archive.org/web/…/report.pdf`) URL 은 달라도 파일명이 같으므로 한 번만 받아요. 파일명을 알 수 없는 주소는 이 중복 제거를 하지 않아요(다른 파일을 잘못 합치지 않게).
- **비용 폭증 방어** — 첨부는 페이지당 +2원이라 큰 PDF 하나가 수백 원이 될 수 있어요. `max_attachment_pages`(기본 **100쪽**)로 첨부 1건의 쪽수 상한을 두고, 넘친 첨부는 파싱 전에 거부해 **청구하지 않아요**(`attachment_page_budget_exceeded`). 계정 설정에서 20·50·100·200·500쪽 또는 「제한 없음」으로 바꿀 수 있어요. 아예 `parse_attachments` 를 끄는 것이 가장 확실한 방법입니다.
### 8.2 EUC-KR/CP949 레거시 인코딩 복원
EUC-KR/CP949 로 서빙하는 정부·기관 사이트의 깨진 글자(mojibake)를 자동 감지·복원해요. 문서 파서의 인코딩 자동감지 철학을 웹에도 적용한 거예요 — 별도 옵션 없이 항상 동작합니다.
### 8.3 크롤 + 안전 검사 (사내 적재 전)
외부에서 긁은 콘텐츠를 사내 RAG 에 넣기 전, 같은 호출에서 개인정보 자동 마스킹(`pii_mask`)·기밀 등급 분류(`dlp_grade`)·유해발화 감지(`moderation_check`)·정신건강 위험 신호 감지(`mental_health_check`)를 묶어 처리할 수 있어요. 긁은 데이터도 개인정보 보호 검사를 거쳐 들어가요. 본문과 첨부 양쪽에 동일하게 적용됩니다.
### 8.4 robots.txt 준수 + 감사로그
`respect_robots=true`(scrape)면 Disallow 경로는 `403 blocked_by_robots` 로 거부해요. `/map` 은 기본으로 robots 를 존중해 Disallow URL 을 결과에서 제외하고 `robots_blocked` 수를 알려줘요. 어떤 키가 어떤 도메인을 언제 크롤했는지 감사로그로 남아 한국 법무팀 친화적입니다.
### 8.5 SSRF 차단
`http`/`https` 만 허용하고, 내부망·로컬·메타데이터 주소(`127.0.0.1`·`10.x`·`192.168.x`·`169.254.169.254` 등)는 차단합니다. 호스트명은 DNS resolve 후 모든 IP 를 검사하고, 리다이렉트도 매 홉마다 같은 기준으로 다시 검사해요. 페이지 최대 10MB, 첨부 최대 30MB.
## 9. 단가 정리
청구는 **성공한 페이지만**이에요. 접속 불가·본문 없음·robots 차단 등으로 실패한 URL 은 청구되지 않아요 — 요청 시 한도(RPM·월)만 검사하고, 월 카운터·사용 원장에는 성공분만 반영합니다.
| 항목 | 단가 |
|---|---:|
| 본문 스크랩 (`/v1/crawl/scrape`) | 2원 / 성공 페이지 |
| 사이트 URL 발견 (`/v1/crawl/map`) | 2원 / 사이트 (반환 URL 수 무관) |
| 풀사이트 자동 크롤 (`/v1/crawl/crawl`) | 2원 / 성공 페이지 (`scrape` 와 동일) |
| ↳ 옛날 게시판 대응 (`link_rules`·`paginate`) | 추가 요금 없음 (본문 페이지 단가만) |
| ↳ 인증 크롤 (`credential_ref` 로그인) | 추가 요금 없음 (본문 페이지 단가만) |
| 자격증명 등록·보관 (`/v1/crawl/credentials`) | 무료 |
| 첨부 파싱 (HWP·PDF·DOCX·XLSX 등) | +2원 / 첨부 문서 페이지 (`doc` 단가) |
| 첨부 스캔 자동 OCR | 2원 / OCR한 페이지 (`ocr` 단가, 그 페이지만 추가) |
| 옵션 `pii_mask` | + 5원 / 건 (적용된 본문·첨부마다) |
| 옵션 `dlp_grade` | + 20원 / 건 |
| 옵션 `moderation_check` | + 5원 / 건 |
| 옵션 `mental_health_check` | + 10원 / 건 |
| JS 렌더 (`render=true`, Playwright · 옵트인) | 5원 / 렌더 성공 페이지 (`crawl_render`) · 렌더 불가/실패 시 정적 추출로 자동 폴백(₩2) |
> 월 구독제(안 써도 청구) 대비, Corepin Crawl 은 종량 ₩2/페이지·성공분만·선불/후불이에요. trafilatura 자체 내재화 + CPU-only 라 호출당 외부 비용이 없습니다.
>
> **옛날 게시판·로그인 크롤은 별도 요금이 없어요.** 자바스크립트 링크 대응(`link_rules`·`paginate`)이든 전용 계정 로그인(`credential_ref`)이든, 실제로 긁어 온 본문 페이지에만 ₩2 가 붙어요. 로그인·자격증명 보관 자체는 무료예요.
무료 한도 1,000회/월. 후불 정산은 매월 1일 09:00 KST. 본체·첨부·옵션 모두 같은 월 한도·청구를 공유해요(자세한 청구는 [빠른 시작 §5](/docs/quickstart)).
## 10. 오류 코드
페이지 단위 실패는 해당 URL 결과의 `error: {code, message}` 로 표시돼요(전체 호출은 200, 그 URL 만 청구 제외). `/map` 의 입력 검증 실패는 호출 전체가 해당 HTTP 코드로 떨어져요.
| HTTP | code | 의미 |
|---|---|---|
| 400 | `empty_url` | `url`·`urls` 둘 다 비어 있음 |
| 400 | `invalid_output_format` | `output_format`이 text/markdown/html/json이 아님 |
| 400 | `too_many_urls` | 한 호출 20개 URL 초과 |
| 400 | `unsupported_scheme` | http/https 외 스킴 |
| 400 | `invalid_url` | URL 에서 호스트를 찾을 수 없음 |
| 400 | `url_too_long` | URL 4096자 초과 |
| 400 | `blocked_private_address` | 내부망·로컬·메타데이터 주소 (SSRF 차단) |
| 403 | `blocked_by_robots` | `respect_robots=true` 인데 robots.txt Disallow 경로 |
| 413 | `page_too_large` | 페이지 본문 10MB 초과 |
| 413 | `attachment_too_large` | 첨부 30MB 초과 |
| 422 | `dns_resolution_failed` | 호스트 DNS 조회 실패 |
| 422 | `no_content` | 본문을 못 찾음 (로그인 필요·JS 렌더 페이지 등) |
| 422 | `extraction_failed` | 본문 추출 실패 |
| 422 | `attachment_not_a_file` | 첨부 링크가 실제 파일이 아니라 웹 페이지(로그인/오류 페이지) |
| 502 | `fetch_failed` | 페이지·첨부에 연결 실패 (4xx/5xx 응답 포함) |
| 502 | `bad_redirect` / `too_many_redirects` | 리다이렉트 위치 비었거나 너무 많음 |
| 502 | `crawl_failed` | 그 외 크롤 처리 실패. 풀사이트 크롤 결과에서는 `detail` 에 서버 내부 오류 종류가 함께 와요. |
| 502 | `attachment_parse_failed` | 첨부 파싱 실패 |
| 503 | `missing_dependency` | 본문 추출 엔진(trafilatura) 미설치 (운영 측 알림) |
| 504 | `fetch_timeout` | 페이지·첨부 응답 시간 초과 |
**풀사이트 크롤(`/v1/crawl/crawl`) 제출·인증 관련** — 잡 제출·자격증명 API 는 호출 전체가 해당 HTTP 코드로 떨어져요. 로그인 실패는 잡이 `failed` 상태로 끝나고 `error` 에 사유가 담겨요.
| HTTP | code | 의미 |
|---|---|---|
| 429 | `quota_active_jobs` | 이 API 키의 동시 진행 크롤 잡 한도 초과 (기존 잡 완료 후 재시도) |
| 503 | `queue_full` | 전체 크롤 잡 큐가 가득 참 (잠시 후 재시도) |
| 404 | `credential_not_found` | `credential_ref` 가 없거나 다른 조직 소유 |
| 400 | `invalid_login_config` | `login_config` 필수 필드 누락·형식 오류 |
| 400 | `secret_in_config` | 비밀(아이디/비번)이 `login_config` 에 잘못 들어옴 (→ `username`/`password` 로) |
| 400 | `invalid_secret` | `username`/`password` 누락 |
| 401 | `login_failed` | 로그인 실패 (아이디/비번 또는 폼 설정 확인) |
| 401 | `login_session_not_held` / `login_no_session_cookie` | 로그인은 됐지만 세션이 유지되지 않음 |
| 503 | `encryption_unavailable` | 서버에 자격증명 암호화 키 미설정 (운영 측 알림 — 평문 저장은 거부) |
공통 오류 응답·재시도 안내는 [빠른 시작 §6](/docs/quickstart) 참고. `X-Request-ID` 응답 헤더는 모든 응답에 포함돼요.
## 11. 일반 크롤러 대비 비교
| 항목 | 일반 크롤러 | Corepin Crawl |
|---|---|---|
| 본문 추출 | 보통 (markdown/json) | 강함 (trafilatura) |
| 한국 사이트 인코딩 | 약함 (EUC-KR 깨짐) | **자동 복원 (압도적)** |
| HWP/PDF 첨부 | 불가 | **자동 발견·파싱** |
| 개인정보·기밀 마스킹 | 없음 | **한 호출 통합 (개인정보 보호)** |
| 옛날·로그인 게시판 | 자바스크립트 링크·로그인 벽에 막힘 | **규칙 주입·전용 계정 로그인으로 통과** |
| 합법성 증빙 | 약함 | **robots 준수 + 감사로그** |
| 가격 | 월 구독 (안 써도 청구) | **종량 ₩2/페이지 (선불·후불)** |
문의: <support@corepin.ai>