{"openapi":"3.1.0","info":{"title":"CLIP API","version":"2026-10-13","description":"## 시작하기\n\nclip HTTP API 는 무역서류 점검, 문서 분류·추출, ESG 서류 점검, 건설안전보건 서류 점검, 보험 청구 판정을 고객 시스템에서 호출할 수 있도록 제공하는 API 입니다. 이 문서는 모든 기능에 공통으로 적용되는 규칙과 기능별 호출 방법을 설명합니다.\n\n모든 주소는 `{{BASE_URL}}` 을 기준으로 합니다. 실제 연동 시에는 담당자가 안내한 서버 주소로 바꿔 사용하십시오.\n\n- 모든 요청은 고객 서버에서 호출해 주십시오. 브라우저나 모바일 앱에서 직접 호출할 수 없습니다(CORS 미지원).\n- 인증이 필요한 모든 요청에는 `x-clip-key` 헤더로 API 키를 보내야 합니다. 인증 세부 사항은 다음 섹션을 참고하십시오.\n- 파일을 보내는 요청은 multipart/form-data 를, 그 밖의 요청은 application/json 을 사용합니다.\n- 문서 처리는 대부분 작업을 만든 뒤 작업 ID 로 결과를 조회하는 방식입니다. 자세한 내용은 비동기 작업 섹션을 참고하십시오.\n\n연동을 시작할 때는 아래 순서로 서버 상태를 먼저 확인하는 것을 권장합니다. 상태 확인 경로는 인증 없이 호출할 수 있습니다.\n\n```curl\n# 1. 서버 동작 확인 (인증 불필요)\ncurl -s {{BASE_URL}}/api/shipping/health\n\n# 2. 키 상태와 잔여 한도 확인\ncurl -s {{BASE_URL}}/api/shipping/dashboard -H \"x-clip-key: YOUR_API_KEY\"\n```\n\n첫 번째 요청은 `{\"status\":\"ok\"}` 응답이면 서버가 정상 동작하는 것입니다. 두 번째 요청에서 키가 유효하면 요금제, 사용 가능한 기능, 한도 정보가 응답으로 옵니다.\n\n## 인증\n\nAPI 키는 모든 인증 요청의 `x-clip-key` 헤더로 보냅니다. 키 값을 요청 본문이나 주소의 쿼리 문자열에 넣지 마십시오.\n\n```curl\ncurl -X POST {{BASE_URL}}/api/shipping/upload -H \"x-clip-key: YOUR_API_KEY\" -F \"file=@/path/to/document.pdf\"\n```\n\nAPI 키는 담당자에게 요청하여 발급받습니다. 키마다 사용할 수 있는 권한이 다르므로, 아래 표에서 연동할 기능에 필요한 권한을 확인하십시오.\n\n| 기능 | 필요한 권한 | 비고 |\n| --- | --- | --- |\n| 무역서류 파일 업로드·처리 | `files`, `ocr` | 업로드한 파일은 같은 키로만 처리할 수 있습니다 |\n| 무역서류 분류·추출 (`/api/shipping/classify`, `/api/shipping/extract`) | `ocr` | 문서 본문을 직접 보내는 방식입니다 |\n| 문서 분류·추출 (`/api/extract/upload`, `/api/extract/process`) | `files`, `ocr` |  |\n| 보험 청구 판정 | `files`, `ocr`, 문서 검색 | 키에 문서 검색 기능이 연결돼 있어야 합니다 |\n| ESG·건설 서류 점검 (저장 모드) | `files`, 문서 검색 | 기본 동작입니다 |\n| ESG·건설 서류 점검 (저장 없는 모드) | `ocr` | `noStore` 필드를 보낼 때입니다 (베타) |\n| ESG·건설 근거 페이지 이미지 | `dlp` | Office·HWP 문서의 근거 페이지를 이미지로 받을 때 필요합니다 |\n| 키 상태·잔여 한도 확인 | 키 활성·만료 상태 | `GET {{BASE_URL}}/api/shipping/dashboard` |\n\n키에는 추가 기능(분류, 키 추출, 개인정보 탐지) 사용 여부가 설정되어 있을 수 있습니다. 설정이 꺼진 기능을 요청하면 403 이 옵니다.\n\n인증 실패는 다음과 같이 구분됩니다.\n\n- 키 헤더가 없거나 비어 있으면 401 이 오며, 본문의 `code` 값이 `api_key_required` 입니다. 배포 설정에 따라 키 헤더 없이도 요청이 처리될 수 있으나, 연동은 항상 키를 보내는 것을 전제로 하십시오.\n- 키가 잘못되었거나 비활성·만료되었거나 권한이 없으면 401 또는 403 이 옵니다. 기능 API 응답의 `error` 문자열로 세부 사유를 확인할 수 있습니다. 다만 키 상태 확인 API(`GET /api/shipping/dashboard`)는 업스트림 사유를 감추고 `{\"error\":\"Upstream error 401\"}` 처럼 상태 코드만 돌려주므로 401 과 403 을 구분할 수 없습니다. 이 API 의 502 본문은 `Failed to fetch: ...` 형식입니다.\n- 분당 또는 일일 사용 한도를 넘으면 429 가 옵니다.\n\n```json\n{\"error\":\"우측 상단에 API Key 를 입력해 주세요.\",\"code\":\"api_key_required\"}\n```\n\n**API 키를 브라우저, 모바일 앱, 공개 저장소 등 클라이언트 쪽 코드에 넣지 마십시오.** 키가 노출되었다고 판단되면 즉시 담당자에게 폐기와 재발급을 요청하십시오.\n\n## 요청 형식\n\n파일을 보내는 API 는 multipart/form-data 형식을 사용하며 파일 필드 이름은 `file` 입니다. 보험 청구 판정은 `files` 필드를 파일마다 반복해서 보내고, `docTypes` 필드를 같은 순서로 반복해서 보내며, `coverages` 는 쉼표로 이은 하나의 문자열로 보냅니다.\n\n파일이 없는 POST 요청(예: ESG 판정 설정 저장)은 application/json 본문을 사용합니다. `Content-Type` 헤더를 `application/json` 으로 지정하고 JSON 본문을 보내십시오. 작업 조회는 GET 요청이며 본문이 없습니다.\n\n```curl\ncurl -X POST {{BASE_URL}}/api/shipping/upload -H \"x-clip-key: YOUR_API_KEY\" -F \"file=@/path/to/document.pdf\"\n```\n\n기능별로 받는 파일 형식이 다릅니다. 아래 목록은 권장 형식이며, 목록에 없는 형식을 보내면 처리가 실패하거나 결과 품질이 떨어질 수 있습니다.\n\n| 기능 | 지원 파일 형식 |\n| --- | --- |\n| ESG·건설 서류 점검 | PDF, PNG, JPG(JPEG), TIF(TIFF), WEBP, XLSX, XLS, DOCX, DOC, PPTX, PPT, HWP, HWPX |\n| 무역서류 점검, 문서 분류·추출 | PDF, PNG, JPG(JPEG), TIF(TIFF), WEBP |\n| 보험 청구 판정 | PDF, JPG(JPEG), PNG, TIF(TIFF) |\n\n파일 크기와 페이지 수 상한은 계정 설정에 따릅니다. 상한을 넘는 파일은 413 으로 거절됩니다. 정확한 상한 값은 담당자에게 확인하십시오. 이미지 해상도는 기본적으로 자동 축소되어 처리됩니다. 파일 크기, 페이지 수, 픽셀 상한을 넘으면 413 으로 거절됩니다.\n\n파일 업로드 요청을 보낼 때는 `Content-Type` 을 직접 지정하지 마십시오. HTTP 클라이언트가 multipart 경계 값을 포함한 헤더를 자동으로 만들도록 두어야 합니다.\n\n## 비동기 작업(작업 ID 폴링)\n\n문서 처리는 시간이 오래 걸리므로 여러 기능이 같은 방식의 비동기 작업으로 동작합니다. 기본 흐름은 다음과 같습니다.\n\n1. 파일을 업로드 API 로 보내면 `fileId` 를 응답으로 받습니다. 업로드 응답의 HTTP 상태는 200 입니다.\n2. 처리 API 를 POST 로 호출하면 `{\"jobId\": \"...\"}` 형태의 응답을 받습니다. 이 값이 작업 ID 입니다.\n3. 같은 처리 API 주소에 `?job=작업ID` 를 붙여 GET 으로 호출합니다. 3초 간격으로 조회하십시오.\n4. 응답의 `status` 가 `pending` 이면 다시 조회하고, `done` 또는 `error` 이면 조회를 멈춥니다.\n\n처리 API 의 요청 본문과 페이지 단위 호출 방식은 기능별 문서를 따르십시오.\n\n| status 값 | 의미 | 처리 방법 |\n| --- | --- | --- |\n| `pending` | 처리 중입니다. 보험 청구 판정은 `note` 필드가 함께 올 수 있습니다. | 3초 뒤에 다시 조회합니다. |\n| `done` | 처리가 끝났습니다. | 결과는 `result` 또는 `results` 필드에 있습니다. 필드 이름은 기능에 따라 다릅니다. |\n| `error` | 처리에 실패했습니다. | 본문의 `status` 는 항상 문자열 `\"error\"` 이고, `error` 필드에 사유가 옵니다. 실패 여부는 조회 응답의 HTTP 상태 코드로 판단하십시오. 처리·검토 API 는 업스트림 상태 코드를 그대로 쓰며(알 수 없으면 502), 조항·정정 점검과 보험 판정은 항상 502 입니다. |\n| `unknown` (HTTP 404) | 작업이 없거나 만료되었거나 서버 재시작으로 사라졌습니다. | 작업을 처음부터 다시 만들어야 합니다. |\n\n**`done` 과 `error` 응답은 한 번 읽으면 서버에서 삭제됩니다.** 응답을 받은 즉시 저장하십시오. 같은 작업을 다시 조회하면 404 `unknown` 이 옵니다. 응답이 유실되었다면 같은 작업을 재조회하지 말고 작업을 처음부터 다시 만들지 여부를 판단하십시오.\n\n**모니터링이나 상태 점검에 HEAD 요청을 사용하지 마십시오.** HEAD 요청도 같은 조회 처리로 들어가 결과를 소비하므로, 이후 GET 조회는 404 `unknown` 이 될 수 있습니다.\n\n- 작업 정보는 서버 메모리에만 있으며, 마지막 상태 변경 후 1시간이 지나면 만료됩니다.\n- 서버가 재시작되면 진행 중이던 작업은 모두 사라지며 조회 시 404 `unknown` 이 옵니다.\n- 여러 대의 서버를 앞단 로드밸런서로 묶은 환경에서는 작업을 만든 서버와 다른 서버로 조회될 수 있습니다. 이 경우에도 404 `unknown` 이 옵니다.\n\n작업 ID 는 결과를 조회하는 값이므로 외부에 노출하지 마십시오.\n\n작업 생성 요청(POST)이 200 을 돌려주었다고 해서 키가 유효하다는 뜻은 아닙니다. 키가 무효하거나 권한·한도가 부족하면 작업은 만들어지고, 실패 사유는 조회 단계에서 401, 403, 413, 429 등으로 나타납니다. 연동 전에 `GET {{BASE_URL}}/api/shipping/dashboard` 로 키 상태를 먼저 확인하십시오.\n\n작업 생성 요청을 다시 보내면 새 작업이 만들어져 처리 횟수가 중복으로 소모됩니다. 재시도 규칙은 처리 시간·재시도 섹션을 따르십시오.\n\n## 오류 응답\n\n오류 응답의 본문 형식은 API 마다 다릅니다. 공통된 형식이 없으므로, 먼저 HTTP 상태 코드로 분류하고 본문에서 `code` 또는 `status` 값, 그리고 `error` 문자열을 확인하십시오. 거절한 상위 시스템의 세부 필드(부족한 권한 이름, 허용량 등)는 전달되지 않습니다.\n\n| 형식 | 본문 예 | 해당 API |\n| --- | --- | --- |\n| 키 누락 | `{\"error\":\"...\",\"code\":\"api_key_required\"}` | 인증이 필요한 모든 API |\n| 입력 오류 (400) | `{\"error\":\"...\"}` | 필드가 없거나 형식이 맞지 않을 때입니다. 메시지는 엔드포인트마다 다릅니다. 보험 청구 판정: `판정할 서류가 없습니다.`, `청구 담보를 하나 이상 선택해 주세요.` / ESG 재점검: `file, item, declaredText, declaredLevel are required` / ESG 판정 설정: `json body required`, `documentIds required` / 건설 항목 점검: `item is required` / 조항·정정 점검: `확인할 서류가 없습니다.` / 무역서류 처리: `fileId and page are required` |\n| 처리 오류 (상태 값 포함) | `{\"error\":\"...\",\"status\":502}` | 무역서류 분류·추출(`/api/shipping/classify`, `/api/shipping/extract`), ESG·건설 서류 점검과 재점검, 문서 분류·추출 판정(`/api/extract/process`) |\n| 처리 오류 (상태 값 없음) | `{\"error\":\"...\"}` | 파일 업로드, 페이지 이미지, 키 상태 확인, 충돌 검사 |\n| 작업 조회 실패 | `{\"status\":\"error\",\"error\":\"...\"}` | 비동기 작업 조회 |\n| 작업 없음 | `{\"status\":\"unknown\"}` | 비동기 작업 조회 (HTTP 404) |\n| HTTP 200 안의 실패 | `{\"results\":[],\"error\":\"...\"}` | 무역서류 46A 점검, 항구 점검 |\n| 문서별 실패 | `results[i].error` 문자열 | 무역서류 조항 점검, 정정 점검. 전체 상태는 done 이어도 문서마다 실패할 수 있습니다 |\n| 형식이 맞지 않는 요청 | 엔드포인트마다 다릅니다 | multipart 라우트에 multipart 가 아닌 본문을 보내면 500 이 나올 수 있습니다(보험 청구 판정). JSON 라우트는 400 입니다(판정 설정 `json body required`). 검사 API(46A·항구·비문서 점검)는 오류 대신 HTTP 200 과 빈 결과를 돌려줍니다. |\n\n```json\n{\"error\":\"처리 중 오류가 발생했습니다.\",\"status\":502}\n```\n\n| 상태 코드 | 의미 | 대응 |\n| --- | --- | --- |\n| 400 | 입력 필드가 없거나 형식이 맞지 않습니다. | 필드 이름과 형식을 확인하십시오. 같은 요청을 재시도하지 마십시오. |\n| 401 | API 키가 없거나 잘못되었거나 비활성·만료되었습니다. | `code` 가 `api_key_required` 이면 키 헤더 누락입니다. 그 밖이면 키 값과 상태를 담당자에게 확인하십시오. 재시도하지 마십시오. |\n| 403 | 키 권한이 부족하거나 설정이 꺼진 기능을 요청했습니다. | 필요한 권한을 담당자에게 확인하십시오. 재시도하지 마십시오. |\n| 404 | 작업이 없거나(`unknown`) 파일을 찾을 수 없습니다. | 작업이 만료되었거나 재시작으로 사라졌는지 확인하십시오. 파일 ID 는 같은 키로 업로드한 것만 사용할 수 있습니다. |\n| 413 | 파일 크기, 페이지 수, 이미지 픽셀 상한 중 하나를 넘었습니다. | 파일을 줄이거나 나누어 보내십시오. 상한은 담당자에게 확인하십시오. 재시도하지 마십시오. |\n| 429 | 분당 또는 일일 사용 한도를 넘었습니다. | 60초 이상 기다린 뒤 재시도하십시오. 일일 한도는 날짜가 바뀌면 초기화되며, 기준은 한국 시간 오전 9시입니다. |\n| 5xx | 서버 또는 문서 처리 엔진의 일시적인 오류입니다. | 지수 백오프로 최대 2회 재시도하십시오. 이 경우 본문이 JSON 이 아닐 수 있습니다. |\n\n**HTTP 200 과 빈 결과(`{\"results\":[]}` 또는 `{\"conflicts\":[],\"checked\":[]}`)는 성공이 아닐 수 있습니다.** 판정 없음과 구별되지 않으므로, 입력을 보냈는데 결과가 비어 있으면 요청 형식과 파일을 확인하십시오.\n\n## 처리 시간·재시도\n\n문서 1건의 판정이나 추출은 보통 수 초에서 수 분이 걸립니다. 문서 길이와 페이지 수, 스캔 여부에 따라 차이가 큽니다. 클라이언트 타임아웃은 20분 이상으로 설정하는 것을 권장합니다.\n\n- 동시 요청은 문서 4~8건 이내로 제한하는 것을 권장합니다. 많이 보내면 한도에 빨리 도달하고 대기 시간이 길어집니다.\n- 요청 1건이 내부적으로 여러 번의 처리 호출로 나뉘므로, 사용량 한도는 보낸 요청 수보다 빠르게 소모될 수 있습니다.\n- 비동기 작업 조회는 3초 간격으로 하고, 전체 대기 시간에도 상한을 두어 상한을 넘으면 실패로 처리하십시오.\n\n| 상황 | 재시도 여부 | 대기 방법 |\n| --- | --- | --- |\n| 네트워크 오류 또는 5xx (조회 GET, 파일 업로드) | 최대 2회 | 지수 백오프 (예: 2초, 4초) |\n| 작업 생성 POST 의 네트워크 오류 | 응답을 받지 못한 경우에만 | 5xx 를 포함해 응답을 받았다면 재시도하지 마십시오. 재시도하면 작업이 중복 생성됩니다 |\n| 429 | 재시도합니다 | 60초 이상 기다린 뒤 재시도합니다. 응답에 Retry-After 헤더가 없으므로 고정 대기 시간을 사용하십시오 |\n| 그 밖의 4xx | 재시도하지 않습니다 | 요청 내용이나 키 권한을 고쳐야 합니다 |\n\n작업 생성 요청(POST)을 재시도하면 새 작업이 생겨 처리 비용이 중복될 수 있습니다. 응답을 받지 못한 경우에만 재시도하고, 이미 받은 작업 ID 는 다시 생성하지 마십시오.\n\n## 데이터 처리와 보관\n\n업로드한 원본 파일과 OCR 로 추출한 텍스트는 문서 처리 엔진으로 전송되어 처리됩니다. 연동 전에 전송 경로가 고객 정책에 맞는지 보안 담당 부서와 확인하십시오.\n\n- **기본 모드**에서는 업로드한 원본 파일이 문서 처리 엔진의 저장소에 남습니다. 파일은 API 키 단위로 격리되어 저장되며, 기간이 지나도 자동으로 삭제되지 않습니다.\n- 무역서류 파일 업로드, 문서 분류·추출, 보험 청구 판정은 항상 원본을 저장합니다.\n- ESG·건설 서류 점검은 **저장 없는 모드**(베타)를 지원합니다. multipart 폼 필드 `noStore` 를 `1` 또는 `true` 로 보내면 원본 파일을 저장하지 않고 OCR 텍스트만 처리해 판정합니다. `0` 또는 `false` 는 저장 모드이며, 값이 없거나 다른 값이면 담당자가 정한 서버 기본값을 따릅니다.\n- 저장 없는 모드에서는 저장된 파일을 기준으로 하는 후속 기능이 동작하지 않을 수 있습니다.\n- 저장된 원본을 삭제해야 하면 담당자에게 요청하십시오.\n\n저장 없는 모드에서는 여러 페이지로 나뉜 문서의 OCR 일부가 실패해도 오류 없이 그 페이지를 뺀 채 판정될 수 있습니다. 판정 결과가 문서 전체를 반영했는지 확인하고, 중요한 문서는 저장 모드로 처리하십시오.\n\n응답 JSON 과 고객 서버의 로그에도 판정 대상 문서의 내용이 들어 있을 수 있습니다. 개인정보와 기밀 문서를 다룬다면 고객 측 보관 기간과 접근 권한 정책을 함께 적용하십시오.\n\n## 버전과 상태 확인\n\n이 문서는 2026-10-13 기준입니다. API 경로에는 버전 번호가 없으며(예: `/v1` 없음), 서버 버전을 응답에서 확인할 수 없습니다. 연동 테스트 전에 담당자에게 배포된 버전을 확인하십시오.\n\n- 응답에 새 필드가 추가될 수 있습니다. 모르는 필드는 무시하도록 파서를 작성하십시오.\n- 변경 사항은 담당자를 통해 확인하십시오.\n\n서버 동작 확인은 인증 없이 호출할 수 있습니다. 이 경로는 서버가 응답하는지만 확인하며, 문서 처리 엔진의 연결 상태나 API 키 상태는 확인하지 않습니다.\n\n```curl\ncurl -s {{BASE_URL}}/api/shipping/health\n```\n\n응답은 `{\"status\":\"ok\"}` 입니다. 키 상태와 잔여 한도는 `GET {{BASE_URL}}/api/shipping/dashboard` 에 `x-clip-key` 헤더를 보내 확인하십시오."},"servers":[{"url":"https://clip.floaterlab.com"}],"tags":[{"name":"보험서류 점검","description":"보험서류 점검 API 는 보험금 청구서류 파일을 올리면 **서류 종류를 분류**하고 **핵심정보를 추출**하며, 선택한 담보의 **점검항목을 AI 로 판정**합니다.\n\n`POST` 요청(업로드, 처리 시작, 판정 시작)에는 `x-clip-key` 헤더로 API 키를 보내야 합니다. 결과 조회(`GET`)에는 헤더가 필요 없으며, 발급된 `jobId` 가 접근 키 역할을 합니다. 주소는 `{{BASE_URL}}` 입니다.\n\n**주의.** 처리 작업은 비동기로 진행됩니다. 처리 시작 `POST` (`process`, `judge`)는 `jobId` 만 돌려주고 결과는 `GET` 으로 조회합니다. 이 두 `POST` 는 키가 있는지만 확인하므로, 키가 잘못되었거나 권한이 없어도 `jobId` 가 발급될 수 있습니다. 이때 오류는 `GET` 조회 결과에서 확인됩니다. 업로드(`upload`)는 즉시 응답하므로 키 오류를 바로 받습니다."},{"name":"무역서류 점검","description":"무역서류 점검 API 는 선적서류와 신용장 파일을 올려 서류 종류와 핵심정보, 근거 좌표를 얻는 API 입니다. 기본 흐름은 파일을 한 번 업로드하고, 쪽 단위로 분류와 추출 작업을 시작한 뒤 결과를 조회하는 비동기 방식입니다.\n\n**교차 검토와 UCP 600·ISBP 821 규정 검토의 판정(통과·하자·확인 필요)은 API 로 제공되지 않습니다.** 이 API 는 문서 분류, 핵심정보, 근거 위치까지 제공합니다."},{"name":"ESG 서류 점검","description":"ESG 서류 점검 API 는 증빙 문서 1건을 ESG 평가 문항에 대해 판정합니다. 평가 문항 91개 가운데 증빙으로 판정하는 문항을 대상으로 하며, 실태표로만 정하는 문항은 실태표 답으로 결과를 정합니다.\n\nAPI 의 응답은 문서 1건에 대한 **의견과 근거**입니다. 여러 문서와 실태표를 합친 **문항별 최종 결과**는 API 가 계산하지 않으며, 연동하는 쪽에서 이 문서의 원칙으로 계산합니다.\n\nLLM 으로 판정하므로 같은 문서라도 실행마다 결과가 조금 달라질 수 있습니다. 결과는 사람의 검토를 거쳐 사용하십시오.\n\nJavaScript 예제는 top-level await 와 import 를 씁니다. `.mjs` 파일이나 `\"type\": \"module\"` 이 설정된 Node.js 18 이상 환경에서 실행하십시오."},{"name":"건설안전보건 평가 서류 점검","description":"건설안전보건 평가 제출서류 1건을 올리면, 평가 문항 37개(그룹 Q1 ~ Q10, 문항번호 `1-1` ~ `10-5`) 중 그 서류가 판단기준을 충족하는 문항을 판정해 돌려줍니다. 판정은 AI 모델이 수행합니다.\n\n연동 API 는 세 가지입니다. 서류 1건을 37개 문항 전체에 대해 점검하는 **일괄 점검**, 지정한 문항 하나로 판정하는 **문항별 점검**, 그리고 Office·HWP 문서의 근거 위치를 그리기 위한 **근거 페이지 이미지**입니다.\n\n모든 요청은 서류 1건을 기준으로 합니다. 여러 서류를 평가하려면 파일마다 호출하고, 결과를 합쳐 문항별 최종 상태를 계산하세요."},{"name":"문서 분류·추출","description":"문서 분류·추출 API 는 업로드한 파일의 **페이지마다** 문서 유형을 분류하고, 핵심 항목(키와 값)을 추출합니다. 파일 업로드, 페이지별 처리 시작, 결과 조회의 세 단계로 호출합니다. 결과에는 근거 위치(좌표)와 신뢰도 점수가 함께 옵니다.\n\n서버 간 호출을 전제로 합니다. API 키는 고객 서버에만 보관하고, 브라우저 코드에 넣거나 브라우저에서 직접 호출하지 마세요. 이 API 는 브라우저용 CORS 헤더를 주지 않습니다.\n\n운영 환경에서는 TLS 를 앞단 프록시에서 구성하는 것을 권장합니다. 업로드한 문서는 서버 파일 저장소에 보관됩니다."}],"paths":{"/api/insurance/upload":{"post":{"tags":["보험서류 점검"],"operationId":"insurance-upload","summary":"서류 파일 업로드","description":"서류 파일 1건을 올리고 이후 처리에 쓸 fileId 를 받습니다.\n\n서류 파일 1건을 저장하고 `fileId` 를 돌려줍니다. 같은 `fileId` 를 여러 번 처리 요청에 재사용할 수 있습니다. 응답은 동기적으로 즉시 돌아옵니다.\n\n처리 시간: 파일 크기에 따라 수 초~수십 초 걸립니다. 업로드는 300초를 넘으면 시간 초과로 실패하므로, 파일이 아주 크다면 크기를 줄이는 것을 고려하십시오. 클라이언트 타임아웃은 300초보다 넉넉하게 설정하십시오.\n\n오류 본문의 `error` 문구에는 처리 서버가 준 원문이 포함될 수 있어 표기가 바뀔 수 있습니다. 상태 코드로 분기하고 문구에 의존하지 마십시오.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","schema":{"type":"string","description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"업로드할 서류 파일입니다. 파일 이름이 포함된 파트로 보내야 하며, 같은 이름의 파트를 여러 개 보내면 첫 번째 파일만 사용됩니다.","examples":["diagnosis.pdf"]}},"required":["file"]}}}},"responses":{"200":{"description":"업로드된 파일의 정보입니다.","content":{"application/json":{"schema":{"type":"object","required":["fileId"],"properties":{"fileId":{"type":"string","description":"저장된 파일의 ID 입니다. 처리 요청의 `fileId` 로 사용합니다. 업로드한 API 키로만 처리할 수 있습니다."},"fileName":{"type":"string","description":"저장된 원본 파일 이름입니다."},"size":{"type":"integer","description":"저장된 파일 크기(바이트)입니다."}}},"example":{"fileId":"fk_2f9c1a7b4e","fileName":"diagnosis.pdf","size":183412}}}},"400":{"description":"file 파트가 없거나 파일 파트가 아닐 때.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. / API 키가 유효하지 않거나 비활성 상태일 때.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 파일 권한이 없거나, 키가 만료되었거나, 파일 저장이 비활성화되었거나, 키의 IP 제한에 걸렸을 때.","content":{"application/json":{"example":{"error":"Permission denied"}}}},"429":{"description":"API 키의 호출 한도를 넘었을 때.","content":{"application/json":{"example":{"error":"Rate limit exceeded"}}}},"500":{"description":"요청 본문이 multipart/form-data 형식이 아닐 때. 형식을 보장하지 않는 기본 오류 응답입니다.","content":{"application/json":{"example":"(본문 형식 보장 없음)"}}},"502":{"description":"처리 서버가 파일 저장에 실패했을 때. 처리 서버가 준 상태 코드와 문구를 그대로 전달하므로 상태 코드는 502 로 고정되지 않습니다. 처리 서버가 문구를 주지 않으면 `upload <상태 코드>` 가 옵니다. / 처리 서버에 연결하지 못했을 때(네트워크 오류). / 처리 서버 응답이 업로드 제한 시간(300초)을 넘었을 때. 이때의 error 문구는 연결 실패의 문구와 다릅니다.","content":{"application/json":{"example":{"error":"upload 502"}}}}}}},"/api/insurance/process":{"post":{"tags":["보험서류 점검"],"operationId":"insurance-process-start","summary":"서류 분류·추출 시작","description":"업로드한 파일의 한 페이지를 분류하고 핵심정보를 추출하도록 요청합니다.\n\n요청을 접수하면 즉시 `jobId` 를 돌려줍니다. 실제 처리 결과는 `GET /api/insurance/process?job=` 로 조회합니다.\n\n`page` 는 1부터 시작하는 쪽 번호이며, 실제 쪽수 범위 안의 값을 보내십시오. 범위를 벗어난 번호는 오류 없이 문서 전체를 처리하지만, 응답의 `page`·`reference` 와 `words` 가 서로 다른 쪽을 가리킬 수 있습니다.\n\n처리 시간: 요청 접수는 즉시 응답합니다. 실제 처리는 문서 1건당 수 초~수 분 걸립니다. 클라이언트 타임아웃은 20분 이상으로 권장합니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","schema":{"type":"string","description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileId":{"type":"string","description":"업로드 응답으로 받은 파일 ID 입니다. 같은 API 키로 업로드한 파일만 처리할 수 있습니다.","examples":["fk_2f9c1a7b4e"]},"page":{"type":"integer","description":"처리할 쪽 번호(1 이상)입니다. 문자열로 된 숫자도 허용됩니다. 0 이하, 숫자가 아닌 값, 없는 값은 400 오류가 됩니다. 실제 쪽수 범위 안의 값을 보내십시오(범위를 벗어난 값의 결과는 「처리 방식과 제한」을 참고하십시오).","examples":["1"]},"mode":{"type":"string","description":"`classify` 를 보내면 서류 종류 분류를 목적으로 하며 키 추출을 요청하지 않습니다. 그 외의 값(생략 포함)은 `full`, 즉 분류와 핵심정보 추출을 모두 요청합니다.","default":"full","examples":["full"]}},"required":["fileId","page"]}}}},"responses":{"200":{"description":"처리 작업의 식별자입니다.","content":{"application/json":{"schema":{"type":"object","required":["jobId"],"properties":{"jobId":{"type":"string","description":"작업 ID 입니다. 결과 조회에 사용합니다. 서버 메모리에만 보관됩니다."}}},"example":{"jobId":"5b0c8f7e-2d41-4a7b-9c1e-7f3a2b6d8e90"}}}},"400":{"description":"fileId 가 비어 있거나, page 가 없거나 1 미만이거나 숫자가 아니거나, 본문이 JSON 객체가 아닐 때.","content":{"application/json":{"example":{"error":"fileId and page are required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"500":{"description":"본문이 JSON `null` 일 때. 형식을 보장하지 않는 기본 오류 응답입니다.","content":{"application/json":{"example":"(본문 형식 보장 없음)"}}}}},"get":{"tags":["보험서류 점검"],"operationId":"insurance-process-poll","summary":"서류 분류·추출 결과 조회","description":"분류·추출 작업의 상태와 결과를 조회합니다(폴링용).\n\n`status` 가 `pending` 인 동안 3초 간격으로 반복 호출하십시오. `done` 또는 `error` 를 받으면 작업이 삭제되므로 그 응답을 저장하고 더 이상 조회하지 마십시오.\n\n이 조회에는 `x-clip-key` 헤더가 필요하지 않습니다. 추측하기 어려운 jobId 가 사실상 접근 키 역할을 합니다.\n\n처리 시간: 조회는 즉시 응답합니다. 3초 간격으로 폴링하는 것을 권장합니다.\n\n오류 본문의 `error` 문구에는 처리 서버가 준 원문이 포함될 수 있어 표기가 바뀔 수 있습니다. 상태 코드로 분기하고 문구에 의존하지 마십시오.","parameters":[{"name":"job","in":"query","required":true,"description":"POST 응답으로 받은 jobId 입니다. 모르는 값이거나 이미 결과를 받아 삭제된 경우 404 가 반환됩니다.","schema":{"type":"string","description":"POST 응답으로 받은 jobId 입니다. 모르는 값이거나 이미 결과를 받아 삭제된 경우 404 가 반환됩니다.","examples":["5b0c8f7e-2d41-4a7b-9c1e-7f3a2b6d8e90"]}}],"responses":{"200":{"description":"처리 상태에 따라 본문 형식이 달라지며, HTTP 상태 코드도 상태에 따라 다릅니다. `pending` 과 `done` 은 200, `unknown` 은 404 입니다. `error` 는 원인에 따른 HTTP 코드로 반환됩니다.","content":{"application/json":{"schema":{"description":"처리 상태입니다. 상태에 따라 HTTP 상태 코드가 다릅니다.","oneOf":[{"type":"object","required":["status"],"description":"처리 중입니다(HTTP 200).","properties":{"status":{"type":"string","enum":["pending"]}}},{"type":"object","required":["status","result"],"description":"처리가 끝났습니다(HTTP 200). 이 응답을 받으면 서버가 해당 작업을 삭제하므로 같은 jobId 로 다시 조회하면 404 가 됩니다. 결과를 반드시 저장하십시오.","properties":{"status":{"type":"string","enum":["done"]},"result":{"type":"object","required":["page","type","keys","words"],"description":"분류와 추출 결과입니다.","properties":{"page":{"type":"integer","description":"요청한 페이지 번호입니다."},"type":{"type":"string","description":"분류된 서류 종류 이름입니다(예: 진단서, 입퇴원확인서). 분류 결과가 없으면 `document` 입니다. 분류된 서류의 이 값만 판정 요청의 docTypes 에 넘기십시오. `document` 인 파일은 판정에서 제외됩니다."},"keys":{"type":"array","items":{"type":"object","required":["key","value","reference"],"properties":{"key":{"type":"string","description":"핵심정보 항목 이름입니다(예: 예금주, 계좌번호, 진료비총액). 서류 종류에 따라 항목 이름이 다릅니다."},"value":{"type":"string","description":"서류에서 추출한 원문 값입니다. 값이 없으면 빈 문자열입니다."},"informationList":{"type":"array","items":{"type":"string"},"description":"목록형 항목의 값 배열입니다. 해당하지 않는 항목에는 없습니다."},"processed":{"description":"정규화된 값입니다. 정규화 대상 항목에만 있으며, 형식은 항목에 따라 다를 수 있습니다."},"reference":{"type":"object","additionalProperties":{"type":"array","items":{"type":"integer"}},"description":"값의 근거가 된 단어 번호 목록입니다. 키는 요청한 page 번호의 문자열이고, 값은 단어 번호(words 의 키)입니다. 근거가 없으면 `{}` 입니다."},"confidence":{"type":"number","description":"추출 등급 점수(0.05~0.99)입니다. 정답일 확률이 아니라 등급을 순서대로 나타낸 값이므로 임계값 비교에만 쓰십시오. 제공되지 않는 경우가 있습니다."}}},"description":"추출된 핵심정보 목록입니다. mode 가 classify 이면 키 추출을 요청하지 않으므로 이 값을 사용하지 않는 것을 권장합니다."},"words":{"type":"object","additionalProperties":{"type":"array","items":{"type":"number"}},"description":"keys 의 근거가 된 단어만 모은 맵입니다. 키는 단어 번호이고, 값은 [x1, y1, x2, y2] 좌표입니다. 0~1 로 정규화된 좌표는 원본 좌표가 절대 좌표인 경우이며, 그 밖의 경우에는 원본 좌표가 그대로 옵니다. mode 가 classify 이면 이 값을 사용하지 않는 것을 권장합니다."}}}}},{"type":"object","required":["status","error"],"description":"처리에 실패했습니다. HTTP 상태 코드는 원인에 따라 달라집니다(아래 오류 목록 참고). 이 응답도 한 번 받으면 작업이 삭제됩니다.","properties":{"status":{"type":"string","enum":["error"]},"error":{"type":"string","description":"실패 사유 메시지입니다."}}},{"type":"object","required":["status"],"description":"해당 jobId 를 찾을 수 없습니다(HTTP 404).","properties":{"status":{"type":"string","enum":["unknown"]}}}]},"example":{"status":"done","result":{"page":1,"type":"진단서","keys":[{"key":"환자이름","value":"홍길동","reference":{"1":[12]},"confidence":0.84}],"words":{"12":[0.11,0.2,0.3,0.23]}}}}}},"401":{"description":"API 키가 유효하지 않을 때. 처리 요청 시점이 아니라 이 조회에서 확인됩니다.","content":{"application/json":{"example":{"status":"error","error":"Invalid or inactive API key"}}}},"403":{"description":"키에 파일 처리 권한이 없거나, 필요한 분석 기능이 허용되지 않았거나, 키의 IP 제한에 걸렸을 때.","content":{"application/json":{"example":{"status":"error","error":"Permission denied"}}}},"404":{"description":"jobId 가 없거나, 이미 결과를 받아 삭제되었거나, 오래되어 새 요청 때 정리되었거나, 서버가 재시작되었을 때. / 처리하려던 파일을 찾을 수 없을 때(잘못된 fileId, 또는 다른 API 키로 업로드한 파일).","content":{"application/json":{"example":{"status":"unknown"}}}},"429":{"description":"API 키의 분당 또는 일일 호출 한도를 넘었을 때. 재시도 시각 정보는 제공되지 않습니다.","content":{"application/json":{"example":{"status":"error","error":"Rate limit exceeded"}}}},"502":{"description":"처리 서버 연결 실패(네트워크 오류)입니다. / 분류·추출 작업이 서버의 처리 시간 제한을 넘었을 때.","content":{"application/json":{"example":{"status":"error","error":"fetch failed"}}}}}}},"/api/insurance/judge":{"post":{"tags":["보험서류 점검"],"operationId":"insurance-judge-start","summary":"점검항목 AI 판정 시작","description":"선택한 담보의 AI 판정 항목을 서류 기준으로 판정하도록 요청합니다.\n\n서류 파일을 파일 전체에 대해 색인한 뒤 점검항목마다 판정합니다. 요청 본문을 모두 받은 뒤에 `jobId` 를 돌려주므로, 파일이 클수록 이 응답이 늦어집니다.\n\n`files` 는 업로드한 파일을 다시 보내야 합니다. 업로드 때 받은 `fileId` 는 판정 요청에 사용할 수 없습니다.\n\n처리 시간: 문서 수와 담보 수에 따라 수 분 이상 걸릴 수 있습니다. 클라이언트 타임아웃은 20분 이상으로 권장합니다. 요청 본문 전송 시간도 응답 시간에 포함됩니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","schema":{"type":"string","description":"발급받은 API 키입니다. 앞뒤 공백은 무시됩니다. 헤더가 없거나 비어 있으면 인증 오류(401)가 반환됩니다. 기본 키로 대신 처리하도록 설정된 배포에서는 예외가 될 수 있으므로, 키는 항상 헤더로 보내십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"coverages":{"type":"string","description":"판정할 담보 ID 를 쉼표로 구분한 문자열입니다(예: `medical_in,surgery`). 공백은 무시되며, 하나도 없으면 400 오류가 됩니다. 담보 ID 는 본문 하단 「부록」을 참고하십시오. 알 수 없는 담보 ID 는 무시됩니다. 모두 알 수 없는 ID 이면 400 이 아니라 `results` 가 빈 배열인 `done` 이 되며, 이때도 서류 업로드와 색인은 실행되어 비용이 발생하므로 담보 ID 를 정확히 보내십시오.","examples":["medical_in,surgery"]},"files":{"type":"string","format":"binary","description":"판정에 사용할 서류 파일입니다. 같은 이름의 파트를 여러 번 보냅니다. 최소 1개가 필요합니다.","examples":["diagnosis.pdf"]},"docTypes":{"type":"string","description":"`files` 와 같은 순서로 보내는 서류 종류 이름입니다. 보통 처리 결과의 `result.type` 을 그대로 넣습니다. 생략하면 파일 이름으로 찾지만, 인식되지 않은 파일(`document` 포함)은 판정에서 제외됩니다. 자세한 규칙은 「서류 분류와 docTypes」를 참고하십시오.","examples":["진단서"]}},"required":["coverages","files"]}}}},"responses":{"200":{"description":"판정 작업의 식별자입니다.","content":{"application/json":{"schema":{"type":"object","required":["jobId"],"properties":{"jobId":{"type":"string","description":"작업 ID 입니다. 결과 조회에 사용합니다. 서버 메모리에만 보관됩니다."}}},"example":{"jobId":"9d1e4c2a-7b3f-4e85-a6d0-2c8f5b1a7e33"}}}},"400":{"description":"coverages 가 없거나 담보 ID 가 하나도 없을 때. / files 파트가 하나도 없을 때. / 모든 파일의 서류 종류를 인식하지 못했을 때. 일부만 인식되지 않은 경우에는 오류가 아니라 해당 파일만 제외됩니다.","content":{"application/json":{"example":{"error":"청구 담보를 하나 이상 선택해 주세요."}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"500":{"description":"요청 본문이 multipart/form-data 형식이 아닐 때. 형식을 보장하지 않는 기본 오류 응답입니다.","content":{"application/json":{"example":"(본문 형식 보장 없음)"}}}}},"get":{"tags":["보험서류 점검"],"operationId":"insurance-judge-poll","summary":"점검항목 AI 판정 결과 조회","description":"판정 작업의 상태와 결과를 조회합니다(폴링용).\n\n이 엔드포인트는 점검항목 목록을 돌려주지 않습니다. 판정 작업의 상태와 결과만 조회합니다. 이 조회에는 `x-clip-key` 헤더가 필요하지 않습니다.\n\n`pending` 인 동안 3초 간격으로 호출하십시오. `done` 또는 `error` 를 받으면 작업이 삭제되므로 그 응답을 저장하십시오.\n\n처리 시간: 조회는 즉시 응답합니다. 3초 간격으로 폴링하는 것을 권장합니다.\n\n오류 본문의 `error` 문구에는 처리 서버가 준 원문이 포함될 수 있어 표기가 바뀔 수 있습니다. 상태 코드로 분기하고 문구에 의존하지 마십시오.","parameters":[{"name":"job","in":"query","required":true,"description":"POST 응답으로 받은 jobId 입니다. 모르는 값이거나 이미 결과를 받아 삭제된 경우 404 가 반환됩니다.","schema":{"type":"string","description":"POST 응답으로 받은 jobId 입니다. 모르는 값이거나 이미 결과를 받아 삭제된 경우 404 가 반환됩니다.","examples":["5b0c8f7e-2d41-4a7b-9c1e-7f3a2b6d8e90"]}}],"responses":{"200":{"description":"`pending` 과 `done` 은 200, `error` 는 502, `unknown` 은 404 로 반환됩니다.","content":{"application/json":{"schema":{"description":"판정 작업의 상태입니다. 상태에 따라 HTTP 상태 코드가 다릅니다.","oneOf":[{"type":"object","required":["status"],"description":"처리 중입니다(HTTP 200). note 는 현재 단계입니다.","properties":{"status":{"type":"string","enum":["pending"]},"note":{"type":"string","enum":["서류 색인 중","점검항목 판정 중"],"description":"현재 단계 안내 문구입니다."}}},{"type":"object","required":["status","results"],"description":"판정이 끝났습니다(HTTP 200). 이 응답을 받으면 작업이 삭제되므로 결과를 반드시 저장하십시오.","properties":{"status":{"type":"string","enum":["done"]},"results":{"type":"array","items":{"type":"object","description":"점검항목 하나의 판정 결과입니다.","required":["coverageId","no","verdict","reason","usedDocs","sources"],"properties":{"coverageId":{"type":"string","enum":["medical_out","medical_in","hospitalization","surgery","diagnosis_benefit","death"],"description":"담보 ID 입니다. 담보 목록은 본문 하단 「담보 ID」를 참고하십시오."},"no":{"type":"string","description":"점검항목 번호입니다(예: MI-4). 항목 제목과 판정 기준은 이 응답에 포함되지 않습니다."},"verdict":{"type":"string","enum":["PASS","FAIL","REVIEW","UNKNOWN"],"description":"판정입니다. PASS 는 기준을 충족함, FAIL 은 기준에 어긋남(지급 제한 사유), REVIEW 는 서류의 값은 확인되었으나 약관이나 계약 정보로 최종 판단이 필요함, UNKNOWN 은 근거를 찾지 못했거나 판정에 실패했음을 뜻합니다."},"reason":{"type":"string","description":"판정 사유입니다. 보통 한국어 1~2문장으로, 서류에서 읽은 값을 근거로 제시합니다. 길이와 형식은 보장되지 않으며 비어 있을 수 있습니다. UNKNOWN 인 경우에는 서버가 정한 문구(「근거 서류가 제출되지 않았습니다.」, 「판정 응답을 해석하지 못했습니다.」, 「판정 실패: (오류 메시지)」)가 오거나, 모델이 작성한 문구가 올 수 있습니다."},"usedDocs":{"type":"array","items":{"type":"string","enum":["claim_form","id_card","bankbook","diagnosis","admission_cert","medical_detail","medical_receipt","prescription","surgery_cert","death_cert","family_relation","pathology","proxy"],"description":"서류 종류 코드입니다. 표는 본문 하단 「서류 종류 코드」를 참고하십시오."},"description":"이 항목이 요구하는 서류 가운데 제출되어 판정에 넘겨진 서류 종류 코드 목록입니다. 모델이 실제로 참조했는지까지 보장하지는 않습니다. 해당 서류가 없으면 빈 배열입니다."},"sources":{"type":"array","items":{"type":"object","description":"판정 근거 구절. 0개에서 여러 개까지 올 수 있으며 순서와 개수는 보장되지 않습니다.","properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호(1부터 시작)"},"text":{"type":"string","description":"근거 구절 텍스트"},"score":{"type":"number","description":"검색 유사도(대체로 0~1)"},"bbox":{"type":"array","items":{"type":"number"},"description":"[x1, y1, x2, y2] 형식의 근거 위치. 페이지 가로·세로 대비 0~1 정규화 좌표이며 좌상단이 원점입니다. 위치를 찾지 못하면 없습니다."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"줄 단위 위치 목록(각 항목은 [x1, y1, x2, y2] 정규화 좌표). 없을 수 있습니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류(예: text, table). 없을 수 있습니다."},"fileName":{"type":"string","description":"근거가 나온 서류의 파일명입니다. 판정 요청에 보낸 파일 이름과 같으므로, 이 값으로 어느 서류의 근거인지 찾습니다."}},"required":["page","text","score"]},"description":"판정에 쓴 원문 근거 목록입니다. 근거 서류가 없거나 판정 호출이 실패하면 빈 배열입니다."}}},"description":"점검항목별 판정 결과입니다."}}},{"type":"object","required":["status","error"],"description":"판정 작업이 서류 업로드나 색인 단계에서 실패했습니다(HTTP 502). 이 상태 코드는 원인에 관계없이 502 로 고정되며, 원인은 error 문구로만 구분할 수 있습니다. 판정 호출 자체의 실패는 항목별 `UNKNOWN` 으로 기록됩니다.","properties":{"status":{"type":"string","enum":["error"]},"error":{"type":"string","description":"실패 사유 메시지입니다(예: 「파일 업로드 실패: …」, 「색인 처리 실패: …」)."}}},{"type":"object","required":["status"],"description":"해당 jobId 를 찾을 수 없습니다(HTTP 404).","properties":{"status":{"type":"string","enum":["unknown"]}}}]},"example":{"status":"done","results":[{"coverageId":"medical_in","no":"MI-4","verdict":"REVIEW","reason":"진단서의 진단명은 확인되었으나, 약관상 면책 해당 여부는 계약 정보가 있어야 판단할 수 있습니다.","usedDocs":["diagnosis"],"sources":[{"page":1,"text":"병명: 요추간판탈출증 (M51.2)","score":0.71,"bbox":[0.12,0.31,0.64,0.34],"boxes":[[0.12,0.31,0.64,0.34]],"layoutClass":"text","fileName":"diagnosis.pdf"}]}]}}}},"404":{"description":"jobId 가 없거나, 이미 결과를 받아 삭제되었거나, 오래되어 새 요청 때 정리되었거나, 서버가 재시작되었을 때.","content":{"application/json":{"example":{"status":"unknown"}}}},"502":{"description":"서류 업로드나 색인 과정에서 오류가 났을 때. 원래 상태 코드는 본문의 error 문구로만 확인할 수 있습니다. / 서류 내용을 색인할 수 없을 때(추출된 내용이 없는 경우 등).","content":{"application/json":{"example":{"status":"error","error":"파일 업로드 실패: Invalid or inactive API key"}}}}}}},"/api/shipping/upload":{"post":{"tags":["무역서류 점검"],"operationId":"upload","summary":"파일 업로드","description":"서류 파일을 서버에 올리고 이후 처리에 쓸 fileId 를 받습니다.\n\n파일은 한 번만 올리면 됩니다. 받은 `fileId` 로 쪽별 처리(`process`)를 여러 번 호출할 수 있습니다.\n\n처리 시간: 파일 크기에 따라 수 초에서 수 분이 걸릴 수 있습니다. 파일 전달 단계는 5분이 지나면 서버에서 중단되므로 그 안에 끝나는 크기여야 합니다. 클라이언트 타임아웃은 넉넉히(10분 이상) 잡으십시오.\n\n**업로드한 파일은 서버 파일 저장소에 보관됩니다.** 이 API 에는 파일 삭제 기능이 없으며, 이 API 는 파일을 저장하지 않는 옵션(저장 없는 모드)을 제공하지 않습니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키. 발급받은 키를 넣으십시오.","schema":{"type":"string","description":"API 키. 발급받은 키를 넣으십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"서류 파일. PDF, PNG, JPG, TIFF, WEBP 를 권장합니다. 파일 크기 한도는 서버 설정에 따릅니다."}},"required":["file"]}}}},"responses":{"200":{"description":"저장소가 성공 응답을 했지만 fileId 를 주지 않았을 때. HTTP 상태는 200 이지만 fileId 가 없으므로 실패로 처리하십시오.","content":{"application/json":{"example":{"error":"upload 200"}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문의 문구는 화면 안내용이므로, 판별에는 code 값(api_key_required)을 쓰십시오. / 처리 엔진이 키를 거부할 때. 상태 코드와 본문은 처리 엔진의 응답을 따릅니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"413":{"description":"파일 크기가 서버 설정의 한도를 넘을 때. 한도는 서버 설정에 따릅니다.","content":{"application/json":{"example":{"error":"Clip API error 413: File size exceeds limit"}}}},"500":{"description":"파일 저장소가 오류를 돌려줄 때(상태 코드는 저장소 응답을 그대로 따릅니다). 본문에는 저장소의 오류 메시지가 있으면 그대로 들어가고, 없으면 이 형태가 나옵니다.","content":{"application/json":{"example":{"error":"upload 500"}}}},"502":{"description":"파일 저장소에 연결하지 못했을 때, 또는 저장소 전달이 5분을 넘어 중단되었을 때","content":{"application/json":{"example":{"error":"The operation was aborted due to timeout"}}}}}}},"/api/shipping/process":{"post":{"tags":["무역서류 점검"],"operationId":"process-start","summary":"쪽 처리 시작","description":"업로드한 파일의 한 쪽에 대해 분류 또는 핵심정보 추출 작업을 시작하고 jobId 를 받습니다.\n\n`mode` 가 `classify` 면 서류 종류만, `full` 이면 서류 종류와 핵심정보를 얻습니다. 작업은 비동기이므로 결과는 `GET /api/shipping/process?job=` 으로 받습니다.\n\nPOST 는 키 유무와 `fileId`·`page` 필수 값만 확인합니다. 쪽 번호가 파일의 쪽 수 안에 있는지는 확인하지 않으므로, 범위 밖 쪽은 지정하지 마십시오. 처리 중의 오류는 POST 가 아니라 GET 조회의 `error` 상태로 나옵니다.\n\n처리 시간: 요청 자체는 즉시 응답합니다. 실제 처리 시간은 GET 조회로 기다립니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키. 발급받은 키를 넣으십시오.","schema":{"type":"string","description":"API 키. 발급받은 키를 넣으십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileId":{"type":"string","description":"업로드 응답의 fileId. 업로드할 때 쓴 키와 같은 키로 호출하십시오.","examples":["f_8c21a7e4"]},"page":{"type":"integer","description":"처리할 쪽 번호(1부터 시작). 파일의 쪽 수 안의 번호만 지정하십시오.","examples":["1"]},"mode":{"type":"string","description":"`classify`(서류 종류만) 또는 `full`(종류와 핵심정보)","default":"full"}},"required":["fileId","page"]}}}},"responses":{"200":{"description":"작업 식별자. 결과가 아니라 작업 번호입니다.","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string","description":"작업 식별자(UUID). GET 조회에 씁니다."}},"required":["jobId"]},"example":{"jobId":"3f2b9c1e-6a4d-4e8b-9f0a-2d7c5b1e8a40"}}}},"400":{"description":"fileId 가 없거나 page 가 1 미만일 때","content":{"application/json":{"example":{"error":"fileId and page are required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문의 문구는 화면 안내용이므로, 판별에는 code 값(api_key_required)을 쓰십시오.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}}}},"get":{"tags":["무역서류 점검"],"operationId":"process-result","summary":"쪽 처리 결과 조회","description":"jobId 의 상태를 조회합니다. 처리가 끝나면 쪽의 서류 종류와 핵심정보를 돌려줍니다.\n\n`status` 가 `pending` 이면 아직 처리 중입니다. 3초 간격으로 다시 조회하십시오. `done` 이면 `result` 를 받습니다. 결과(오류 결과 포함)는 한 번 받으면 서버에서 삭제됩니다.\n\n처리 시간: 처리 시간은 파일 크기와 쪽 수에 따라 수 초에서 수 분입니다. 클라이언트 타임아웃은 20분 이상 권장합니다.","parameters":[{"name":"x-clip-key","in":"header","required":false,"description":"이 조회는 키를 검사하지 않습니다. 같은 키로 보내는 것을 권장합니다.","schema":{"type":"string","description":"이 조회는 키를 검사하지 않습니다. 같은 키로 보내는 것을 권장합니다.","examples":["YOUR_API_KEY"]}},{"name":"job","in":"query","required":true,"description":"POST 응답의 jobId","schema":{"type":"string","description":"POST 응답의 jobId","examples":["3f2b9c1e-6a4d-4e8b-9f0a-2d7c5b1e8a40"]}}],"responses":{"200":{"description":"`status` 가 `pending`, `done`, `error`, `unknown` 중 하나입니다. `done` 일 때만 `result` 가 있고, `error` 일 때만 `error` 메시지가 있습니다.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","done","error","unknown"],"description":"작업 상태"},"result":{"type":"object","description":"status 가 done 일 때의 쪽 처리 결과","properties":{"page":{"type":"integer","description":"처리한 쪽 번호"},"type":{"type":"string","description":"서류 종류 이름(분류 결과). 분류하지 못하면 document 입니다. 값은 고정 목록이 아닙니다."},"keys":{"type":"array","description":"추출된 핵심정보 목록. classify 모드에서는 비어 있습니다.","items":{"type":"object","description":"추출된 핵심정보 한 항목","properties":{"key":{"type":"string","description":"항목 이름(예: L/C No., Amount). 서류 종류마다 다릅니다."},"value":{"type":"string","description":"추출된 값 문자열. 값을 찾지 못하면 빈 문자열입니다."},"informationList":{"type":"array","items":{"type":"string"},"description":"여러 줄로 된 항목(예: 서류 목록, 조건 목록)의 줄 단위 값. 해당하지 않으면 생략됩니다."},"processed":{"type":"object","description":"금액·과부족처럼 구조화된 값(예: 금액은 currency·amount, 과부족은 plus·minus). 해당하지 않으면 생략됩니다."},"reference":{"type":"object","description":"값이 적힌 쪽 번호(문자열 키)와 단어 번호 목록. 번호로 words 에서 좌표를 찾습니다.","additionalProperties":{"type":"array","items":{"type":"integer"}}},"confidence":{"type":"number","description":"항목의 등급 점수(0~1). 확률이 아니라 처리 엔진이 매긴 등급 점수입니다. 값이 낮은 항목은 사람이 다시 확인할 후보로 참고하십시오. 값이 없는 응답도 있으니 없으면 표시를 생략하십시오."}},"required":["key","value","reference"]}},"words":{"type":"object","description":"근거 단어의 좌표. 키 이름이 단어 번호(문자열)이고, 값이 좌표입니다(쪽 번호 단계가 없는 평면 맵). keys 의 reference 가 가리키는 단어만 들어 있습니다.","additionalProperties":{"type":"array","items":{"type":"number"},"description":"단어의 좌표 [x0, y0, x1, y1]. 보통 쪽 크기에 대한 0~1 비율입니다."}}},"required":["page","type","keys","words"]},"error":{"type":"string","description":"status 가 error 일 때의 오류 메시지"}},"required":["status"]},"example":{"status":"done","result":{"page":1,"type":"letter of credit","keys":[{"key":"L/C No.","value":"LC2026-0042","reference":{"1":[12]},"confidence":0.97},{"key":"Amount","value":"USD 125,000.00","processed":{"currency":"USD","amount":125000},"reference":{"1":[18,19]},"confidence":0.95}],"words":{"12":[0.11,0.08,0.31,0.1],"18":[0.2,0.42,0.3,0.44],"19":[0.32,0.42,0.42,0.44]}}}}}},"401":{"description":"처리 엔진이 키를 거부할 때. 상태 코드는 처리 엔진의 응답을 따르며, 본문의 status 는 error 입니다.","content":{"application/json":{"example":{"status":"error","error":"process 401"}}}},"404":{"description":"jobId 를 찾을 수 없을 때. 잘못된 값, 이미 한 번 받은 값, 서버 재시작으로 사라진 값, 정리된 값이 해당합니다. 본문의 status 가 unknown 입니다. / 처리 엔진이 파일이나 쪽을 찾지 못했을 때. 본문의 status 가 error 이므로, unknown(작업 없음)과 구분하십시오.","content":{"application/json":{"example":{"status":"unknown"}}}},"413":{"description":"파일 크기가 서버 설정의 한도를 넘을 때. 한도는 서버 설정에 따릅니다.","content":{"application/json":{"example":{"status":"error","error":"File size exceeds limit"}}}},"429":{"description":"요청이 너무 많을 때","content":{"application/json":{"example":{"status":"error","error":"process 429"}}}},"502":{"description":"처리 엔진에 연결하지 못했을 때, 또는 상태를 알 수 없을 때","content":{"application/json":{"example":{"status":"error","error":"fetch failed"}}}}}}},"/api/shipping/classify":{"post":{"tags":["무역서류 점검"],"operationId":"classify","summary":"파일 서류 종류 분류","description":"파일 1건을 한 번에 처리해 서류 종류와 쪽 수를 돌려줍니다. 응답까지 기다리는 동기 호출입니다.\n\n핵심정보는 추출하지 않습니다. 파일에 서류가 한 종류만 들어 있을 때 쓰십시오. 여러 서류가 섞인 파일은 `review` 를 사용하십시오. 파일 전체를 동기로 처리하므로 쪽 수가 많으면 시간과 비용이 큽니다. 쪽 수 확인만을 목적으로 호출하지 마십시오.\n\n처리 시간: 처리 시간은 파일 크기와 쪽 수에 따라 수 초에서 수 분입니다. 클라이언트 타임아웃은 20분 이상 권장합니다. 서버 내부에서 처리 엔진 호출이 실패하면 재시도가 최대 3회까지 이루어지므로, 실패 응답이 늦게 올 수 있고 OCR 이 중복 실행될 수 있습니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키. 발급받은 키를 넣으십시오.","schema":{"type":"string","description":"API 키. 발급받은 키를 넣으십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"서류 파일. 파일 크기 한도는 서버 설정에 따릅니다."}},"required":["file"]}}}},"responses":{"200":{"description":"파일의 대표 서류 종류와 신뢰도, 쪽 수입니다.","content":{"application/json":{"schema":{"type":"object","properties":{"documentType":{"type":"string","description":"서류 종류 이름. 분류하지 못하면 unknown 또는 document 가 올 수 있습니다. 값은 고정 목록이 아닙니다."},"confidence":{"type":"number","description":"서류 분류 신뢰도(0~1). 현재는 값이 제공되지 않아 항상 0 으로 돌아오므로 판단에 쓰지 마십시오."},"pages":{"type":"integer","description":"파일의 쪽 수"}},"required":["documentType","confidence","pages"]},"example":{"documentType":"commercial invoice","confidence":0,"pages":3}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문의 문구는 화면 안내용이므로, 판별에는 code 값(api_key_required)을 쓰십시오. / 키가 잘못되었을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 이 작업 권한이 없을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"Clip API error 403: Forbidden","status":403}}}},"404":{"description":"처리 엔진이 대상을 찾지 못했을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"Clip API error 404: Not Found","status":404}}}},"413":{"description":"파일 크기가 서버 설정의 한도를 넘을 때. 한도는 서버 설정에 따르며, 현재 한도는 dashboard 에서 확인할 수 있습니다.","content":{"application/json":{"example":{"error":"Clip API error 413: File size exceeds limit","status":413}}}},"429":{"description":"요청이 너무 많을 때. 잠시 뒤 다시 보내십시오.","content":{"application/json":{"example":{"error":"Clip API error 429: Too Many Requests","status":429}}}},"500":{"description":"처리 엔진이 오류를 돌려줄 때(상태 코드는 그대로 전달됩니다)","content":{"application/json":{"example":{"error":"Clip API error 500: Internal Server Error","status":500}}}},"502":{"description":"처리 엔진에 연결하지 못했을 때, 또는 처리 엔진의 응답 형식이 맞지 않을 때 / 응답 형식이 맞지 않을 때(메시지에 Invalid OCR response shape 가 붙습니다)","content":{"application/json":{"example":{"error":"Network error: fetch failed","status":502}}}}}}},"/api/shipping/extract":{"post":{"tags":["무역서류 점검"],"operationId":"extract","summary":"파일 핵심정보 추출","description":"파일 1건을 한 번에 처리해 서류 종류와 핵심정보를 평면 목록으로 돌려줍니다. 동기 호출입니다.\n\n`data` 는 항목 이름을 키로 하는 평면 객체입니다. 쪽 구분 없이 합친 값이므로 서류가 한 종류만 들어 있는 파일에 맞습니다.\n\n처리 시간: 처리 시간은 파일 크기와 쪽 수에 따라 수 초에서 수 분입니다. 클라이언트 타임아웃은 20분 이상 권장합니다. 서버 내부에서 처리 엔진 호출이 실패하면 재시도가 최대 3회까지 이루어지므로, 실패 응답이 늦게 올 수 있고 OCR 이 중복 실행될 수 있습니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키. 발급받은 키를 넣으십시오.","schema":{"type":"string","description":"API 키. 발급받은 키를 넣으십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"서류 파일. 파일 크기 한도는 서버 설정에 따릅니다."}},"required":["file"]}}}},"responses":{"200":{"description":"서류 종류, 신뢰도, 쪽 수, 그리고 핵심정보입니다.","content":{"application/json":{"schema":{"type":"object","properties":{"documentType":{"type":"string","description":"서류 종류 이름. 값은 고정 목록이 아닙니다."},"confidence":{"type":"number","description":"서류 분류 신뢰도(0~1). 현재는 값이 제공되지 않아 항상 0 으로 돌아오므로 판단에 쓰지 마십시오."},"pages":{"type":"integer","description":"파일의 쪽 수"},"data":{"type":"object","description":"항목 이름 → 값 문자열. 값이 비어 있는 항목은 빠집니다. 같은 이름의 항목이 여러 쪽에 있으면 뒤의 값이 남습니다.","additionalProperties":{"type":"string"}}},"required":["documentType","confidence","pages","data"]},"example":{"documentType":"commercial invoice","confidence":0,"pages":3,"data":{"Invoice No.":"INV-2026-0931","Total Amount":"USD 125,000.00"}}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문의 문구는 화면 안내용이므로, 판별에는 code 값(api_key_required)을 쓰십시오. / 키가 잘못되었을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 이 작업 권한이 없을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"Clip API error 403: Forbidden","status":403}}}},"404":{"description":"처리 엔진이 대상을 찾지 못했을 때. 처리 엔진의 응답 상태를 그대로 전달합니다.","content":{"application/json":{"example":{"error":"Clip API error 404: Not Found","status":404}}}},"413":{"description":"파일 크기가 서버 설정의 한도를 넘을 때. 한도는 서버 설정에 따르며, 현재 한도는 dashboard 에서 확인할 수 있습니다.","content":{"application/json":{"example":{"error":"Clip API error 413: File size exceeds limit","status":413}}}},"429":{"description":"요청이 너무 많을 때. 잠시 뒤 다시 보내십시오.","content":{"application/json":{"example":{"error":"Clip API error 429: Too Many Requests","status":429}}}},"500":{"description":"처리 엔진이 오류를 돌려줄 때(상태 코드는 그대로 전달됩니다)","content":{"application/json":{"example":{"error":"Clip API error 500: Internal Server Error","status":500}}}},"502":{"description":"처리 엔진에 연결하지 못했을 때, 또는 처리 엔진의 응답 형식이 맞지 않을 때 / 응답 형식이 맞지 않을 때(메시지에 Invalid OCR response shape 가 붙습니다)","content":{"application/json":{"example":{"error":"Network error: fetch failed","status":502}}}}}}},"/api/shipping/review":{"post":{"tags":["무역서류 점검"],"operationId":"review-start","summary":"파일 서류 분리·추출 시작","description":"파일 1건을 서류별로 나누고 핵심정보를 추출하는 작업을 시작해 jobId 를 받습니다.\n\n한 파일에 여러 서류(예: 신용장과 첨부 서류)가 섞여 있을 때 쪽 단위 호출 없이 서류별 결과를 받을 수 있습니다. `mode` 가 `classify` 면 서류 범위만 받고 키는 받지 않습니다.\n\n처리 시간: 요청 자체는 즉시 응답합니다. 처리는 GET 조회로 기다립니다.\n\n**이 경로는 clip 웹 화면이 쓰지 않는 경로입니다.** 서류 경계는 파일 전체 분류로 정해지므로 화면의 결과와 다를 수 있습니다. 연동 전에 실제 샘플 파일로 응답 형식과 결과를 먼저 확인하십시오.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키. 발급받은 키를 넣으십시오.","schema":{"type":"string","description":"API 키. 발급받은 키를 넣으십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"서류 파일. 파일 크기 한도는 서버 설정에 따릅니다."},"mode":{"type":"string","description":"`classify`(서류 범위만) 또는 `full`(범위와 핵심정보)","default":"full"}},"required":["file"]}}}},"responses":{"200":{"description":"작업 식별자입니다. 결과는 GET 으로 받습니다.","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string","description":"작업 식별자(UUID)"}},"required":["jobId"]},"example":{"jobId":"9d4e1a7b-2c3f-4a6e-8b1d-5f0e7c9a2b11"}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문의 문구는 화면 안내용이므로, 판별에는 code 값(api_key_required)을 쓰십시오.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}}}},"get":{"tags":["무역서류 점검"],"operationId":"review-result","summary":"서류 분리·추출 결과 조회","description":"jobId 의 상태를 조회합니다. 완료되면 서류별 범위와 핵심정보, 근거 좌표를 돌려줍니다.\n\n`status` 가 `pending` 이면 3초 간격으로 다시 조회하십시오. `done` 이면 `result` 를 받습니다. 결과(오류 결과 포함)는 한 번 받으면 서버에서 삭제됩니다.\n\n처리 시간: 처리 시간은 파일 크기와 쪽 수에 따라 수 초에서 수 분입니다. 클라이언트 타임아웃은 20분 이상 권장합니다.","parameters":[{"name":"x-clip-key","in":"header","required":false,"description":"이 조회는 키를 검사하지 않습니다. 같은 키로 보내는 것을 권장합니다.","schema":{"type":"string","description":"이 조회는 키를 검사하지 않습니다. 같은 키로 보내는 것을 권장합니다.","examples":["YOUR_API_KEY"]}},{"name":"job","in":"query","required":true,"description":"POST 응답의 jobId","schema":{"type":"string","description":"POST 응답의 jobId","examples":["9d4e1a7b-2c3f-4a6e-8b1d-5f0e7c9a2b11"]}}],"responses":{"200":{"description":"`status` 가 `pending`, `done`, `error`, `unknown` 중 하나입니다. `done` 일 때만 `result` 가 있습니다.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","done","error","unknown"],"description":"작업 상태"},"result":{"type":"object","description":"status 가 done 일 때의 결과","properties":{"documents":{"type":"array","description":"서류 목록(시작 쪽 순서). 범위는 대체로 앞 서류의 끝 다음 쪽부터 이어집니다. 다만 시작 쪽이 같은 두 서류가 있으면 그 쪽이 두 서류에 모두 들어갈 수 있습니다.","items":{"type":"object","properties":{"type":{"type":"string","description":"서류 종류 이름. 값은 고정 목록이 아닙니다."},"pageStart":{"type":"integer","description":"서류의 첫 쪽 번호"},"pageEnd":{"type":"integer","description":"서류의 마지막 쪽 번호(포함)"},"keys":{"type":"array","description":"이 서류의 핵심정보. classify 모드에서는 비어 있습니다. 여러 서류에 걸친 항목은 각 서류에 복제됩니다.","items":{"type":"object","description":"추출된 핵심정보 한 항목","properties":{"key":{"type":"string","description":"항목 이름(예: L/C No., Amount). 서류 종류마다 다릅니다."},"value":{"type":"string","description":"추출된 값 문자열. 값을 찾지 못하면 빈 문자열입니다."},"informationList":{"type":"array","items":{"type":"string"},"description":"여러 줄로 된 항목(예: 서류 목록, 조건 목록)의 줄 단위 값. 해당하지 않으면 생략됩니다."},"processed":{"type":"object","description":"금액·과부족처럼 구조화된 값(예: 금액은 currency·amount, 과부족은 plus·minus). 해당하지 않으면 생략됩니다."},"reference":{"type":"object","description":"값이 적힌 쪽 번호(문자열 키)와 단어 번호 목록. 번호로 words 에서 좌표를 찾습니다.","additionalProperties":{"type":"array","items":{"type":"integer"}}}},"required":["key","value","reference"]}}},"required":["type","pageStart","pageEnd","keys"]}},"words":{"type":"object","description":"근거 단어 좌표. 2단계 맵입니다. 바깥 키는 쪽 번호(문자열), 안쪽 키는 단어 번호(문자열), 값은 좌표입니다. classify 모드에서는 비어 있습니다.","additionalProperties":{"type":"object","additionalProperties":{"type":"array","items":{"type":"number"},"description":"단어의 좌표 [x0, y0, x1, y1]. 보통 쪽 크기에 대한 0~1 비율입니다."}}},"pageCount":{"type":"integer","description":"파일의 전체 쪽 수"}},"required":["documents","words","pageCount"]},"error":{"type":"string","description":"status 가 error 일 때의 오류 메시지"}},"required":["status"]},"example":{"status":"done","result":{"documents":[{"type":"letter of credit","pageStart":1,"pageEnd":2,"keys":[{"key":"L/C No.","value":"LC2026-0042","reference":{"1":[12]}}]},{"type":"commercial invoice","pageStart":3,"pageEnd":3,"keys":[{"key":"Invoice No.","value":"INV-2026-0931","reference":{"3":[40]}}]}],"words":{"1":{"12":[0.11,0.08,0.31,0.1]},"3":{"40":[0.2,0.5,0.4,0.52]}},"pageCount":3}}}}},"401":{"description":"처리 엔진이 키를 거부할 때. 상태 코드는 처리 엔진의 응답을 따르며, 본문의 status 는 error 입니다.","content":{"application/json":{"example":{"status":"error","error":"review 401"}}}},"404":{"description":"jobId 를 찾을 수 없을 때(잘못된 값, 이미 받은 값, 서버 재시작으로 사라진 값, 정리된 값). 본문의 status 가 unknown 입니다. / 처리 엔진이 파일을 찾지 못했을 때. 본문의 status 가 error 이므로 unknown 과 구분하십시오.","content":{"application/json":{"example":{"status":"unknown"}}}},"413":{"description":"파일 크기가 서버 설정의 한도를 넘을 때. 한도는 서버 설정에 따릅니다.","content":{"application/json":{"example":{"status":"error","error":"File size exceeds limit"}}}},"429":{"description":"요청이 너무 많을 때","content":{"application/json":{"example":{"status":"error","error":"review 429"}}}},"502":{"description":"처리 엔진에 연결하지 못했을 때, 또는 상태를 알 수 없을 때","content":{"application/json":{"example":{"status":"error","error":"fetch failed"}}}}}}},"/api/esg/check":{"post":{"tags":["ESG 서류 점검"],"operationId":"esg-check","summary":"서류 1건 판정","description":"증빙 문서 1건을 ESG 평가 문항에 대해 판정합니다.\n\n문서 한 건을 보내면 이 문서와 관련된 문항을 골라 판정하고, 문항별 의견과 근거를 돌려줍니다. 여러 문서와 실태표를 합친 최종 결과는 이 API 가 만들지 않으며, 연동하는 쪽에서 ‘문항별 최종 결과 산출’의 원칙으로 계산합니다.\n\n**저장 없는 모드(`noStore`, 베타)**: 기본 모드에서는 업로드한 원본이 서버 파일 저장소에 남습니다. `noStore=1` 을 보내면 원본을 저장소에 올리지 않고 판정만 합니다. 베타 기능이므로 운영 적용 전에 샘플 문서로 결과를 비교하는 것을 권장합니다.\n\n처리 시간: 문서 1건당 수 초~수 분이 걸립니다. 클라이언트 타임아웃은 20분 이상으로 잡는 것을 권장합니다.\n\n파일명에 `실태표`가 들어 있으면 증빙으로 판정하지 않고, `matches` 와 `unmet` 가 빈 배열인 HTTP 200 응답을 돌려줍니다. 실태표 답변은 파일로 올리지 말고 `declared` 필드로 보내십시오.\n\n웹 화면은 같은 파일명을 같은 문서로 취급합니다. 서로 다른 문서에는 서로 다른 파일명을 지정하는 것을 권장합니다. 응답의 `sources[].fileName` 으로 근거가 나온 문서를 구분할 수 있습니다.\n\n긴 문서는 판정 과정에서 근거를 추가로 찾기 때문에, 같은 문서라도 문항마다 `sources` 개수가 다를 수 있습니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","schema":{"type":"string","description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"판정할 증빙 문서 1건입니다. 같은 필드로 여러 파일을 보내면 첫 번째 파일만 처리합니다. 서버는 확장자와 크기를 검사하지 않으며, PDF, 이미지, Office(xlsx, docx, pptx), HWP 계열 문서를 대상으로 합니다."},"evalYear":{"type":"integer","description":"평가연도입니다. 1900보다 큰 정수만 반영되고 그 밖의 값은 무시됩니다. 기준연도 조건이 있는 문항(전체 문항의 절반 가까이)을 올바르게 판정하려면 보내는 것을 권장합니다.","default":"미지정","examples":["2025"]},"declared":{"type":"string","contentMediaType":"application/json","description":"실태표(자가신고) 답변입니다. 키는 문항 번호(`id`, 예 `037`), 값은 `결과번호. 선택지 문구` 형식입니다. 판별유형 ② 문항의 답만 판정에 쓰이며 나머지 키는 오류 없이 무시됩니다. 자세한 형식은 ‘실태표(declared) 보내기’를 참고하십시오.","default":"없음","examples":["{\"037\":\"1. 예, 전 직원에 대하여 성희롱 예방교육을 실시하고 있습니다.\"}"]},"noStore":{"type":"string","description":"(베타) `1` 또는 `true` 이면 저장 없는 모드로 판정합니다. 원본을 파일 저장소에 올리지 않고 색인도 만들지 않습니다. `0` 또는 `false` 이면 기본 모드입니다. 그 밖의 값은 서버 설정을 따릅니다.","default":"서버 설정"}},"required":["file"]}}}},"responses":{"200":{"description":"EsgCheckResponse. matches 는 충족으로 본 문항, unmet 는 나머지 판정 문항입니다. 읽는 방법은 ‘판정 결과 읽는 법’을 참고하십시오.","content":{"application/json":{"schema":{"type":"object","description":"문서 1건의 판정 결과","required":["fileName","documentId","matches","unmet","candidateCount","capped"],"properties":{"fileName":{"type":"string","description":"업로드한 파일의 이름입니다. 이름이 없으면 document 입니다."},"documentId":{"type":"string","description":"서버 내부 식별자입니다. 판정 직후 정리되므로 연동에서는 사용하지 않습니다. 저장 없는 모드와 실태표 파일 응답에서는 빈 문자열입니다."},"matches":{"type":"array","items":{"type":"object","description":"문항 1개에 대한 이 문서의 의견. matches 와 unmet 가 같은 구조입니다.","required":["id","group","groupName","question","reason","sources"],"properties":{"id":{"type":"string","description":"문항 번호(001~091, 3자리 문자열)입니다. 공식 지표번호가 아닙니다."},"group":{"type":"string","enum":["E","S","G","SF"],"description":"파트입니다. E 환경, S 사회, G 지배구조, SF 안전역량."},"groupName":{"type":"string","description":"파트 이름입니다."},"question":{"type":"string","description":"문항 원문입니다."},"evidenceKind":{"type":"string","enum":["기록","서술","실태표","없음"],"description":"근거의 종류입니다. 기록은 실제 수행 기록물(일지·대장·결과표·수료증·회의록 등), 서술은 규정·방침·계획의 문장, 실태표는 실태표 답변이 주된 근거, 없음은 이 문서에서 근거를 확인하지 못했다는 뜻입니다. 단일 선택 문항에서만 나올 수 있습니다."},"absentLevel":{"type":"integer","description":"규칙에 따라 level 을 비우기 전에 모델이 고른 단계입니다. level 이 없고 absentLevel 이 있으면 근거 부족으로 단계를 비운 것이며 부정 판정이 아닙니다."},"reason":{"type":"string","description":"판정 근거를 설명하는 자유 텍스트입니다. level 등이 비워진 경우에는 모델의 원래 응답 문장이 들어갑니다."},"sources":{"type":"array","items":{"type":"object","description":"판정 근거 구절 1개","required":["page","text","score"],"properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호입니다(보통 1부터). 업로드한 파일 기준입니다. 저장 모드에서 페이지 정보가 없으면 0 이 올 수 있습니다."},"text":{"type":"string","description":"근거 구절 원문입니다. 표는 HTML 또는 Markdown 문자열로 들어올 수 있으므로 화면에 넣을 때 이스케이프하십시오."},"score":{"type":"number","description":"근거 유사도(0~1)입니다. 상대 지표이며 0.5 미만이면 근거가 약할 수 있습니다. 저장 없는 모드에서는 문항 문구와의 글자 겹침률이라 의미가 다르므로 0.5 기준을 적용하지 마십시오."},"bbox":{"type":"array","items":{"type":"number"},"description":"근거 위치 [x1, y1, x2, y2]입니다. 각 값은 0~1로 정규화되어 있고 원점은 페이지 좌상단입니다. 위치를 알 수 없으면 없습니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류입니다(text, table, image, paragraph_title 등). 저장 없는 모드에서는 없습니다."},"fileName":{"type":"string","description":"근거가 나온 문서 파일명입니다(업로드한 파일 이름)."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"베타(저장 없는 모드). 줄 단위 위치 박스 [x1, y1, x2, y2]의 목록입니다."}}},"description":"근거 구절 목록입니다. 0개 이상이며 개수는 문항마다 다를 수 있습니다."},"level":{"type":"integer","description":"문서가 근거로 확인한 선택지 번호입니다(1이 가장 유리, 클수록 불리). 단일 선택 문항과 수치 문항에서만 채워집니다."},"levelText":{"type":"string","description":"level 에 해당하는 선택지 문구입니다. 접두로 판단하십시오. 예, 또는 네, 로 시작하면 긍정, 아니오로 시작하면 부정, 해당사항 없음으로 시작하면 평가 대상이 아닙니다."},"levelCount":{"type":"integer","description":"문항의 선택지 개수입니다. 체크리스트형 문항에서도 선택지 개수이며, levels 의 상한이 아닙니다. 항목 번호는 levelCount 를 넘을 수 있습니다."},"levels":{"type":"array","items":{"type":"integer"},"description":"체크리스트형 문항에서 이 문서가 입증한 항목 번호들(오름차순)입니다. 빈 배열이면 입증된 항목이 없다는 뜻입니다."},"levelLabels":{"type":"array","items":{"type":"string"},"description":"levels 와 같은 순서의 항목 이름입니다."},"values":{"type":"object","additionalProperties":{"type":["number","null"]},"description":"수치 문항의 값입니다. rate 는 대표값(예 산업재해율, %), avg 는 동종 업종 평균(%)입니다. 문서에서 읽지 못한 값은 null 입니다."}}},"description":"이 문서가 충족으로 본 문항들입니다."},"unmet":{"type":"array","items":{"type":"object","description":"문항 1개에 대한 이 문서의 의견. matches 와 unmet 가 같은 구조입니다.","required":["id","group","groupName","question","reason","sources"],"properties":{"id":{"type":"string","description":"문항 번호(001~091, 3자리 문자열)입니다. 공식 지표번호가 아닙니다."},"group":{"type":"string","enum":["E","S","G","SF"],"description":"파트입니다. E 환경, S 사회, G 지배구조, SF 안전역량."},"groupName":{"type":"string","description":"파트 이름입니다."},"question":{"type":"string","description":"문항 원문입니다."},"evidenceKind":{"type":"string","enum":["기록","서술","실태표","없음"],"description":"근거의 종류입니다. 기록은 실제 수행 기록물(일지·대장·결과표·수료증·회의록 등), 서술은 규정·방침·계획의 문장, 실태표는 실태표 답변이 주된 근거, 없음은 이 문서에서 근거를 확인하지 못했다는 뜻입니다. 단일 선택 문항에서만 나올 수 있습니다."},"absentLevel":{"type":"integer","description":"규칙에 따라 level 을 비우기 전에 모델이 고른 단계입니다. level 이 없고 absentLevel 이 있으면 근거 부족으로 단계를 비운 것이며 부정 판정이 아닙니다."},"reason":{"type":"string","description":"판정 근거를 설명하는 자유 텍스트입니다. level 등이 비워진 경우에는 모델의 원래 응답 문장이 들어갑니다."},"sources":{"type":"array","items":{"type":"object","description":"판정 근거 구절 1개","required":["page","text","score"],"properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호입니다(보통 1부터). 업로드한 파일 기준입니다. 저장 모드에서 페이지 정보가 없으면 0 이 올 수 있습니다."},"text":{"type":"string","description":"근거 구절 원문입니다. 표는 HTML 또는 Markdown 문자열로 들어올 수 있으므로 화면에 넣을 때 이스케이프하십시오."},"score":{"type":"number","description":"근거 유사도(0~1)입니다. 상대 지표이며 0.5 미만이면 근거가 약할 수 있습니다. 저장 없는 모드에서는 문항 문구와의 글자 겹침률이라 의미가 다르므로 0.5 기준을 적용하지 마십시오."},"bbox":{"type":"array","items":{"type":"number"},"description":"근거 위치 [x1, y1, x2, y2]입니다. 각 값은 0~1로 정규화되어 있고 원점은 페이지 좌상단입니다. 위치를 알 수 없으면 없습니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류입니다(text, table, image, paragraph_title 등). 저장 없는 모드에서는 없습니다."},"fileName":{"type":"string","description":"근거가 나온 문서 파일명입니다(업로드한 파일 이름)."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"베타(저장 없는 모드). 줄 단위 위치 박스 [x1, y1, x2, y2]의 목록입니다."}}},"description":"근거 구절 목록입니다. 0개 이상이며 개수는 문항마다 다를 수 있습니다."},"level":{"type":"integer","description":"문서가 근거로 확인한 선택지 번호입니다(1이 가장 유리, 클수록 불리). 단일 선택 문항과 수치 문항에서만 채워집니다."},"levelText":{"type":"string","description":"level 에 해당하는 선택지 문구입니다. 접두로 판단하십시오. 예, 또는 네, 로 시작하면 긍정, 아니오로 시작하면 부정, 해당사항 없음으로 시작하면 평가 대상이 아닙니다."},"levelCount":{"type":"integer","description":"문항의 선택지 개수입니다. 체크리스트형 문항에서도 선택지 개수이며, levels 의 상한이 아닙니다. 항목 번호는 levelCount 를 넘을 수 있습니다."},"levels":{"type":"array","items":{"type":"integer"},"description":"체크리스트형 문항에서 이 문서가 입증한 항목 번호들(오름차순)입니다. 빈 배열이면 입증된 항목이 없다는 뜻입니다."},"levelLabels":{"type":"array","items":{"type":"string"},"description":"levels 와 같은 순서의 항목 이름입니다."},"values":{"type":"object","additionalProperties":{"type":["number","null"]},"description":"수치 문항의 값입니다. rate 는 대표값(예 산업재해율, %), avg 는 동종 업종 평균(%)입니다. 문서에서 읽지 못한 값은 null 입니다."}}},"description":"충족이 아니거나 근거를 찾지 못한 문항들입니다. 부정 단계, 해당사항 없음 단계, 근거 없음이 섞여 있으므로 levelText 와 level 로 구분하십시오."},"candidateCount":{"type":"integer","description":"matches 와 unmet 의 개수 합입니다. 이 문서와 관련되어 판정한 문항 수입니다."},"capped":{"type":"boolean","description":"파트별 판정 상한에 걸려 일부 문항이 빠졌는지 나타냅니다."},"summary":{"type":"string","description":"문서 요약(두 문장 이내)입니다. 요약을 만들지 못한 경우에는 없습니다."}}},"example":{"fileName":"2025_환경정책서.pdf","documentId":"","matches":[{"id":"001","group":"E","groupName":"환경 (Environmental)","question":"문서화된 환경정책을 보유하고 있습니까?","evidenceKind":"기록","reason":"환경경영방침 문서에 에너지 사용 절감과 폐기물 재활용 목표 및 실천방안이 명시되어 있습니다.","sources":[{"page":2,"text":"제1조(목적) 본 방침은 에너지 사용 절감과 폐기물 재활용 목표를 정하고 이를 실천하기 위한 사항을 정합니다.","score":0.92,"bbox":[0.08,0.21,0.91,0.34],"layoutClass":"text","fileName":"2025_환경정책서.pdf"}],"level":1,"levelText":"예, 정책을 보유하고 있습니다.","levelCount":2},{"id":"033","group":"S","groupName":"사회 (Social)","question":"근로계약서에 근로자 임금의 구성항목, 계산방법, 지급방법 등을 구체적으로 명시하고 있습니까?","reason":"근로계약서에 임금 구성항목과 지급일이 명시되어 있으며 계산방법은 확인되지 않습니다.","sources":[{"page":1,"text":"제5조(임금) 임금은 기본급, 직책수당으로 구성되며 매월 25일에 지급합니다.","score":0.74,"bbox":[0.1,0.43,0.9,0.47],"layoutClass":"text","fileName":"2025_환경정책서.pdf"}],"levels":[1,3],"levelLabels":["임금 구성항목(기본급·수당 등)","임금 지급방법(지급일·지급 수단)"],"levelCount":3}],"unmet":[{"id":"037","group":"S","groupName":"사회 (Social)","question":"전 임직원에 대하여 성희롱 예방 교육을 실시하고 있습니까?","evidenceKind":"기록","reason":"교육 이수 현황표에 일부 부서의 이수 기록만 확인됩니다.","sources":[{"page":7,"text":"2025년 성희롱 예방교육 이수 현황(영업본부 이수 완료, 생산본부 미실시)","score":0.61,"bbox":[0.12,0.3,0.88,0.36],"layoutClass":"table","fileName":"2025_환경정책서.pdf"}],"level":2,"levelText":"아니오, 일부 직원에 대하여 실시하고 있습니다.","levelCount":3}],"candidateCount":3,"capped":false,"summary":"ESG 환경정책과 임직원 교육 현황을 담은 문서입니다."}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때입니다. / 내부 처리 단계가 400 을 돌려준 때입니다. 본문 메시지로 원인을 확인하십시오.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때입니다. 본문을 읽기 전에 검사합니다. / 키는 보냈지만 키가 유효하지 않거나 비활성 상태일 때입니다. 메시지의 접두는 실패한 단계에 따라 다릅니다(저장 모드는 파일 업로드 실패, 저장 없는 모드는 문서 OCR 실패).","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 필요한 권한이 없을 때입니다(저장 모드는 files, 저장 없는 모드는 ocr 권한). 키 만료, IP 제한, 파일 저장소 비활성도 여기에 해당합니다.","content":{"application/json":{"example":{"error":"파일 업로드 실패: Permission denied","status":403}}}},"413":{"description":"저장 없는 모드에서 파일 크기나 페이지 수가 서버 제한을 넘었을 때입니다. 메시지 언어는 설정에 따라 다를 수 있습니다.","content":{"application/json":{"example":{"error":"문서 OCR 실패: File size exceeds limit","status":413}}}},"422":{"description":"저장 모드에서 색인 결과에 추출된 내용이 0개일 때입니다(문서에 읽을 텍스트가 없는 경우). / 저장 없는 모드에서 문서에서 추출된 내용이 없을 때입니다.","content":{"application/json":{"example":{"error":"qna 색인 element가 0개입니다(추출된 내용 없음).","status":422}}}},"429":{"description":"키의 분당 또는 일일 호출 한도를 넘었을 때입니다. 잠시 기다린 뒤 재시도하십시오.","content":{"application/json":{"example":{"error":"qna 요청 실패: Rate limit exceeded","status":429}}}},"500":{"description":"multipart/form-data 형식이 아니거나 본문이 손상되어 요청을 읽지 못했을 때입니다. 본문 없이 프레임워크 기본 응답이 나옵니다.","content":{"application/json":{"example":"(본문 없음)"}}},"502":{"description":"서버가 내부 처리 서비스에 연결하지 못했을 때입니다. 잠시 후 재시도하십시오.","content":{"application/json":{"example":{"error":"qna 서버 연결 실패: fetch failed","status":502}}}},"503":{"description":"내부 처리 서비스를 일시적으로 쓸 수 없을 때입니다.","content":{"application/json":{"example":{"error":"qna 색인 실패: QnA service not available","status":503}}}},"504":{"description":"처리 시간이 서버 제한을 넘었을 때입니다.","content":{"application/json":{"example":{"error":"qna 요청 실패: Upstream timeout","status":504}}}}}}},"/api/esg/recheck":{"post":{"tags":["ESG 서류 점검"],"operationId":"esg-recheck","summary":"실태표 기준 재검토","description":"실태표 답변이 유리한 문항의 근거를 문서 한 건에 대해 다시 확인합니다.\n\n문서 판정보다 실태표 답변이 유리한 문항에 한해 호출하는 선택 기능입니다. 한 번에 문항 하나와 문서 한 건을 보냅니다. 응답의 `level` 을 채택할지는 호출하는 쪽이 정합니다(‘문항별 최종 결과 산출’ 6단계).\n\n처리 시간: 문항당 수 초~수십 초가 걸립니다. 클라이언트 타임아웃은 5분 이상으로 잡는 것을 권장합니다.\n\n`declaredLevel` 은 반드시 보내십시오. `priorLevel` 은 1차 판정 단계가 있을 때만 1 이상의 번호로 보내십시오. 생략하면 0 으로 처리됩니다.\n\n재검토 결과의 sources 에는 fileName 이 없습니다. 근거 문서는 응답 최상위의 fileName 으로 확인하십시오.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","schema":{"type":"string","description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"근거를 다시 확인할 문서 1건입니다. 보통 같은 문항의 1차 판정에서 근거가 있었던 파일을 먼저 보내고, 수용되지 않으면 문서 요약이 그 문항의 증빙 서류와 맞는 다른 문서를 순서대로 보냅니다(문항당 최대 3개 문서)."},"item":{"type":"string","description":"재검토할 문항 번호(예 `037`)입니다. 공식 지표번호(예 `S-002-007-001`)는 받지 않습니다. 판정 대상이 아닌 번호는 502 로 응답합니다.","examples":["037"]},"declaredText":{"type":"string","description":"실태표 답변 문구입니다. `결과번호. 선택지 문구` 형식으로 보내십시오.","examples":["1. 예, 전 직원에 대하여 성희롱 예방교육을 실시하고 있습니다."]},"declaredLevel":{"type":"integer","description":"실태표가 고른 선택지 번호입니다(1부터). 반드시 보내십시오. 생략하거나 빈 값이면 0 으로 처리되어 재확인이 제대로 되지 않습니다. 숫자가 아니거나 정수가 아닌 값은 400 으로 응답합니다.","examples":["1"]},"evalYear":{"type":"integer","description":"평가연도입니다. 판정 요청과 같은 값을 보내십시오.","default":"미지정","examples":["2025"]},"priorLevel":{"type":"integer","description":"1차 문서 판정의 선택지 번호입니다(1 이상). 1차 판정 단계가 있을 때만 보내십시오. 생략하면 0 으로 처리되며, 0 은 1차 판정 단계가 없다는 뜻이 아닙니다.","default":"없음","examples":["2"]},"priorReason":{"type":"string","description":"1차 문서 판정의 reason 문장입니다. 있으면 판정 프롬프트에 참고 사유로 들어갑니다.","default":"없음"},"noStore":{"type":"string","description":"(베타) 판정 요청의 noStore 와 같은 의미입니다.","default":"서버 설정"}},"required":["file","item","declaredText","declaredLevel"]}}}},"responses":{"200":{"description":"재검토 결과입니다. level 이 null 이면 문서에서 근거를 찾지 못한 것이므로 1차 판정을 유지하십시오. level 이 있으면 문서 근거가 확인된 선택지 번호입니다.","content":{"application/json":{"schema":{"type":"object","description":"재검토 결과","required":["level","reason","sources","fileName"],"properties":{"level":{"type":["integer","null"],"description":"문서에서 확인된 선택지 번호입니다. 근거를 찾지 못했으면 null 이며, 이 경우 1차 판정을 유지하면 됩니다."},"reason":{"type":"string","description":"재검토 근거 또는 근거를 찾지 못한 이유를 설명하는 텍스트입니다."},"sources":{"type":"array","items":{"type":"object","description":"판정 근거 구절 1개","required":["page","text","score"],"properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호입니다(보통 1부터). 업로드한 파일 기준입니다. 저장 모드에서 페이지 정보가 없으면 0 이 올 수 있습니다."},"text":{"type":"string","description":"근거 구절 원문입니다. 표는 HTML 또는 Markdown 문자열로 들어올 수 있으므로 화면에 넣을 때 이스케이프하십시오."},"score":{"type":"number","description":"근거 유사도(0~1)입니다. 상대 지표이며 0.5 미만이면 근거가 약할 수 있습니다. 저장 없는 모드에서는 문항 문구와의 글자 겹침률이라 의미가 다르므로 0.5 기준을 적용하지 마십시오."},"bbox":{"type":"array","items":{"type":"number"},"description":"근거 위치 [x1, y1, x2, y2]입니다. 각 값은 0~1로 정규화되어 있고 원점은 페이지 좌상단입니다. 위치를 알 수 없으면 없습니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류입니다(text, table, image, paragraph_title 등). 저장 없는 모드에서는 없습니다."},"fileName":{"type":"string","description":"근거가 나온 문서 파일명입니다(업로드한 파일 이름)."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"베타(저장 없는 모드). 줄 단위 위치 박스 [x1, y1, x2, y2]의 목록입니다."}}},"description":"근거 구절 목록입니다. 문서 판정의 sources 와 같은 구조이지만 fileName 은 없습니다."},"fileName":{"type":"string","description":"업로드한 파일의 이름입니다."}}},"example":{"level":1,"reason":"2025년 교육 이수 현황표에 전 부서 이수 완료가 확인됩니다.","sources":[{"page":3,"text":"2025년 성희롱 예방교육 이수 현황: 전 부서 이수 완료(이수율 100%)","score":0.88,"bbox":[0.1,0.22,0.9,0.28],"layoutClass":"table"}],"fileName":"2025_성희롱예방교육이수현황.xlsx"}}}},"400":{"description":"file, item, declaredText 중 하나가 없거나 비었을 때, 또는 declaredLevel 이 숫자로 바꿀 수 없거나 정수가 아닐 때(예 abc, 1.5)입니다. declaredLevel 을 생략하면 이 오류 없이 0 으로 처리됩니다.","content":{"application/json":{"example":{"error":"file, item, declaredText, declaredLevel are required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때입니다. / x-clip-key 는 보냈지만 키가 유효하지 않거나 비활성 상태일 때입니다. 상위 서비스의 상태 코드와 메시지가 그대로 전달됩니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 권한이 없거나 만료·IP 제한에 걸렸을 때입니다. 상위 서비스의 상태 코드가 그대로 전달됩니다.","content":{"application/json":{"example":{"error":"Permission denied","status":403}}}},"413":{"description":"문서의 크기나 페이지 수가 서버 제한을 넘었을 때입니다.","content":{"application/json":{"example":{"error":"File size exceeds limit","status":413}}}},"429":{"description":"키의 호출 한도를 넘었을 때입니다. 잠시 기다린 뒤 재시도하십시오.","content":{"application/json":{"example":{"error":"qna 요청 실패: Rate limit exceeded","status":429}}}},"500":{"description":"multipart/form-data 형식이 아니거나 본문이 손상되었을 때입니다.","content":{"application/json":{"example":"(본문 없음)"}}},"502":{"description":"item 이 판정 대상 문항 번호가 아닐 때입니다. 입력 오류지만 502 로 응답합니다. / 내부 처리 서비스에 연결하지 못했을 때입니다. 잠시 후 재시도하십시오. 그 밖의 상위 서비스 오류도 502 로 전달될 수 있습니다.","content":{"application/json":{"example":{"error":"unknown item: 999","status":502}}}}}}},"/api/esg/page-image":{"post":{"tags":["ESG 서류 점검"],"operationId":"esg-page-image","summary":"근거 페이지 이미지","description":"Office·HWP 문서의 페이지 이미지를 받아 근거 위치를 표시합니다.\n\nxlsx, docx, pptx, hwp 등 브라우저가 직접 그리지 못하는 문서의 페이지 이미지를 돌려줍니다. 판정은 하지 않습니다. 판정 응답의 `sources[].bbox` 는 이 이미지 기준의 좌표이므로, Office·HWP 문서의 근거는 반드시 이 이미지 위에 그려야 맞습니다. PDF 와 이미지 파일은 원본을 그대로 쓰면 됩니다.\n\n처리 시간: 문서 전체를 렌더링하므로 페이지 수에 따라 수 초~수 분이 걸립니다. 같은 파일은 한 번만 요청하고 결과를 캐시하는 것을 권장합니다.\n\n이 API 는 키에 DLP 권한이 있어야 합니다. 권한이 없으면 403 으로 응답합니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","schema":{"type":"string","description":"API 키입니다. 모든 요청에 보내십시오. 서버 쪽에서만 보관하고 브라우저 코드에 넣지 마십시오.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"페이지 이미지를 만들 문서 1건입니다. 페이지 선택 옵션은 없으며 문서 전체를 렌더링합니다."}},"required":["file"]}}}},"responses":{"200":{"description":"페이지 번호별 이미지입니다. 큰 문서는 응답 크기가 수 MB 에서 수십 MB 가 될 수 있습니다.","content":{"application/json":{"schema":{"type":"object","description":"페이지 이미지 묶음","required":["pageCount","pages"],"properties":{"pageCount":{"type":"integer","description":"문서의 페이지 수입니다."},"pages":{"type":"object","additionalProperties":{"type":"string"},"description":"페이지 번호(문자열 \"1\"부터)를 키로, 페이지 이미지 데이터 URL(JPEG)을 값으로 하는 객체입니다. 이미지가 없는 페이지는 빠집니다."}}},"example":{"pageCount":2,"pages":{"1":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...","2":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."}}}}},"400":{"description":"file 필드가 없거나 파일이 아닐 때입니다.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때입니다. / x-clip-key 는 보냈지만 키가 유효하지 않거나 비활성 상태일 때입니다. 상위 서비스의 상태 코드가 그대로 전달됩니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 DLP 권한이 없을 때입니다. 상위 서비스의 상태 코드와 메시지가 그대로 전달됩니다.","content":{"application/json":{"example":{"error":"Permission denied"}}}},"413":{"description":"문서의 크기나 페이지 수가 서버 제한을 넘었을 때입니다. 상위 서비스의 응답을 그대로 전달합니다.","content":{"application/json":{"example":{"error":"File size exceeds limit"}}}},"500":{"description":"multipart/form-data 형식이 아니거나 본문이 손상되었을 때입니다.","content":{"application/json":{"example":"(본문 없음)"}}},"502":{"description":"내부 처리 서비스에 연결하지 못했거나 응답을 해석하지 못했을 때입니다.","content":{"application/json":{"example":{"error":"Upstream error: fetch failed"}}}}}}},"/api/construct/check":{"post":{"tags":["건설안전보건 평가 서류 점검"],"operationId":"construct-check","summary":"서류 1건 일괄 점검","description":"서류 1건을 37개 문항 전체에 대해 점검하고, 충족 문항과 미충족 문항을 돌려줍니다.\n\n서류 1건을 올리면 서버가 먼저 이 서류가 근거가 될 수 있는 문항을 골라내고, 고른 문항마다 판단기준 충족 여부를 판정합니다. 충족한 문항은 `matches`, 후보였지만 미충족인 문항은 `unmet`에 담깁니다. 후보에 들지 않은 문항은 응답에 나오지 않습니다.\n\n여러 서류의 결과를 합쳐 문항별 최종 상태를 내는 일은 호출하는 쪽의 몫입니다. 자세한 규칙은 “문항별 최종 결과 산출” 섹션을 참고하세요.\n\n처리 시간: 문서 1건당 수 초에서 수 분이 걸립니다. 클라이언트 타임아웃은 20분 이상으로 잡아 주세요.\n\n- 한 요청에는 서류 1건만 보냅니다. 여러 건은 파일마다 따로 호출하세요.\n- 5xx 와 네트워크 오류는 2초, 4초 간격으로 최대 2회 재시도하는 것을 권장합니다. 4xx 는 요청을 고치기 전에는 재시도해도 같은 결과가 나오므로 재시도하지 마세요.\n- 같은 파일을 다시 보내면 판정과 저장 작업이 중복됩니다. 응답을 받지 못했을 때에만 재전송하세요. 멱등 키는 지원하지 않습니다.\n- 동시에 많이 보내면 서버 대기열에 쌓여 응답이 느려질 수 있습니다. 동시 업로드 수를 적당히 제한하세요.\n- 저장 모드에서는 원본 사본이 문서 저장소에 남고 자동으로 지워지지 않습니다. 사본을 남기고 싶지 않다면 noStore 를 1 로 보내세요.\n\n예제의 JavaScript 는 최상위 `await` 와 `import` 를 씁니다. `.mjs` 파일로 저장하거나 `package.json` 에 `\"type\": \"module\"` 을 설정한 환경에서 실행하세요.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키. 모든 요청에 넣어야 하며, 생략하면 401(api_key_required)이 옵니다.","schema":{"type":"string","description":"발급받은 API 키. 모든 요청에 넣어야 하며, 생략하면 401(api_key_required)이 옵니다.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"점검할 서류 1건. 파일 파트로 보내야 하며, 파일이 없거나 일반 텍스트 필드로 보내면 400 이 나옵니다. 파일 형식과 크기는 서버가 따로 검사하지 않고, 처리 엔진이 읽을 수 있는 형식만 통과합니다. 여러 파일 파트를 보내면 첫 번째만 처리합니다. 파일명(확장자 포함)은 응답 fileName 으로 돌아오니 원래 파일명을 그대로 보내 주세요.","examples":["@안전보건방침.pdf"]},"noStore":{"type":"string","description":"[베타] 저장 없는 모드 여부. 1 또는 true 이면 원본 파일을 문서 저장소에 올리지 않고 색인도 만들지 않습니다. 텍스트와 단어 위치만 추출해 판정합니다. 0 또는 false 이면 서버 설정과 관계없이 저장 모드로 처리합니다. 대소문자와 앞뒤 공백은 무시합니다. bbox·boxes 가 비는 경우가 있으며(페이지 치수 정보가 없는 문서, 예: 단일 이미지나 변환 문서), 이는 저장 모드에서도 같습니다. 저장 없는 모드에서는 근거 점수(score) 계산 방식도 저장 모드와 다릅니다.","default":"서버 설정을 따릅니다(기본은 저장 모드)","examples":["1"]}},"required":["file"]}}}},"responses":{"200":{"description":"서류 1건에 대한 점검 결과입니다. 판정이 끝난 문항만 matches·unmet 에 담깁니다. 후보 선별 단계가 빈 결과를 내면 candidateCount 가 0 이고 matches·unmet 가 모두 빈 200 응답이 나올 수 있습니다. 이 경우는 해당 없음이 아니라 재시도가 필요한 결과일 수 있습니다. 이 응답에는 문항별 최종 상태가 없고, 서류가 여러 건이면 호출하는 쪽에서 합칩니다.","content":{"application/json":{"schema":{"type":"object","description":"서류 1건을 37개 문항 전체에 대해 점검한 결과","properties":{"fileName":{"type":"string","description":"업로드한 파일명. 파일명이 비어 있었다면 document 입니다."},"documentId":{"type":"string","description":"사용하지 않는 값입니다. 다른 호출에 쓰지 마세요. 저장 없는 모드에서는 빈 문자열입니다."},"matches":{"type":"array","items":{"type":"object","description":"문항 판정 결과 1건","properties":{"id":{"type":"string","description":"문항번호(예: 1-1, 10-5). 아래 문항 목록의 37개 중 하나입니다."},"group":{"type":"string","description":"문항 그룹 번호(Q1 ~ Q10)"},"groupName":{"type":"string","description":"문항 그룹 이름(목표 및 경영방침, 성과측정, 교육, 조직, 위험성평가, 예산 및 인증, 협의체, 재해 관리, 법정 교육, 관리자 선임 중 하나)"},"question":{"type":"string","description":"문항 문구"},"reason":{"type":"string","description":"판정 사유. 보통 첫 줄에 충족 또는 미충족이 오고 다음 줄에 근거 한 문장이 옵니다. 형식은 보장되지 않으므로 충족 여부는 matches 또는 unmet 중 어디에 들어 있는지로 판단하세요."},"sources":{"type":"array","items":{"type":"object","description":"근거 구절. 0개에서 여러 개까지 올 수 있으며 순서와 개수는 보장되지 않습니다.","properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호(1부터 시작)"},"text":{"type":"string","description":"근거 구절 텍스트. 문항별 점검에서는 모델이 인용한 문장이라 원문과 다를 수 있습니다."},"score":{"type":"number","description":"근거 점수(대체로 0~1). 저장 모드에서는 검색 유사도이고, 저장 없는 모드에서는 계산 방식이 다릅니다. 문항별 점검의 빠른 경로에서는 항상 1 입니다."},"bbox":{"type":"array","items":{"type":"number"},"description":"[x1, y1, x2, y2] 형식의 근거 위치. 페이지 가로·세로 대비 0~1 정규화 좌표이며 좌상단이 원점입니다. 위치를 찾지 못하면 없습니다."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"줄 단위 위치 목록(각 항목은 [x1, y1, x2, y2] 정규화 좌표). 위치를 찾지 못하면 빈 배열입니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류(예: text, table). 없을 수 있습니다."},"fileName":{"type":"string","description":"근거가 나온 문서명. 없을 수 있습니다."}},"required":["page","text","score"]},"description":"근거 구절 목록"}},"required":["id","group","groupName","question","reason","sources"]},"description":"서류가 판단기준을 충족한다고 판정된 문항 목록"},"unmet":{"type":"array","items":{"type":"object","description":"문항 판정 결과 1건","properties":{"id":{"type":"string","description":"문항번호(예: 1-1, 10-5). 아래 문항 목록의 37개 중 하나입니다."},"group":{"type":"string","description":"문항 그룹 번호(Q1 ~ Q10)"},"groupName":{"type":"string","description":"문항 그룹 이름(목표 및 경영방침, 성과측정, 교육, 조직, 위험성평가, 예산 및 인증, 협의체, 재해 관리, 법정 교육, 관리자 선임 중 하나)"},"question":{"type":"string","description":"문항 문구"},"reason":{"type":"string","description":"판정 사유. 보통 첫 줄에 충족 또는 미충족이 오고 다음 줄에 근거 한 문장이 옵니다. 형식은 보장되지 않으므로 충족 여부는 matches 또는 unmet 중 어디에 들어 있는지로 판단하세요."},"sources":{"type":"array","items":{"type":"object","description":"근거 구절. 0개에서 여러 개까지 올 수 있으며 순서와 개수는 보장되지 않습니다.","properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호(1부터 시작)"},"text":{"type":"string","description":"근거 구절 텍스트. 문항별 점검에서는 모델이 인용한 문장이라 원문과 다를 수 있습니다."},"score":{"type":"number","description":"근거 점수(대체로 0~1). 저장 모드에서는 검색 유사도이고, 저장 없는 모드에서는 계산 방식이 다릅니다. 문항별 점검의 빠른 경로에서는 항상 1 입니다."},"bbox":{"type":"array","items":{"type":"number"},"description":"[x1, y1, x2, y2] 형식의 근거 위치. 페이지 가로·세로 대비 0~1 정규화 좌표이며 좌상단이 원점입니다. 위치를 찾지 못하면 없습니다."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"줄 단위 위치 목록(각 항목은 [x1, y1, x2, y2] 정규화 좌표). 위치를 찾지 못하면 빈 배열입니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류(예: text, table). 없을 수 있습니다."},"fileName":{"type":"string","description":"근거가 나온 문서명. 없을 수 있습니다."}},"required":["page","text","score"]},"description":"근거 구절 목록"}},"required":["id","group","groupName","question","reason","sources"]},"description":"후보로 골랐지만 미충족으로 판정된 문항 목록. 이 서류로는 충족되지 않았다는 뜻이며, 문항 자체가 미충족으로 확정된 것은 아닙니다. 판정 호출이 실패한 문항이 여기에 들어올 수도 있으니 reason 이 비어 있으면 재시도를 고려하세요."},"candidateCount":{"type":"integer","description":"후보로 고른 문항 수(matches 개수와 unmet 개수의 합). 후보에 들지 않은 문항은 응답에 나오지 않습니다. 0 이면 matches·unmet 가 모두 빈 값이며, 이는 해당 없음이 아니라 후보 선별이 실패했을 수도 있다는 뜻입니다."},"capped":{"type":"boolean","description":"후보 수가 상한에 걸려 일부 문항이 빠졌는지 여부. 현재 문항 구성에서는 보통 false 입니다."},"summary":{"type":"string","description":"서류 요약(한국어 2문장 이내). 표시용이며 판정에는 쓰이지 않습니다. 요약 기능이 꺼져 있거나 요약 생성에 실패하면 필드가 없거나 빈 문자열일 수 있습니다."}},"required":["fileName","documentId","matches","unmet","candidateCount","capped"]},"example":{"fileName":"안전보건방침.pdf","documentId":"","matches":[{"id":"1-1","group":"Q1","groupName":"목표 및 경영방침","question":"안전보건 목표(or 경영방침)을 보유하고 있는가?","reason":"충족\n2026년 본사 안전보건 경영방침이 명시되어 있습니다.","sources":[{"page":1,"text":"2026년 안전보건 경영방침 ...","score":0.62,"bbox":[0.08,0.12,0.91,0.2],"boxes":[[0.08,0.12,0.91,0.2]],"layoutClass":"text"}]}],"unmet":[{"id":"2-1","group":"Q2","groupName":"성과측정","question":"안전보건 추진계획에 대한 성과측정을 주기적으로 시행하고 있는가?","reason":"미충족\n성과측정 결과나 점검 내용이 포함되어 있지 않습니다.","sources":[]}],"candidateCount":2,"capped":false,"summary":"2026년 본사 안전보건 경영방침을 담은 문서입니다."}}}},"400":{"description":"요청에 file 파트가 없거나, file 을 파일이 아닌 일반 텍스트 필드로 보냈을 때. curl 은 -F \"file=@경로\" 로 보내야 합니다.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문을 읽기 전에 확인합니다. / 헤더로 보낸 키가 존재하지 않거나 비활성화되었을 때. 처리 단계 이름이 앞에 붙고 status 가 함께 옵니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 필요한 권한이 없거나, 키가 만료되었거나, 키의 IP 제한에 걸렸을 때. 저장 모드에서는 파일 저장 권한과 OCR 권한이, 저장 없는 모드에서는 OCR 권한이 필요합니다. 본문 문구는 처리 경로에 따라 조금 다를 수 있습니다.","content":{"application/json":{"example":{"error":"파일 업로드 실패: Permission denied","status":403}}}},"413":{"description":"파일 크기나 페이지 수가 키에 설정된 한도를 넘었을 때. 한도 값은 키마다 다르며, 본문의 처리 단계 이름은 처리 경로에 따라 다를 수 있습니다.","content":{"application/json":{"example":{"error":"파일 업로드 실패: File size exceeds limit","status":413}}}},"422":{"description":"저장 모드에서 문서에서 추출할 내용이 없을 때(빈 문서이거나 글자를 인식할 수 없는 경우). / 저장 없는 모드(noStore=1)에서 문서에서 추출할 내용이 없을 때.","content":{"application/json":{"example":{"error":"qna 색인 element가 0개입니다(추출된 내용 없음).","status":422}}}},"429":{"description":"키의 분당 또는 일일 호출 한도를 처리 중에 넘었을 때. 잠시 뒤 다시 시도하세요. 본문의 error 문구는 처리 경로에 따라 다르니 status 값으로 판단하세요.","content":{"application/json":{"example":{"error":"배치 LLM 요청 실패: 429","status":429}}}},"500":{"description":"짧은 문서의 판정 단계에서 모델 호출이 실패했을 때. 단계 이름과 상세 사유가 함께 옵니다. 5xx 이므로 잠시 뒤 재시도할 수 있습니다. / multipart/form-data 가 아닌 Content-Type 으로 보냈을 때. 이 경우 JSON 본문 없이 기본 500 응답이 옵니다.","content":{"application/json":{"example":{"error":"LLM 요청 실패: ...","status":500}}}},"502":{"description":"서버가 처리 서버에 연결하지 못했을 때. / 긴 문서의 판정 단계에서 모델 응답이 실패했을 때. 5xx 이므로 잠시 뒤 재시도할 수 있습니다.","content":{"application/json":{"example":{"error":"파일 업로드 연결 실패: fetch failed","status":502}}}},"504":{"description":"내부 처리가 시간 제한을 넘었을 때. 처리 서버의 시간 제한에 걸린 경우도 여기에 해당합니다.","content":{"application/json":{"example":{"error":"Upstream timeout: ...","status":504}}}}}}},"/api/construct/item-check":{"post":{"tags":["건설안전보건 평가 서류 점검"],"operationId":"construct-item-check","summary":"서류 1건 문항별 점검","description":"서류 1건을 지정한 문항 1개에 대해서만 판정하고, 충족 여부와 근거를 돌려줍니다.\n\n문항을 직접 지정해 판정합니다. 일괄 점검과 달리 후보를 고르는 단계를 거치지 않으므로, 서류와 문항의 대응을 이미 알고 있을 때 씁니다. 응답은 `passed`(충족 여부)와 판정 상세인 `match` 입니다.\n\n처리 시간: 보통 수 초 안에 응답합니다. 문서가 매우 길면 더 오래 걸릴 수 있으니 클라이언트 타임아웃은 20분 이상으로 잡아 주세요.\n\n- 서류와 문항이 맞는지는 검증하지 않습니다. 엉뚱한 서류를 보내도 오류 없이 passed 가 false 로 옵니다.\n- 한 문항에 서류가 여러 건이면 파일마다 호출하고, passed 가 하나라도 true 이면 그 문항은 충족 후보입니다. 충족 판단 기준은 “문항별 최종 결과 산출” 섹션을 따르세요.\n- reason 이 빈 문자열이고 sources 도 비어 있으면 판정 호출이 실패했을 수 있습니다. passed 가 false 라도 미충족으로 확정하지 말고, 같은 요청을 다시 보내 확인하세요.\n- 5xx 와 네트워크 오류는 재시도를 권장하고, 4xx 는 재시도하지 마세요. 재전송하면 저장 모드에서는 원본 사본이 다시 쌓입니다.\n- sources[0].text 는 모델이 인용한 문장이라 원문과 다를 수 있습니다. 위치(bbox 또는 boxes)가 있을 때에만 원문 위치로 믿고 표시하세요. 빠른 경로(indexed 가 false)에서 score 는 항상 1 이므로 신뢰도로 쓰지 마세요.\n- 빠른 경로 응답에는 summary 가 없습니다.\n\n예제의 JavaScript 는 최상위 `await` 와 `import` 를 씁니다. `.mjs` 파일로 저장하거나 `package.json` 에 `\"type\": \"module\"` 을 설정한 환경에서 실행하세요.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키. 모든 요청에 넣어야 하며, 생략하면 401(api_key_required)이 옵니다.","schema":{"type":"string","description":"발급받은 API 키. 모든 요청에 넣어야 하며, 생략하면 401(api_key_required)이 옵니다.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"지정한 문항의 근거로 확인할 서류 1건. 파일 파트로 보내야 하며, 없으면 400 이 나옵니다. 이 검사는 item 검사보다 먼저 합니다.","examples":["@선임계.pdf"]},"item":{"type":"string","description":"판정할 문항번호. 예: 10-1. 아래 부록 “문항 목록”의 문항번호 37개 중 하나와 정확히 일치해야 하며, 앞뒤 공백은 허용되지 않습니다. 빈 값이면 400, 목록에 없는 값이면 502 가 나옵니다. 문항번호 형식(예: 10-1)이 아닌 값은 사용할 수 없습니다.","examples":["10-1"]},"noStore":{"type":"string","description":"[베타] 저장 없는 모드 여부. 1 또는 true 이면 원본을 문서 저장소에 올리지 않고 판정만 합니다. 0 또는 false 이면 저장 모드로 처리합니다. 대소문자와 앞뒤 공백은 무시합니다. 이 모드에서 응답의 indexed 는 항상 false 입니다.","default":"서버 설정을 따릅니다(기본은 저장 모드)","examples":["1"]}},"required":["file","item"]}}}},"responses":{"200":{"description":"판정 결과입니다. passed 가 true 이면 이 서류가 지정한 문항의 판단기준을 충족한다는 뜻입니다. match.id 는 요청한 item 과 같습니다.","content":{"application/json":{"schema":{"type":"object","description":"문항별 점검 결과 1건","properties":{"fileName":{"type":"string","description":"업로드한 파일명. 파일명이 비어 있었다면 document 입니다."},"passed":{"type":"boolean","description":"이 서류가 요청한 문항의 판단기준을 충족하는지 여부. 응답을 충족으로 해석하지 못한 경우에도 false 가 됩니다."},"match":{"type":"object","description":"판정 상세. id 는 요청한 item 과 같고, group·groupName·question 이 함께 옵니다.","properties":{"id":{"type":"string","description":"문항번호(예: 1-1, 10-5). 아래 문항 목록의 37개 중 하나입니다."},"group":{"type":"string","description":"문항 그룹 번호(Q1 ~ Q10)"},"groupName":{"type":"string","description":"문항 그룹 이름(목표 및 경영방침, 성과측정, 교육, 조직, 위험성평가, 예산 및 인증, 협의체, 재해 관리, 법정 교육, 관리자 선임 중 하나)"},"question":{"type":"string","description":"문항 문구"},"reason":{"type":"string","description":"판정 사유. 보통 첫 줄에 충족 또는 미충족이 오고 다음 줄에 근거 한 문장이 옵니다. 형식은 보장되지 않으므로 충족 여부는 matches 또는 unmet 중 어디에 들어 있는지로 판단하세요."},"sources":{"type":"array","items":{"type":"object","description":"근거 구절. 0개에서 여러 개까지 올 수 있으며 순서와 개수는 보장되지 않습니다.","properties":{"page":{"type":"integer","description":"근거가 있는 페이지 번호(1부터 시작)"},"text":{"type":"string","description":"근거 구절 텍스트. 문항별 점검에서는 모델이 인용한 문장이라 원문과 다를 수 있습니다."},"score":{"type":"number","description":"근거 점수(대체로 0~1). 저장 모드에서는 검색 유사도이고, 저장 없는 모드에서는 계산 방식이 다릅니다. 문항별 점검의 빠른 경로에서는 항상 1 입니다."},"bbox":{"type":"array","items":{"type":"number"},"description":"[x1, y1, x2, y2] 형식의 근거 위치. 페이지 가로·세로 대비 0~1 정규화 좌표이며 좌상단이 원점입니다. 위치를 찾지 못하면 없습니다."},"boxes":{"type":"array","items":{"type":"array","items":{"type":"number"}},"description":"줄 단위 위치 목록(각 항목은 [x1, y1, x2, y2] 정규화 좌표). 위치를 찾지 못하면 빈 배열입니다."},"layoutClass":{"type":"string","description":"문서 레이아웃 분류(예: text, table). 없을 수 있습니다."},"fileName":{"type":"string","description":"근거가 나온 문서명. 없을 수 있습니다."}},"required":["page","text","score"]},"description":"근거 구절 목록"}},"required":["id","group","groupName","question","reason","sources"]},"indexed":{"type":"boolean","description":"false 이면 문서 전문을 바로 읽어 판정한 빠른 경로입니다. true 이면 빠른 경로로 처리할 수 없어(글자 추출 실패, 빈 결과, 문서가 매우 긴 경우) 색인을 만들어 판정한 것입니다. 판정 의미는 같지만 처리가 더 느리고 근거 구절의 형태가 다릅니다. 저장 없는 모드에서는 항상 false 입니다."}},"required":["fileName","passed","match","indexed"]},"example":{"fileName":"선임계.pdf","passed":true,"match":{"id":"10-1","group":"Q10","groupName":"관리자 선임","question":"주요 공사현장의 안전관리자를 선임하였는가?","reason":"안전관리자 선임계가 확인됩니다.","sources":[{"page":1,"text":"안전관리자 선임계 ...","score":1,"bbox":[0.1,0.2,0.8,0.23],"boxes":[[0.1,0.2,0.8,0.23]]}]},"indexed":false}}}},"400":{"description":"file 파트가 없거나 파일이 아닐 때. / item 필드가 없거나 빈 문자열일 때.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때. 본문을 읽기 전에 확인합니다. / 헤더로 보낸 키가 존재하지 않거나 비활성화되었을 때.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 필요한 권한이 없거나 만료·IP 제한에 걸렸을 때. 본문 문구는 처리 경로에 따라 조금 다를 수 있습니다.","content":{"application/json":{"example":{"error":"파일 업로드 실패: Permission denied","status":403}}}},"413":{"description":"파일 크기나 페이지 수가 키의 한도를 넘었을 때(저장 없는 모드 포함). 본문의 처리 단계 이름은 처리 경로에 따라 다를 수 있습니다.","content":{"application/json":{"example":{"error":"파일 업로드 실패: File size exceeds limit","status":413}}}},"422":{"description":"저장 모드에서 문서에서 추출할 내용이 없을 때. / 저장 없는 모드(noStore=1)에서 문서에서 추출할 내용이 없을 때.","content":{"application/json":{"example":{"error":"qna 색인 element가 0개입니다(추출된 내용 없음).","status":422}}}},"429":{"description":"키의 호출 한도를 넘었을 때. 잠시 뒤 다시 시도하세요. 본문의 error 문구는 처리 경로에 따라 다르니 status 값으로 판단하세요.","content":{"application/json":{"example":{"error":"LLM 요청 실패: 429","status":429}}}},"500":{"description":"짧은 문서의 판정 단계에서 모델 호출이 실패했을 때. 5xx 이므로 잠시 뒤 재시도할 수 있습니다. / multipart/form-data 가 아닌 Content-Type 으로 보냈을 때. JSON 본문 없이 기본 500 응답이 옵니다.","content":{"application/json":{"example":{"error":"LLM 요청 실패: ...","status":500}}}},"502":{"description":"item 값이 문항 목록에 없을 때(검증 실패인데도 400 이 아닌 502 로 옵니다). 이 경우 파일은 서버에 올라가지 않습니다. / 처리 서버에 연결하지 못했거나, 긴 문서의 판정 단계에서 모델 응답이 실패했을 때. 5xx 이므로 잠시 뒤 재시도할 수 있습니다.","content":{"application/json":{"example":{"error":"unknown item: 11-1","status":502}}}},"504":{"description":"내부 처리가 시간 제한을 넘었을 때.","content":{"application/json":{"example":{"error":"Upstream timeout: ...","status":504}}}}}}},"/api/construct/page-image":{"post":{"tags":["건설안전보건 평가 서류 점검"],"operationId":"construct-page-image","summary":"근거 페이지 이미지 (Office·HWP)","description":"xlsx, docx, pptx, hwp 같은 문서의 모든 페이지를 이미지로 받아, 근거 위치(bbox)를 겹쳐 그릴 때 씁니다.\n\nPDF 와 이미지 파일은 원본을 직접 그릴 수 있으므로 이 API 가 필요하지 않습니다. Office 와 HWP 문서처럼 브라우저가 직접 그릴 수 없는 문서에서만 사용하세요. 판정 결과를 얻는 데는 필요하지 않습니다.\n\n처리 시간: 페이지 수에 비례해 시간이 걸리고 응답 크기도 커집니다. 같은 문서는 한 번만 받아 캐시해 두는 것을 권장합니다. 클라이언트 타임아웃은 20분 이상으로 잡아 주세요.\n\nnoStore 옵션을 받지 않습니다. 저장 없는 모드를 쓰더라도 이 API 는 파일을 처리 서버로 보냅니다. 파일 전송 정책상 문제가 된다면 이 API 를 쓰지 마세요.\n\n- pages 의 키는 문자열입니다. 요청한 페이지 번호에 해당하는 키가 없으면 다른 페이지 이미지가 대신 그려질 수 있으니 키를 확인하세요.\n- 오류 응답에는 status 필드가 없고, 키 누락에 해당하는 401 만 code 필드를 가집니다.\n\n예제의 JavaScript 는 최상위 `await` 와 `import` 를 씁니다. `.mjs` 파일로 저장하거나 `package.json` 에 `\"type\": \"module\"` 을 설정한 환경에서 실행하세요.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키. 모든 요청에 넣어야 하며, 이 키에는 문서 렌더링 권한이 있어야 합니다.","schema":{"type":"string","description":"발급받은 API 키. 모든 요청에 넣어야 하며, 이 키에는 문서 렌더링 권한이 있어야 합니다.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"페이지 이미지를 받을 문서 1건. 파일 파트로 보내야 하며, 없으면 400 이 나옵니다. 형식은 서버가 제한하지 않습니다. 문서 전체 페이지가 대상이며 페이지 범위는 지정할 수 없습니다.","examples":["@점검서류.xlsx"]}},"required":["file"]}}}},"responses":{"200":{"description":"문서의 페이지 이미지 목록입니다. 근거 구절의 bbox 는 이 이미지 기준의 0~1 정규화 좌표이므로 이미지 가로·세로를 곱해 그대로 겹쳐 그릴 수 있습니다.","content":{"application/json":{"schema":{"type":"object","description":"페이지 이미지 응답","properties":{"pageCount":{"type":"integer","description":"처리된 페이지 수"},"pages":{"type":"object","description":"페이지 번호(문자열 \"1\", \"2\" …, 원본 페이지 번호)를 키로 하는 맵. 값은 회전이 보정된 JPEG 이미지의 data URL 입니다. 이미지를 만들지 못한 페이지는 빠질 수 있습니다.","additionalProperties":{"type":"string","description":"data:image/jpeg;base64, 로 시작하는 JPEG 이미지 데이터"}}},"required":["pageCount","pages"]},"example":{"pageCount":2,"pages":{"1":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...","2":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."}}}}},"400":{"description":"file 파트가 없거나 파일이 아닐 때. 키 확인보다 먼저 검사합니다.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 비어 있을 때.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 문서 렌더링 권한이 없을 때.","content":{"application/json":{"example":{"error":"Permission denied"}}}},"413":{"description":"파일 크기나 페이지 수가 키의 한도를 넘었을 때.","content":{"application/json":{"example":{"error":"File size exceeds limit"}}}},"500":{"description":"multipart/form-data 가 아닌 Content-Type 으로 보냈을 때. JSON 본문 없이 기본 500 응답이 옵니다.","content":{"application/json":{"example":"(JSON 본문 없음)"}}},"502":{"description":"처리 서버에 연결하지 못했거나 응답을 읽지 못했을 때.","content":{"application/json":{"example":{"error":"Upstream error: fetch failed"}}}}}}},"/api/extract/upload":{"post":{"tags":["문서 분류·추출"],"operationId":"extract-upload","summary":"파일 업로드","description":"원본 파일 1건을 올리고 fileId 를 받습니다.\n\n`fileId` 는 이후 처리 요청에 쓰는 파일 식별자입니다. 같은 파일을 다시 올리면 새 `fileId` 가 발급됩니다. 발급된 `fileId` 는 이 파일을 올릴 때 쓴 API 키로만 처리할 수 있습니다.\n\n처리 시간: 파일 크기와 네트워크 속도에 따라 달라집니다. 전송이 5분을 넘으면 502 로 실패합니다.\n\n- 업로드 단계는 OCR 을 하지 않으므로 처리 시간은 포함되지 않습니다. 처리 시간은 처리 요청 이후에 걸립니다.\n- 업로드한 원본은 서버 파일 저장소에 보관되며, 이 API 에는 삭제 기능이 없습니다. 개인정보가 담긴 문서는 이 점을 고려해 주세요.\n- 응답이 HTTP 2xx 이더라도 fileId 가 없으면 {\"error\":\"upload 200\"} 같은 본문이 올 수 있습니다. 상태코드만 보고 성공으로 처리하지 말고 fileId 가 있는지 확인하세요.\n- 파일 크기와 쪽수 한도는 서버 설정에 따르며, 처리 결과 조회에서 413 으로 거절될 수 있습니다. 413 을 받으면 파일을 나누거나 해상도를 낮춰 다시 요청하세요.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"발급받은 API 키입니다. 비어 있으면 401 이 납니다.","schema":{"type":"string","description":"발급받은 API 키입니다. 비어 있으면 401 이 납니다.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"업로드할 원본 파일 1건입니다. 연동 기준 형식은 PDF, TIF/TIFF, PNG, JPG/JPEG, WEBP 입니다. 이 단계에서는 형식과 크기를 검사하지 않으며, 다른 폼 필드는 읽지 않고 무시합니다."}},"required":["file"]}}}},"responses":{"200":{"description":"업로드에 성공하면 HTTP 200 으로 응답합니다.","content":{"application/json":{"schema":{"type":"object","properties":{"fileId":{"type":"string","description":"파일 식별자(UUID)입니다. 처리 요청의 fileId 로 씁니다."},"fileName":{"type":"string","description":"업로드한 파일 이름입니다."},"size":{"type":"integer","description":"저장된 파일 크기(바이트)입니다."}},"required":["fileId","fileName","size"]},"example":{"fileId":"3f2c9a1e-8b7d-4c2a-9e5f-1a2b3c4d5e6f","fileName":"document.pdf","size":1843200}}}},"400":{"description":"file 필드가 없거나 파일이 아닌 일반 값으로 왔을 때입니다. application/x-www-form-urlencoded 로 file 을 텍스트로 보낸 경우도 이 오류입니다.","content":{"application/json":{"example":{"error":"file is required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 공백만 있을 때입니다. 판별은 code 값으로 하세요. / 키가 잘못되었거나 비활성 상태일 때입니다. 파일 전체를 전송한 뒤에 나옵니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"403":{"description":"키에 파일 권한이 없거나, 키가 만료되었거나, 서버 관리자가 파일 저장을 껐을 때입니다. 본문 문구는 Permission denied, API key has expired, File storage is disabled by administrator 중 하나입니다.","content":{"application/json":{"example":{"error":"Permission denied"}}}},"413":{"description":"처리 서버가 거절했을 때입니다. 상태코드와 문구를 그대로 전달하며, 문구가 없으면 error 는 upload 413 입니다.","content":{"application/json":{"example":{"error":"upload 413"}}}},"500":{"description":"multipart/form-data 가 아닌 본문(예: application/json)을 보냈을 때입니다. 본문 없이 500 이 납니다.","content":{"application/json":{"example":""}}},"502":{"description":"저장소 기록에 실패했거나, 서버와 연결하지 못했거나, 전송이 5분을 넘었을 때입니다. 예외 문구가 그대로 올 수 있습니다.","content":{"application/json":{"example":{"error":"fetch failed"}}}}}}},"/api/extract/process":{"post":{"tags":["문서 분류·추출"],"operationId":"extract-process","summary":"페이지 처리 시작","description":"업로드한 파일의 한 페이지에 대해 분류와 추출 작업을 만들고 jobId 를 받습니다.\n\n이 호출은 작업만 만들고 바로 응답합니다. 처리 결과는 작업 상태·결과 조회 API 로 받습니다. 문서가 여러 페이지이면 페이지마다 한 번씩 호출합니다.\n\n처리 시간: 작업 생성은 즉시 끝납니다. 페이지 1장의 실제 처리는 수 초에서 수 분이 걸립니다. 클라이언트 타임아웃은 20분 이상 권장합니다.\n\n- 문서 유형과 추출 항목은 서버 설정에 따라 정해집니다. 요청으로 유형이나 키 목록을 바꾸거나 개인정보 탐지 같은 추가 옵션을 켤 수는 없습니다.\n- 같은 fileId, page, mode 로 다시 요청하면 처음부터 다시 처리합니다. 이전 결과를 재사용하지 않으므로 같은 페이지를 반복 요청하지 마세요.\n- 이 작업은 취소할 수 없습니다. 연결을 끊어도 서버 처리는 계속되며, 키의 분당·일일 사용량에 1회로 집계됩니다. 한도를 넘으면 조회 결과의 error 로 429 가 옵니다.\n- 페이지는 한 번에 하나씩 순서대로 요청하는 것을 권장합니다. 동시 처리 수는 서버 설정에 따릅니다. 많이 동시에 보내면 뒤쪽 작업의 대기 시간이 길어져 시간 초과로 실패할 수 있습니다.\n- 서버는 페이지 수를 알려주지 않습니다. PDF 와 TIFF 는 쪽수를 직접 세고, PNG·JPG·WEBP 는 1쪽으로 보세요. 이미지 크기가 서버 한도를 넘으면 413(Image size exceeds limit)으로 거절될 수 있습니다. 그때는 해상도를 낮춰 다시 요청하세요. 좌표는 상대값이라 해상도를 낮춰도 해석에는 영향이 없습니다.","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"업로드할 때 쓴 것과 같은 API 키입니다. 비어 있으면 401 이 납니다.","schema":{"type":"string","description":"업로드할 때 쓴 것과 같은 API 키입니다. 비어 있으면 401 이 납니다.","examples":["YOUR_API_KEY"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileId":{"type":"string","description":"업로드 응답의 fileId 입니다. 이 단계에서는 존재 여부를 확인하지 않습니다. 잘못된 값이면 조회 결과에서 File not found 오류로 나옵니다.","examples":["3f2c9a1e-8b7d-4c2a-9e5f-1a2b3c4d5e6f"]},"page":{"type":"integer","description":"처리할 페이지 번호입니다. 1부터 시작합니다. 실제 페이지 수를 넘는 번호를 보내면 오류 없이 잘못된 결과가 나올 수 있으니 페이지 수를 정확히 계산하세요.","default":"없음","examples":["1"]},"mode":{"type":"string","description":"full 이면 문서 유형 분류와 키 추출을 함께 합니다. classify 이면 문서 유형 분류만 요청합니다. keys 는 보통 빈 배열이지만 보장되지 않으므로, 키 값이 필요하면 full 을 쓰세요. 그 밖의 값(오타 포함)은 모두 full 로 처리됩니다. 키 값이 필요하면 생략하거나 full 을 보내세요.","default":"full","examples":["full"]}},"required":["fileId","page"]}}}},"responses":{"200":{"description":"작업이 만들어지면 HTTP 200 으로 응답합니다(202 가 아닙니다).","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string","description":"작업 식별자(UUID)입니다. 작업 상태·결과 조회 요청의 job 값으로 씁니다."}},"required":["jobId"]},"example":{"jobId":"9d1e4b7a-2c3f-4e8a-b6d5-7f0a1c2e3b4d"}}}},"400":{"description":"fileId 가 비었거나, page 가 없거나, 0 이거나, 1 보다 작을 때입니다.","content":{"application/json":{"example":{"error":"fileId and page are required"}}}},"401":{"description":"x-clip-key 헤더가 없거나 공백만 있을 때입니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"500":{"description":"본문이 JSON 의 null 일 때입니다. 본문 없이 500 이 납니다.","content":{"application/json":{"example":""}}}}},"get":{"tags":["문서 분류·추출"],"operationId":"extract-poll","summary":"작업 상태·결과 조회","description":"jobId 로 작업 상태를 확인하고, 끝나면 페이지 결과를 받습니다.\n\n처리 요청과 짝을 이룹니다. 3초 간격으로 조회하고, status 가 done 또는 error 가 될 때까지 기다리세요. 이 조회에는 x-clip-key 헤더가 필요하지 않습니다. 보내도 무시됩니다.\n\n처리 시간: 조회는 즉시 응답합니다. 작업 1건(페이지 1장)은 수 초에서 수 분이 걸리므로 클라이언트 타임아웃은 20분 이상 권장합니다.\n\n- done 과 error 는 한 번 읽으면 서버에서 삭제됩니다. 두 번째 조회는 unknown(404)이 됩니다. 결과를 받으면 바로 저장하세요.\n- 완료되었지만 아직 읽지 않은 결과가 최소 1시간 보관된다는 보장은 없습니다. 정리는 새 처리 요청이 들어올 때 이뤄지므로 1시간이 지나도 남아 있을 수 있습니다. 받은 즉시 저장하세요.\n- 처리 파일이 손상되었거나 암호화되었거나 지원하지 않는 형식이면 오류가 아니라 done 으로 옵니다. type 은 document, keys 는 빈 배열, words 는 빈 객체입니다. 처리 서버가 JSON 이 아닌 응답을 줄 때도 같은 빈 결과가 됩니다. 미분류와 구별되지 않으므로 빈 결과를 받으면 원본 파일을 확인하세요.\n- error 본문에 문구가 없으면 process 뒤에 HTTP 상태코드를 붙인 문구(예: process 404)가 옵니다.\n- 작업 상태는 서버 메모리에 있습니다. 서버가 재시작되면 진행 중인 작업이 사라지므로 해당 페이지를 다시 요청하세요. 여러 서버 인스턴스 뒤에서 운영한다면 같은 jobId 가 같은 인스턴스로 가도록 설정하세요.\n- jobId 는 결과에 접근할 수 있는 값입니다. 요청 로그에 jobId 가 남을 수 있으니 취급에 주의하세요.","parameters":[{"name":"job","in":"query","required":true,"description":"처리 요청 응답의 jobId 입니다. 없거나 모르는 값이면 404 unknown 이 옵니다.","schema":{"type":"string","description":"처리 요청 응답의 jobId 입니다. 없거나 모르는 값이면 404 unknown 이 옵니다.","examples":["9d1e4b7a-2c3f-4e8a-b6d5-7f0a1c2e3b4d"]}}],"responses":{"200":{"description":"HTTP 200 이면 status 가 pending(처리 중) 또는 done(완료, result 포함)입니다. 오류는 HTTP 상태코드가 원인에 따라 달라지고 본문 status 가 error 입니다.","content":{"application/json":{"schema":{"description":"작업 상태입니다. HTTP 200 이면 status 가 pending 또는 done 입니다. 오류는 HTTP 상태코드와 status 값으로 구분합니다.","oneOf":[{"type":"object","description":"처리 중입니다. 잠시 후 다시 조회하세요.","properties":{"status":{"type":"string","enum":["pending"],"description":"처리 중입니다."}},"required":["status"]},{"type":"object","description":"처리가 끝났습니다. 이 응답을 받으면 결과가 서버에서 삭제되므로 바로 저장하세요.","properties":{"status":{"type":"string","enum":["done"],"description":"처리가 끝났습니다."},"result":{"type":"object","description":"요청한 한 페이지의 분류·추출 결과입니다. 문서 단위가 아니라 페이지 단위입니다.","properties":{"page":{"type":"integer","description":"요청한 페이지 번호(1부터)입니다."},"type":{"type":"string","description":"문서 유형 이름입니다. 분류하지 못한 페이지는 \"document\" 로 옵니다. 빈 문자열이 올 수도 있습니다. 두 경우 모두 미분류로 보면 됩니다."},"keys":{"type":"array","items":{"type":"object","description":"추출된 키 하나입니다. 값이 없으면 value 가 빈 문자열일 수 있습니다.","properties":{"key":{"type":"string","description":"키 이름입니다. 문서 유형마다 정해진 이름을 씁니다."},"value":{"type":"string","description":"추출한 값 원문입니다(앞뒤 공백 제거). 여러 줄 값은 줄바꿈을 포함하고, 표나 여러 행에 걸친 값은 \"|\" 로 이어 붙을 수 있습니다. 빈 문자열일 수 있습니다."},"informationList":{"type":"array","items":{"type":"string"},"description":"목록형 키(예: 필요 서류 목록)의 항목입니다. 있을 때만 옵니다."},"processed":{"description":"값을 정규화한 결과입니다. 날짜는 \"YYYY-MM-DD\" 문자열, 통화 코드·전화번호는 문자열, 금액은 {currency, amount}, 과부족은 {plus, minus}(%), 품명은 {goodsName}, 참·거짓 값은 boolean 입니다. 합계 검증 키는 sumOfLines·lineCount·printed·matchesPrinted 등 객체이며 키에 따라 필드가 다릅니다. 정규화할 수 없으면 생략됩니다.","example":{"currency":"USD","amount":80816.94}},"reference":{"type":"object","description":"근거 위치입니다. 키는 요청한 페이지 번호 문자열 하나이고, 값은 words 의 키(seq) 번호 배열입니다. 위치를 찾지 못했으면 빈 객체이거나 빈 배열입니다.","additionalProperties":{"type":"array","items":{"type":"integer"}}},"confidence":{"type":"number","description":"추출 신뢰도 점수(0.05~0.99, 소수 둘째 자리)입니다. 확률이 아닙니다. 값이 원문에서 얼마나 명확히 확인되는지를 나타내는 등급 점수입니다. 값이 없는 키에는 없습니다. 양호·보통·주의·위험 구간은 서버 값이 아니므로 \"신뢰도 구간\" 항목의 규칙으로 계산하세요."}},"required":["key","value","reference"]},"description":"추출된 키·값 목록입니다. mode 가 classify 이면 보통 빈 배열이지만 보장되지는 않습니다."},"words":{"type":"object","description":"근거 단어 박스 사전입니다. 키는 seq 번호 문자열이고 값은 [x1, y1, x2, y2] 상대 좌표(0~1, 원점은 왼쪽 위)입니다. keys[].reference 가 가리키는 단어만 담깁니다. mode 가 classify 이면 빈 객체입니다.","additionalProperties":{"type":"array","items":{"type":"number"},"description":"[x1, y1, x2, y2]. 페이지 가로·세로에 대한 비율입니다."}}},"required":["page","type","keys","words"]}},"required":["status","result"]},{"type":"object","description":"처리에 실패했습니다. HTTP 상태코드는 원인에 따라 달라집니다. 이 응답도 읽으면 작업이 삭제됩니다.","properties":{"status":{"type":"string","enum":["error"],"description":"처리에 실패했습니다."},"error":{"type":"string","description":"오류 설명입니다. 내부 처리 단계의 문구가 그대로 올 수 있어 로그용으로 보관하세요."}},"required":["status","error"]},{"type":"object","description":"없거나 이미 읽었거나 만료된 jobId 입니다(HTTP 404).","properties":{"status":{"type":"string","enum":["unknown"],"description":"작업을 찾을 수 없습니다."}},"required":["status"]}]},"example":{"status":"done","result":{"page":1,"type":"Letter of Credit","keys":[{"key":"L/C No.","value":"M079Y2606NU00178","reference":{"1":[23,64]},"confidence":0.9},{"key":"Issue Date","value":"260617","processed":"2026-06-17","reference":{"1":[69]},"confidence":0.93},{"key":"Amount","value":"USD 80,816.94","processed":{"currency":"USD","amount":80816.94},"reference":{"1":[116,117]},"confidence":0.93}],"words":{"23":[0.1558,0.1044,0.2906,0.1141],"64":[0.0969,0.3406,0.2333,0.3497],"69":[0.096,0.3719,0.1493,0.3811],"116":[0.2018,0.5898,0.2413,0.6018],"117":[0.2494,0.5898,0.3398,0.6018]}}}}}},"401":{"description":"작업을 만들 때 쓴 키가 잘못되었거나 비활성·만료되었을 때입니다.","content":{"application/json":{"example":{"status":"error","error":"Invalid or inactive API key"}}}},"403":{"description":"키에 OCR 또는 키 추출 권한이 없을 때입니다. mode 가 classify 여도 키 추출 권한이 없으면 같은 오류가 납니다. 파일 저장이 꺼져 있을 때도 이 오류입니다. 이 API 에 쓰는 키에는 IP 허용 목록을 설정하지 마세요. 설정하면 결과 조회가 IP address not allowed 로 거절될 수 있으며, 고객 서버 IP 를 추가해도 해결되지 않습니다.","content":{"application/json":{"example":{"status":"error","error":"Permission denied"}}}},"404":{"description":"jobId 를 모르거나, done·error 를 이미 한 번 읽었거나, 결과가 정리되었거나, 서버가 재시작되었을 때입니다. 다시 처리 요청을 보내야 합니다. / fileId 가 없거나 다른 키로 올린 파일일 때입니다.","content":{"application/json":{"example":{"status":"unknown"}}}},"413":{"description":"파일 크기, 쪽수, 이미지 크기가 서버 한도를 넘었을 때입니다. 문구는 File size exceeds limit, Page count exceeds limit, Image size exceeds limit 중 하나입니다.","content":{"application/json":{"example":{"status":"error","error":"File size exceeds limit"}}}},"429":{"description":"키의 분당 또는 일일 사용 한도를 넘었을 때입니다. 문구는 Rate limit exceeded 또는 Daily limit exceeded 입니다. 재시도 시간은 응답에 없으니 잠시 후 다시 시도하세요.","content":{"application/json":{"example":{"status":"error","error":"Rate limit exceeded"}}}},"500":{"description":"처리 서버의 내부 오류입니다. error 는 처리 서버가 준 문구이고, 문구가 없으면 process 500 입니다.","content":{"application/json":{"example":{"status":"error","error":"process 500"}}}},"502":{"description":"처리 시간이 초과되었거나, 서버와 연결하지 못했거나, 저장된 파일을 읽지 못했을 때입니다. 오래 걸리는 페이지는 이 오류로 끝날 수 있습니다. 본문 문구는 경로에 따라 다릅니다. 처리 서버 쪽 시간 초과는 Processing failed: 로 시작합니다.","content":{"application/json":{"example":{"status":"error","error":"The operation was aborted due to timeout"}}}}}}}},"components":{"securitySchemes":{"clipKey":{"type":"apiKey","in":"header","name":"x-clip-key","description":"발급받은 CLIP API 키"}}},"security":[{"clipKey":[]}]}