{"openapi":"3.1.0","info":{"title":"CLIP API","version":"2026-10-28","description":"## 시작하기\n\nCLIP HTTP API 는 발전 분석시트 입력을(를) 고객 시스템에서 호출할 수 있도록 제공하는 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/health\n\n# 2. 키 상태와 잔여 한도 확인\ncurl -s {{BASE_URL}}/api/key-status -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/power/upload -H \"x-clip-key: YOUR_API_KEY\" -F \"file=@/path/to/document.pdf\"\n```\n\nAPI 키는 담당자에게 요청하여 발급받습니다. 키마다 사용할 수 있는 권한이 다르므로, 아래 표에서 연동할 기능에 필요한 권한을 확인하십시오.\n\n| 기능 | 필요한 권한 | 비고 |\n| --- | --- | --- |\n| 키 상태·잔여 한도 확인 | 키 활성·만료 상태 | `GET {{BASE_URL}}/api/key-status` |\n\n키에는 추가 기능(분류, 키 추출, 개인정보 탐지) 사용 여부가 설정되어 있을 수 있습니다. 설정이 꺼진 기능을 요청하면 403 이 옵니다.\n\n인증 실패는 다음과 같이 구분됩니다.\n\n- 키 헤더가 없거나 비어 있으면 401 이 오며, 본문의 `code` 값이 `api_key_required` 입니다. 배포 설정에 따라 키 헤더 없이도 요청이 처리될 수 있으나, 연동은 항상 키를 보내는 것을 전제로 하십시오.\n- 키가 잘못되었거나 비활성·만료되었거나 권한이 없으면 401 또는 403 이 옵니다. 기능 API 응답의 `error` 문자열로 세부 사유를 확인할 수 있습니다. 다만 키 상태 확인 API(`GET /api/key-status`)는 업스트림 사유를 감추고 `{\"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` 입니다.\n\n파일이 없는 POST 요청은 application/json 본문을 사용합니다. `Content-Type` 헤더를 `application/json` 으로 지정하고 JSON 본문을 보내십시오. 작업 조회는 GET 요청이며 본문이 없습니다.\n\n```curl\ncurl -X POST {{BASE_URL}}/api/power/upload -H \"x-clip-key: YOUR_API_KEY\" -F \"file=@/path/to/document.pdf\"\n```\n\n기능별로 받는 파일 형식이 다릅니다. 아래 목록은 권장 형식이며, 목록에 없는 형식을 보내면 처리가 실패하거나 결과 품질이 떨어질 수 있습니다.\n\n| 기능 | 지원 파일 형식 |\n| --- | --- |\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` | 처리 중입니다. | 3초 뒤에 다시 조회합니다. |\n| `done` | 처리가 끝났습니다. | 결과는 `result` 또는 `results` 필드에 있습니다. 필드 이름은 기능에 따라 다릅니다. |\n| `error` | 처리에 실패했습니다. | 본문의 `status` 는 항상 문자열 `\"error\"` 이고, `error` 필드에 사유가 옵니다. 실패 여부는 조회 응답의 HTTP 상태 코드로 판단하십시오. 처리·검토 API 는 처리 엔진의 상태 코드를 그대로 쓰며(알 수 없으면 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/key-status` 로 키 상태를 먼저 확인하십시오.\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\":\"...\"}` | 필드가 없거나 형식이 맞지 않을 때입니다. 메시지는 엔드포인트마다 다릅니다(각 엔드포인트의 오류 표 참고). |\n| 처리 오류 (상태 값 없음) | `{\"error\":\"...\"}` | 파일 업로드, 근거 페이지 이미지, 키 상태 확인 |\n| 작업 조회 실패 | `{\"status\":\"error\",\"error\":\"...\"}` | 비동기 작업 조회 |\n| 작업 없음 | `{\"status\":\"unknown\"}` | 비동기 작업 조회 (HTTP 404) |\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## 처리 시간·재시도\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\n응답 JSON 과 고객 서버의 로그에도 판정 대상 문서의 내용이 들어 있을 수 있습니다. 개인정보와 기밀 문서를 다룬다면 고객 측 보관 기간과 접근 권한 정책을 함께 적용하십시오.\n\n## 버전과 상태 확인\n\n이 문서는 2026-10-28 기준입니다. API 경로에는 버전 번호가 없으며(예: `/v1` 없음), 서버 버전을 응답에서 확인할 수 없습니다. 연동 테스트 전에 담당자에게 배포된 버전을 확인하십시오.\n\n- 응답에 새 필드가 추가될 수 있습니다. 모르는 필드는 무시하도록 파서를 작성하십시오.\n- 변경 사항은 담당자를 통해 확인하십시오.\n\n서버 동작 확인은 인증 없이 호출할 수 있습니다. 이 경로는 서버가 응답하는지만 확인하며, 문서 처리 엔진의 연결 상태나 API 키 상태는 확인하지 않습니다.\n\n```curl\ncurl -s {{BASE_URL}}/api/health\n```\n\n응답은 `{\"status\":\"ok\"}` 입니다. 키 상태와 잔여 한도는 `GET {{BASE_URL}}/api/key-status` 에 `x-clip-key` 헤더를 보내 확인하십시오."},"servers":[{"url":"https://clip-power.floaterlab.com"}],"tags":[{"name":"발전 분석시트 입력","description":"발전소 ZLD(무방류) 수질 분석시트 — ZLD 전처리수 분석(양식 A), 증발농축기·결정화기 분석(양식 B) — 의 손글씨 스캔에서 지점·시각별 측정값과 일일 점검값을 뽑고, 양식에 인쇄된 관리기준을 넘은 값을 표시합니다. 값마다 원문 위치를 함께 돌려줍니다."}],"paths":{"/api/power/extract":{"post":{"tags":["발전 분석시트 입력"],"operationId":"power-extract","summary":"분석시트 입력 작업 만들기","description":"ZLD 분석시트 스캔 파일을 보내 지점·시각별 값 추출 작업을 만들고 jobId 를 받습니다.\n\n쪽마다 양식(A·B)을 가려 지점·시각별 값으로 바꿉니다. 업로드 단계 없이 이 요청에 파일을 바로 보냅니다. 한 파일에 양식 A·B 시트가 날짜별로 섞여 있어도 쪽마다 나눕니다.\n\n처리 시간: 작업 생성은 즉시 끝납니다. 파일마다 전체를 한 번에 처리하며 수십 쪽 파일은 수 분이 걸릴 수 있습니다.\n\n- 서류 원본은 처리 엔진에 저장하지 않습니다. 처리하는 동안만 전달되고 남지 않습니다.\n- 처리 엔진의 일시 오류(5xx)는 파일마다 2번까지 자동으로 다시 시도합니다.","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":{"files":{"type":"string","format":"binary","description":"분석시트 파일입니다. 같은 이름으로 여러 번 보내면 여러 파일을 한 작업으로 처리합니다. PDF·TIFF·PNG·JPG·WEBP 를 받습니다. 연도는 파일명(\"2026년 …\")에서 읽으므로 기간이 드러나는 이름을 권장합니다.","examples":["2026년 5월 13-31.pdf"]}},"required":["files"]}}}},"responses":{"200":{"description":"작업이 만들어지면 HTTP 202 로 응답합니다.","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"total":{"type":"integer","description":"받은 파일 수입니다."}},"required":["jobId","total"]},"example":{"jobId":"7b0c2f4e-9a1d-4e3b-8c5f-2d6a1b3c4e5f","total":1}}}},"400":{"description":"files 가 없을 때입니다.","content":{"application/json":{"example":{"error":"서류가 없습니다. files 필드로 파일을 보내 주세요."}}}},"401":{"description":"x-clip-key 헤더가 없을 때입니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}}}},"get":{"tags":["발전 분석시트 입력"],"operationId":"power-extract-poll","summary":"작업 상태·결과 조회","description":"jobId 로 진행률을 확인하고, 끝나면 시트별 값을 받습니다.\n\n3초 간격으로 조회하십시오. 작업을 만든 키와 같은 키를 `x-clip-key` 헤더로 보내야 합니다. 다른 키로 조회하면 없는 작업과 같게 404 가 옵니다.\n\n처리 시간: 조회는 즉시 응답합니다.\n\n- 결과는 끝난 뒤 1시간 동안 여러 번 조회할 수 있습니다.\n- 일부 파일만 실패하면 작업은 `done` 이고 실패한 파일은 `result.failed` 에 담깁니다.\n- 작업 정보는 서버 메모리에 있어 서버가 재시작되면 사라집니다(404 `unknown`).","parameters":[{"name":"x-clip-key","in":"header","required":true,"description":"작업을 만든 키입니다.","schema":{"type":"string","description":"작업을 만든 키입니다.","examples":["YOUR_API_KEY"]}},{"name":"job","in":"query","required":true,"description":"작업 만들기 응답의 jobId 입니다.","schema":{"type":"string","description":"작업 만들기 응답의 jobId 입니다.","examples":["7b0c2f4e-9a1d-4e3b-8c5f-2d6a1b3c4e5f"]}}],"responses":{"200":{"description":"HTTP 200 이면 status 가 pending(진행률 포함) 또는 done(결과 포함)입니다.","content":{"application/json":{"schema":{"description":"작업 상태입니다.","oneOf":[{"type":"object","description":"처리 중입니다.","properties":{"status":{"type":"string","enum":["pending"]},"progress":{"type":"object","properties":{"done":{"type":"integer"},"total":{"type":"integer"}}}},"required":["status"]},{"type":"object","description":"끝났습니다.","properties":{"status":{"type":"string","enum":["done"]},"result":{"type":"object","properties":{"files":{"type":"array","description":"처리에 성공한 파일의 결과입니다(보낸 순서).","items":{"type":"object","properties":{"fileName":{"type":"string"},"pageCount":{"type":"integer"},"sheets":{"type":"array","items":{"type":"object","description":"분석시트 1장(1쪽)입니다.","properties":{"page":{"type":"integer","description":"파일 안 쪽 번호(1부터)입니다."},"sheet_type":{"type":"string","enum":["zld_pretreatment","evap_crystallizer"],"description":"양식입니다. zld_pretreatment = 양식 A(ZLD 전처리수 분석), evap_crystallizer = 양식 B(증발농축기·결정화기 분석)."},"date":{"type":["string","null"],"description":"시트 날짜(YYYY-MM-DD)입니다. 월·일은 손글씨, 연도는 파일명(\"2026년 1월 …\")을 우선합니다. 읽지 못했으면 null 입니다."},"date_raw":{"type":"string","description":"손글씨 날짜 원문입니다."},"locations":{"type":"array","description":"측정 지점(또는 장비)별 시각 목록입니다.","items":{"type":"object","properties":{"location":{"type":"string","enum":["fgd_waste_water","reaction_clarifier_pond","pre_treated_water","evaporator_a","evaporator_b","crystallizer_a","crystallizer_b"]},"process":{"type":"string","enum":["primary_sedimentation","secondary_coagulation"],"description":"반응/응집조만 있습니다. 1차 침전조·2차 응집조."},"samples":{"type":"array","items":{"type":"object","properties":{"time":{"type":["string","null"],"description":"측정 시각(\"HH:MM\")입니다. 양식 B 는 인쇄된 시각(09:00·16:00·21:00·05:00)이고, 양식 A 는 손으로 적은 시각입니다. 읽지 못했으면 null 입니다."},"day_offset":{"type":"integer","enum":[1],"description":"자정을 넘긴 측정이면 1 입니다(양식 B 의 05:00 — 시트 날짜는 운전일이라 다음 날 새벽). 아니면 없습니다."},"values":{"type":"object","additionalProperties":{"type":"number"},"description":"항목 키 → 숫자입니다. 빈 칸·읽지 못한 칸은 키가 없습니다. 항목 키는 `ph`, `conductivity_ms_cm`, `ca_mg_l`, `mg_mg_l`, `so4_mg_l`, `cl_mg_l`, `ts_pct`, `tds_pct`, `tss_pct`, `sg` 입니다."}},"required":["time","values"]}}},"required":["location","samples"]}},"daily_checks":{"type":"object","description":"양식 A 비고란의 일일 점검입니다. 키는 `cooling_tower_conductivity`, `lime_dissolving_tank`, `lime_injection_tank`, `soda_ash_dissolving_tank`, `soda_ash_injection_tank` 이고, 값은 { value, unit, raw } 입니다. 단위가 적혀 있지 않으면 unit 이 null 입니다.","additionalProperties":{"type":"object","properties":{"value":{"type":["number","null"]},"unit":{"type":["string","null"]},"raw":{"type":"string","description":"읽은 원문 그대로."}},"required":["value","unit","raw"]}},"flags":{"type":"array","description":"확인이 필요한 칸입니다. path 는 `locations[0].samples[1].values.ph` 형식입니다.","items":{"type":"object","properties":{"path":{"type":"string"},"raw":{"type":"string"},"reason":{"type":"string","enum":["not_numeric","length_mismatch","missing_time","extra_values"],"description":"not_numeric = 숫자로 읽을 수 없음, length_mismatch = 값 개수가 시각 수와 달라 순서가 어긋났을 수 있음, missing_time = 시각을 읽지 못함, extra_values = 인쇄된 시각(4회)보다 값이 많음."}},"required":["path","raw","reason"]}},"out_of_spec":{"type":"array","description":"양식에 인쇄된 관리기준을 벗어난 값입니다. 추출 결과와 별개로 검사 단계에서 붙습니다.","items":{"type":"object","properties":{"path":{"type":"string"},"item":{"type":"string"},"value":{"type":"number"},"spec":{"type":"string","description":"기준 표기. 예: \"42% 이하\", \"700~1000 mg/L\"."}},"required":["path","item","value","spec"]}},"evidence":{"type":"object","description":"값의 원문 위치입니다. 키는 값의 path 입니다. { raw, key, page, boxes? } — boxes 는 [x1, y1, x2, y2] 상대 좌표(0~1, 원점은 왼쪽 위)이고, 쪽은 읽었지만 자리를 찾지 못했으면 없습니다.","additionalProperties":{"type":"object"}}},"required":["page","sheet_type","date","date_raw","locations","flags","out_of_spec","evidence"]},"description":"찾은 분석시트입니다. 쪽 순서가 아니라 양식(sheet_type)과 날짜(date)로 구분하십시오."},"otherPages":{"type":"array","description":"시트가 아닌 쪽과 PIN 분류입니다.","items":{"type":"object","properties":{"page":{"type":"integer"},"type":{"type":"string"}}}},"pinKeys":{"type":"array","description":"변환 전 PIN 키 원본([{page, key, value, list?}])입니다.","items":{"type":"object"}},"warnings":{"type":"array","items":{"type":"string"},"description":"파일 단위로 확인할 점입니다. 예: 같은 양식·같은 날짜가 두 번 나옴, 날짜를 읽지 못한 쪽."}},"required":["fileName","pageCount","sheets","otherPages","pinKeys","warnings"]}},"failed":{"type":"array","description":"처리하지 못한 파일입니다. 일부 파일만 실패해도 작업은 done 이며 여기에 담깁니다.","items":{"type":"object","properties":{"fileName":{"type":"string"},"error":{"type":"string"},"status":{"type":"integer"}},"required":["fileName","error","status"]}}},"required":["files","failed"]}},"required":["status","result"]},{"type":"object","description":"모든 파일이 실패했습니다.","properties":{"status":{"type":"string","enum":["error"]},"error":{"type":"string"}},"required":["status","error"]},{"type":"object","description":"작업이 없습니다(HTTP 404).","properties":{"status":{"type":"string","enum":["unknown"]}},"required":["status"]}]},"example":{"status":"done","result":{"files":[{"fileName":"2026년 5월 13-31.pdf","pageCount":1,"sheets":[{"page":1,"sheet_type":"evap_crystallizer","date":"2026-05-31","date_raw":"2026년 5월 31일","locations":[{"location":"evaporator_a","samples":[{"time":"09:00","values":{"ph":6.64,"tds_pct":13.16,"cl_mg_l":65500}},{"time":"05:00","day_offset":1,"values":{"ph":6.62,"tds_pct":13.04}}]},{"location":"crystallizer_a","samples":[{"time":"09:00","values":{"ph":5.53,"tds_pct":43.55}}]}],"flags":[],"out_of_spec":[{"path":"locations[1].samples[0].values.tds_pct","item":"tds_pct","value":43.55,"spec":"42% 이하"}],"evidence":{"locations[0].samples[0].values.ph":{"raw":"6.64","key":"evaporator_a_ph","page":1,"boxes":[[0.31,0.22,0.35,0.24]]}}}],"otherPages":[],"pinKeys":[],"warnings":[]}],"failed":[]}}}}},"401":{"description":"x-clip-key 헤더가 없을 때입니다.","content":{"application/json":{"example":{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}}}},"404":{"description":"jobId 를 모르거나, 다른 키로 조회했거나, 만료(1시간)되었거나, 서버가 재시작되었을 때입니다. 작업을 다시 만드십시오.","content":{"application/json":{"example":{"status":"unknown"}}}}}}}},"components":{"securitySchemes":{"clipKey":{"type":"apiKey","in":"header","name":"x-clip-key","description":"발급받은 CLIP API 키"}}},"security":[{"clipKey":[]}]}