
문서 기준일 2026-10-23
API 명세서
(주)플로터랩

Copyright © 2026 Floater Lab, Inc.
본 문서의 저작권은 (주)플로터랩에 있습니다.
API 연동 가이드
CLIP API
이 문서는 지금 접속한 CLIP 인스턴스(https://clip-nicednb-tcb.floaterlab.com)에서 사용할 수 있는 API 만 담고 있습니다.
시작하기
CLIP HTTP API 는 기술신용평가 서류 입력을(를) 고객 시스템에서 호출할 수 있도록 제공하는 API 입니다. 이 문서는 공통으로 적용되는 규칙과 기능별 호출 방법을 설명합니다.
모든 주소는 https://clip-nicednb-tcb.floaterlab.com 을 기준으로 합니다. 실제 연동 시에는 담당자가 안내한 서버 주소로 바꿔 사용하십시오.
- 모든 요청은 고객 서버에서 호출해 주십시오. 브라우저나 모바일 앱에서 직접 호출할 수 없습니다(CORS 미지원).
- 인증이 필요한 모든 요청에는
x-clip-key헤더로 API 키를 보내야 합니다. 인증 세부 사항은 다음 섹션을 참고하십시오. - 파일을 보내는 요청은 multipart/form-data 를, 그 밖의 요청은 application/json 을 사용합니다.
- 일부 기능은 작업을 만든 뒤 작업 ID 로 결과를 조회하는 방식입니다. 자세한 내용은 비동기 작업 섹션을 참고하십시오.
연동을 시작할 때는 아래 순서로 서버 상태를 먼저 확인하는 것을 권장합니다. 상태 확인 경로는 인증 없이 호출할 수 있습니다.
# 1. 서버 동작 확인 (인증 불필요)
curl -s https://clip-nicednb-tcb.floaterlab.com/api/health
# 2. 키 상태와 잔여 한도 확인
curl -s https://clip-nicednb-tcb.floaterlab.com/api/key-status -H "x-clip-key: YOUR_API_KEY"첫 번째 요청은 {"status":"ok"} 응답이면 서버가 정상 동작하는 것입니다. 두 번째 요청에서 키가 유효하면 요금제, 사용 가능한 기능, 한도 정보가 응답으로 옵니다.
인증
API 키는 모든 인증 요청의 x-clip-key 헤더로 보냅니다. 키 값을 요청 본문이나 주소의 쿼리 문자열에 넣지 마십시오.
curl -X POST https://clip-nicednb-tcb.floaterlab.com/api/tcb/upload -H "x-clip-key: YOUR_API_KEY" -F "file=@/path/to/document.pdf"API 키는 담당자에게 요청하여 발급받습니다. 키마다 사용할 수 있는 권한이 다르므로, 아래 표에서 연동할 기능에 필요한 권한을 확인하십시오.
| 기능 | 필요한 권한 | 비고 |
|---|---|---|
| 키 상태·잔여 한도 확인 | 키 활성·만료 상태 | GET https://clip-nicednb-tcb.floaterlab.com/api/key-status |
키에는 추가 기능(분류, 키 추출, 개인정보 탐지) 사용 여부가 설정되어 있을 수 있습니다. 설정이 꺼진 기능을 요청하면 403 이 옵니다.
인증 실패는 다음과 같이 구분됩니다.
- 키 헤더가 없거나 비어 있으면 401 이 오며, 본문의
code값이api_key_required입니다. 배포 설정에 따라 키 헤더 없이도 요청이 처리될 수 있으나, 연동은 항상 키를 보내는 것을 전제로 하십시오. - 키가 잘못되었거나 비활성·만료되었거나 권한이 없으면 401 또는 403 이 옵니다. 기능 API 응답의
error문자열로 세부 사유를 확인할 수 있습니다. 다만 키 상태 확인 API(GET /api/key-status)는 업스트림 사유를 감추고{"error":"Upstream error 401"}처럼 상태 코드만 돌려주므로 401 과 403 을 구분할 수 없습니다. 이 API 의 502 본문은Failed to fetch: ...형식입니다. - 분당 또는 일일 사용 한도를 넘으면 429 가 옵니다.
{"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"}요청 형식
파일을 보내는 API 는 multipart/form-data 형식을 사용하며 파일 필드 이름은 file 입니다.
파일이 없는 POST 요청은 application/json 본문을 사용합니다. Content-Type 헤더를 application/json 으로 지정하고 JSON 본문을 보내십시오. 작업 조회는 GET 요청이며 본문이 없습니다.
curl -X POST https://clip-nicednb-tcb.floaterlab.com/api/tcb/upload -H "x-clip-key: YOUR_API_KEY" -F "file=@/path/to/document.pdf"기능별로 받는 파일 형식이 다릅니다. 아래 목록은 권장 형식이며, 목록에 없는 형식을 보내면 처리가 실패하거나 결과 품질이 떨어질 수 있습니다.
| 기능 | 지원 파일 형식 |
|---|
파일 크기와 페이지 수 상한은 계정 설정에 따릅니다. 상한을 넘는 파일은 413 으로 거절됩니다. 정확한 상한 값은 담당자에게 확인하십시오. 이미지 해상도는 기본적으로 자동 축소되어 처리됩니다. 파일 크기, 페이지 수, 픽셀 상한을 넘으면 413 으로 거절됩니다.
Content-Type 을 직접 지정하지 마십시오. HTTP 클라이언트가 multipart 경계 값을 포함한 헤더를 자동으로 만들도록 두어야 합니다.비동기 작업(작업 ID 폴링)
문서 처리는 시간이 오래 걸리므로 여러 기능이 같은 방식의 비동기 작업으로 동작합니다. 기본 흐름은 다음과 같습니다.
- 파일을 업로드 API 로 보내면
fileId를 응답으로 받습니다. 업로드 응답의 HTTP 상태는 200 입니다. - 처리 API 를 POST 로 호출하면
{"jobId": "..."}형태의 응답을 받습니다. 이 값이 작업 ID 입니다. - 같은 처리 API 주소에
?job=작업ID를 붙여 GET 으로 호출합니다. 3초 간격으로 조회하십시오. - 응답의
status가pending이면 다시 조회하고,done또는error이면 조회를 멈춥니다.
처리 API 의 요청 본문과 페이지 단위 호출 방식은 기능별 문서를 따르십시오.
| status 값 | 의미 | 처리 방법 |
|---|---|---|
pending | 처리 중입니다. | 3초 뒤에 다시 조회합니다. |
done | 처리가 끝났습니다. | 결과는 result 또는 results 필드에 있습니다. 필드 이름은 기능에 따라 다릅니다. |
error | 처리에 실패했습니다. | 본문의 status 는 항상 문자열 "error" 이고, error 필드에 사유가 옵니다. 실패 여부는 조회 응답의 HTTP 상태 코드로 판단하십시오. 처리·검토 API 는 처리 엔진의 상태 코드를 그대로 쓰며(알 수 없으면 502). |
unknown (HTTP 404) | 작업이 없거나 만료되었거나 서버 재시작으로 사라졌습니다. | 작업을 처음부터 다시 만들어야 합니다. |
unknown 이 옵니다. 응답이 유실되었다면 같은 작업을 재조회하지 말고 작업을 처음부터 다시 만들지 여부를 판단하십시오.unknown 이 될 수 있습니다.- 작업 정보는 서버 메모리에만 있으며, 마지막 상태 변경 후 1시간이 지나면 만료됩니다.
- 서버가 재시작되면 진행 중이던 작업은 모두 사라지며 조회 시 404
unknown이 옵니다. - 여러 대의 서버를 앞단 로드밸런서로 묶은 환경에서는 작업을 만든 서버와 다른 서버로 조회될 수 있습니다. 이 경우에도 404
unknown이 옵니다.
작업 ID 는 결과를 조회하는 값이므로 외부에 노출하지 마십시오.
작업 생성 요청(POST)이 200 을 돌려주었다고 해서 키가 유효하다는 뜻은 아닙니다. 키가 무효하거나 권한·한도가 부족하면 작업은 만들어지고, 실패 사유는 조회 단계에서 401, 403, 413, 429 등으로 나타납니다. 연동 전에 GET https://clip-nicednb-tcb.floaterlab.com/api/key-status 로 키 상태를 먼저 확인하십시오.
작업 생성 요청을 다시 보내면 새 작업이 만들어져 처리 횟수가 중복으로 소모됩니다. 재시도 규칙은 처리 시간·재시도 섹션을 따르십시오.
오류 응답
오류 응답의 본문 형식은 API 마다 다릅니다. 공통된 형식이 없으므로, 먼저 HTTP 상태 코드로 분류하고 본문에서 code 또는 status 값, 그리고 error 문자열을 확인하십시오. 거절한 상위 시스템의 세부 필드(부족한 권한 이름, 허용량 등)는 전달되지 않습니다.
| 형식 | 본문 예 | 해당 API |
|---|---|---|
| 키 누락 | {"error":"...","code":"api_key_required"} | 인증이 필요한 모든 API |
| 입력 오류 (400) | {"error":"..."} | 필드가 없거나 형식이 맞지 않을 때입니다. 메시지는 엔드포인트마다 다릅니다(각 엔드포인트의 오류 표 참고). |
| 처리 오류 (상태 값 없음) | {"error":"..."} | 파일 업로드, 근거 페이지 이미지, 키 상태 확인 |
| 작업 조회 실패 | {"status":"error","error":"..."} | 비동기 작업 조회 |
| 작업 없음 | {"status":"unknown"} | 비동기 작업 조회 (HTTP 404) |
{"error":"처리 중 오류가 발생했습니다.","status":502}| 상태 코드 | 의미 | 대응 |
|---|---|---|
| 400 | 입력 필드가 없거나 형식이 맞지 않습니다. | 필드 이름과 형식을 확인하십시오. 같은 요청을 재시도하지 마십시오. |
| 401 | API 키가 없거나 잘못되었거나 비활성·만료되었습니다. | code 가 api_key_required 이면 키 헤더 누락입니다. 그 밖이면 키 값과 상태를 담당자에게 확인하십시오. 재시도하지 마십시오. |
| 403 | 키 권한이 부족하거나 설정이 꺼진 기능을 요청했습니다. | 필요한 권한을 담당자에게 확인하십시오. 재시도하지 마십시오. |
| 404 | 작업이 없거나(unknown) 파일을 찾을 수 없습니다. | 작업이 만료되었거나 재시작으로 사라졌는지 확인하십시오. 파일 ID 는 같은 키로 업로드한 것만 사용할 수 있습니다. |
| 413 | 파일 크기, 페이지 수, 이미지 픽셀 상한 중 하나를 넘었습니다. | 파일을 줄이거나 나누어 보내십시오. 상한은 담당자에게 확인하십시오. 재시도하지 마십시오. |
| 429 | 분당 또는 일일 사용 한도를 넘었습니다. | 60초 이상 기다린 뒤 재시도하십시오. 일일 한도는 날짜가 바뀌면 초기화되며, 기준은 한국 시간 오전 9시입니다. |
| 5xx | 서버 또는 문서 처리 엔진의 일시적인 오류입니다. | 지수 백오프로 최대 2회 재시도하십시오. 이 경우 본문이 JSON 이 아닐 수 있습니다. |
처리 시간·재시도
문서 1건의 판정이나 추출은 보통 수 초에서 수 분이 걸립니다. 문서 길이와 페이지 수, 스캔 여부에 따라 차이가 큽니다. 클라이언트 타임아웃은 20분 이상으로 설정하는 것을 권장합니다.
- 동시 요청은 문서 4~8건 이내로 제한하는 것을 권장합니다. 많이 보내면 한도에 빨리 도달하고 대기 시간이 길어집니다.
- 요청 1건이 내부적으로 여러 번의 처리 호출로 나뉘므로, 사용량 한도는 보낸 요청 수보다 빠르게 소모될 수 있습니다.
- 비동기 작업 조회는 3초 간격으로 하고, 전체 대기 시간에도 상한을 두어 상한을 넘으면 실패로 처리하십시오.
| 상황 | 재시도 여부 | 대기 방법 |
|---|---|---|
| 네트워크 오류 또는 5xx (조회 GET, 파일 업로드) | 최대 2회 | 지수 백오프 (예: 2초, 4초) |
| 작업 생성 POST 의 네트워크 오류 | 응답을 받지 못한 경우에만 | 5xx 를 포함해 응답을 받았다면 재시도하지 마십시오. 재시도하면 작업이 중복 생성됩니다 |
| 429 | 재시도합니다 | 60초 이상 기다린 뒤 재시도합니다. 응답에 Retry-After 헤더가 없으므로 고정 대기 시간을 사용하십시오 |
| 그 밖의 4xx | 재시도하지 않습니다 | 요청 내용이나 키 권한을 고쳐야 합니다 |
데이터 처리와 보관
업로드한 원본 파일과 OCR 로 추출한 텍스트는 문서 처리 엔진으로 전송되어 처리됩니다. 연동 전에 전송 경로가 고객 정책에 맞는지 보안 담당 부서와 확인하십시오.
- 기본 모드에서는 업로드한 원본 파일이 문서 처리 엔진의 저장소에 남습니다. 파일은 API 키 단위로 격리되어 저장되며, 기간이 지나도 자동으로 삭제되지 않습니다.
- 기술신용평가 서류 입력은 원본을 저장하지 않습니다. 처리하는 동안만 문서 처리 엔진에 전달됩니다.
- 저장된 원본을 삭제해야 하면 담당자에게 요청하십시오.
버전과 상태 확인
이 문서는 2026-10-23 기준입니다. API 경로에는 버전 번호가 없으며(예: /v1 없음), 서버 버전을 응답에서 확인할 수 없습니다. 연동 테스트 전에 담당자에게 배포된 버전을 확인하십시오.
- 응답에 새 필드가 추가될 수 있습니다. 모르는 필드는 무시하도록 파서를 작성하십시오.
- 변경 사항은 담당자를 통해 확인하십시오.
서버 동작 확인은 인증 없이 호출할 수 있습니다. 이 경로는 서버가 응답하는지만 확인하며, 문서 처리 엔진의 연결 상태나 API 키 상태는 확인하지 않습니다.
curl -s https://clip-nicednb-tcb.floaterlab.com/api/health응답은 {"status":"ok"} 입니다. 키 상태와 잔여 한도는 GET https://clip-nicednb-tcb.floaterlab.com/api/key-status 에 x-clip-key 헤더를 보내 확인하십시오.
기능
기술신용평가 서류 입력
주주명부·4대보험 사업장 가입자 명부·경영시스템인증서에서 기술신용평가 입력 항목(주요주주, 인력, 인증 실적)을 뽑고, 지분율·신규채용·인증 유효 여부를 계산합니다. 값마다 원문 위치를 함께 돌려줍니다. 서류 원본은 처리 엔진에 저장하지 않습니다.
연동 순서
- 작업 만들기 —
POST /api/tcb/extract에 서류 파일들(files)과 평가일(evalDate)을 보내고jobId를 받습니다(HTTP 202). - 결과 조회 —
GET /api/tcb/extract?job=...를 같은 키로 3초 간격 호출합니다. 처리 중에는 끝난 파일들의 쪽 수로 진행률(progress)이 옵니다. - 입력 항목 사용 —
done이면result.files[].documents[]를 서류 종류(type)별로 읽어 평가 입력 화면에 채웁니다. 값마다 원문 위치(page,boxes)가 있어 근거를 보여 줄 수 있습니다.
import time
import requests
BASE = 'https://clip-nicednb-tcb.floaterlab.com'
HEADERS = {'x-clip-key': 'YOUR_API_KEY'}
def extract(paths, eval_date=None, wait_limit=1800):
files = [('files', (p.split('/')[-1], open(p, 'rb'))) for p in paths]
data = {'evalDate': eval_date} if eval_date else {}
r = requests.post(BASE + '/api/tcb/extract', headers=HEADERS, files=files, data=data, timeout=600)
r.raise_for_status() # 202
job = r.json()['jobId']
started = time.time()
while time.time() - started < wait_limit:
time.sleep(3)
s = requests.get(BASE + '/api/tcb/extract', headers=HEADERS, params={'job': job}, timeout=60)
body = s.json()
if body['status'] == 'pending':
continue
if body['status'] == 'done':
return body['result']
raise RuntimeError(f"{s.status_code} {body.get('error') or body['status']}")
raise TimeoutError(job)
result = extract(['shareholders.pdf', 'insurance-roster.pdf'], eval_date='2026-10-26')
print('입수 현황', result['received'])
for f in result['files']:
for d in f['documents']:
if d['type'] == 'shareholders':
for h in d['holders']:
print('주주', h['name']['value'], h['ratio']['value'], '%', '(최대주주)' if h['largest'] else '')
elif d['type'] == 'insurance_roster':
new = [m for m in d['members'] if m['newHire']]
print('종업원', d['headcount'], '명, 신규채용', len(new), '명')
elif d['type'] == 'iso_cert':
print('인증', d['standard']['value'], d['certNo']['value'], d['expiresOn']['value'], '유효' if d['valid'] else '만료/확인')
for x in result['failed']:
print('실패', x['fileName'], x['status'], x['error'])결과 읽는 법
결과는 파일 → 서류 → 항목 순서로 담깁니다. documents[] 의 type 으로 서류 종류를 구분하고, 항목 값은 모두 {value, text?, page?, boxes?} 형태입니다.
| 서류(type) | 평가 입력 항목 | 서버가 계산·판단하는 것 |
|---|---|---|
주주명부 (shareholders, 주식등변동상황명세서 포함) | 주주명·지분율·최대주주와의 관계·주식 수, 회사명·사업자등록번호·기준일·총주식수 | 지분율이 없으면 주식 수 ÷ 총주식수로 계산(ratioComputed), 최대주주(largest, 관계 "본인"), 합계(ratioSum)와 기타(others) |
4대보험 사업장 가입자 명부 (insurance_roster) | 성명·생년월일·보험별 자격취득일, 사업장·사업자등록번호·발급일, 적혀 있으면 직위·부서·경력·학위(손글씨 포함 — 행 수가 다르면 원문 위치로 행을 맞추고 경고) | 입사일 = 보험별 취득일 중 가장 이른 날(hiredOn, hiredFrom), 평가일 기준 1년 이내 신규채용(newHire), 종업원 수(headcount) |
경영시스템인증서 (iso_cert) | 인증 종류·인증번호·인증등록일(취득일자)·만료일자·업체명·주소·인증범위·인증기관 | 인증 종류 정리("ISO 9001"), 만료일이 없으면 "9999-12-31", 평가일 기준 유효(valid) |
result.received는 서류 입수 현황입니다(종류별 찾은 서류 수). 0 이면 해당 서류가 입수되지 않은 것입니다.- 여러 명부(다른 사업장·다른 파일)에 같은 사람이 있으면
members[].samePerson이 1 이상이고,result.uniqueHeadcount는 동일인물을 한 번만 센 종업원 수입니다. - 한 파일에 같은 종류 서류가 여러 부 있으면(예: 발급번호가 다른 명부 두 부) 서류가 따로 나옵니다.
warnings는 사람이 확인할 점입니다. 값이 틀렸다는 뜻이 아니라 원문과 대조가 필요하다는 뜻입니다.- 값마다 PIN 원문(
text)과 키 이름(key)이 함께 오고, clip 이 정리·계산·대체한 값에는 그 방법(how)이 붙습니다. 원문 그대로인 값에는how가 없습니다. 판정 전 PIN 원본 키 전부는 서류의pinKeys에 있습니다. page가 없는 값은 계산했거나 문서에 없는 값입니다.page는 있는데boxes가 없으면 그 쪽에서 읽었지만 정확한 자리는 찾지 못한 값입니다.- 생년월일은 주민등록번호 앞 6자리만 담습니다. 원문의 앞자리가 6자리가 아니면(가려진 경우 등) 지어내지 않고 null 입니다.
화면 작업과 API
| 화면(/tcb)에서 | API 에서 |
|---|---|
| 서류 파일을 올린다(올릴 때마다 쌓임) | POST /api/tcb/extract — 화면은 파일마다 작업을 하나씩 만든다 |
| 평가일을 정한다 | evalDate |
| 서류 입수 현황 | result.received |
| 파일별 쪽 분류(대상 아님·분류 보정) | files[].pages[], otherPages, pages[].retyped |
| 주요주주·인력·인증 실적 표 | documents[] (type 별) |
| 값에 마우스를 올려 원문 근거 보기 | 각 값의 page 와 boxes |
| CSV·JSON 받기 | result 를 그대로 저장하거나 표로 바꾼다 |
엔드포인트
/api/tcb/extract서류 입력 작업 만들기
서류 파일들을 보내 기술신용평가 입력 항목 추출 작업을 만들고 jobId 를 받습니다.
파일마다 쪽별로 문서 유형을 분류하고, 대상 서류(주주명부·4대보험 사업장 가입자 명부·경영시스템인증서)에서 평가 입력 항목을 뽑습니다. 업로드 단계 없이 이 요청에 파일을 바로 보냅니다. 한 파일에 여러 서류가 섞여 있어도 되고, 대상이 아닌 쪽은 건너뜁니다.
처리 시간 · 작업 생성은 즉시 끝납니다. 파일마다 전체를 한 번에 처리하며 쪽당 수 초가 걸립니다. 서버 전체에서 동시에 4개 파일까지 처리하고 나머지는 들어온 순서대로 기다립니다. 쪽 수·파일 크기 상한은 처리 엔진 설정을 따릅니다(넘으면 413).
요청 · multipart/form-data
| 이름 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| x-clip-key | header | string | 필수 | API 키입니다. 결과를 조회할 때도 같은 키를 보내야 합니다.예: YOUR_API_KEY |
| files | form | file | 필수 | 서류 파일입니다. 같은 이름으로 여러 번 보내면 여러 파일을 한 작업으로 처리합니다. PDF·TIFF·PNG·JPG·WEBP 를 받습니다.예: shareholders.pdf |
| evalDate | form | string | 선택 | 평가일(YYYY-MM-DD)입니다. 신규채용(입사 1년 이내)과 인증 유효 여부를 이 날짜 기준으로 판단합니다.기본값: 서버의 오늘 날짜(UTC)예: 2026-10-26 |
응답 · 200
작업이 만들어지면 HTTP 202 로 응답합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
| jobId* | string | 작업 식별자입니다. |
| total* | integer | 받은 파일 수입니다. |
{
"jobId": "7b0c2f4e-9a1d-4e3b-8c5f-2d6a1b3c4e5f",
"total": 3
}오류
| 상태 | 언제 | 본문 예 |
|---|---|---|
| 400 | files 가 없을 때입니다. | {"error":"서류가 없습니다. files 필드로 파일을 보내 주세요."} |
| 400 | evalDate 가 YYYY-MM-DD 형식이 아닐 때입니다. | {"error":"evalDate 는 YYYY-MM-DD 형식이어야 합니다."} |
| 401 | x-clip-key 헤더가 없을 때입니다. | {"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"} |
참고
- 서류 원본은 처리 엔진에 저장하지 않습니다. 처리하는 동안만 전달되고 남지 않습니다.
- 키가 무효하거나 권한이 없어도 작업은 만들어지며, 실패는 조회 단계에서 나타납니다. 연동 전에
GET https://clip-nicednb-tcb.floaterlab.com/api/key-status로 키 상태를 확인하십시오. - 처리 엔진의 일시 오류(5xx)는 파일마다 2번까지 자동으로 다시 시도합니다.
예제
# 1) 작업 만들기 — 파일 여러 개를 한 번에(files 를 반복)
curl -X POST 'https://clip-nicednb-tcb.floaterlab.com/api/tcb/extract' \
-H 'x-clip-key: YOUR_API_KEY' \
-F 'files=@shareholders.pdf' \
-F 'files=@insurance-roster.pdf' \
-F 'files=@iso9001.pdf' \
-F 'evalDate=2026-10-26'
# → 202 {"jobId":"...","total":3}
# 2) 결과 조회 — 같은 키로, 3초 간격
curl 'https://clip-nicednb-tcb.floaterlab.com/api/tcb/extract?job=JOB_ID' -H 'x-clip-key: YOUR_API_KEY'import time
import requests
BASE = 'https://clip-nicednb-tcb.floaterlab.com'
HEADERS = {'x-clip-key': 'YOUR_API_KEY'}
def extract(paths, eval_date=None, wait_limit=1800):
files = [('files', (p.split('/')[-1], open(p, 'rb'))) for p in paths]
data = {'evalDate': eval_date} if eval_date else {}
r = requests.post(BASE + '/api/tcb/extract', headers=HEADERS, files=files, data=data, timeout=600)
r.raise_for_status() # 202
job = r.json()['jobId']
started = time.time()
while time.time() - started < wait_limit:
time.sleep(3)
s = requests.get(BASE + '/api/tcb/extract', headers=HEADERS, params={'job': job}, timeout=60)
body = s.json()
if body['status'] == 'pending':
continue
if body['status'] == 'done':
return body['result']
raise RuntimeError(f"{s.status_code} {body.get('error') or body['status']}")
raise TimeoutError(job)
result = extract(['shareholders.pdf', 'insurance-roster.pdf'], eval_date='2026-10-26')
print('입수 현황', result['received'])
for f in result['files']:
for d in f['documents']:
if d['type'] == 'shareholders':
for h in d['holders']:
print('주주', h['name']['value'], h['ratio']['value'], '%', '(최대주주)' if h['largest'] else '')
elif d['type'] == 'insurance_roster':
new = [m for m in d['members'] if m['newHire']]
print('종업원', d['headcount'], '명, 신규채용', len(new), '명')
elif d['type'] == 'iso_cert':
print('인증', d['standard']['value'], d['certNo']['value'], d['expiresOn']['value'], '유효' if d['valid'] else '만료/확인')
for x in result['failed']:
print('실패', x['fileName'], x['status'], x['error'])const BASE = 'https://clip-nicednb-tcb.floaterlab.com';
const headers = { 'x-clip-key': 'YOUR_API_KEY' };
async function extract(files, evalDate) {
const form = new FormData();
for (const f of files) form.append('files', f); // File 또는 Blob
if (evalDate) form.append('evalDate', evalDate);
const r = await fetch(BASE + '/api/tcb/extract', { method: 'POST', headers, body: form });
if (r.status !== 202) throw new Error((await r.json()).error);
const { jobId } = await r.json();
for (;;) {
await new Promise((ok) => setTimeout(ok, 3000));
const s = await fetch(BASE + '/api/tcb/extract?job=' + jobId, { headers });
const body = await s.json();
if (body.status === 'pending') continue;
if (body.status === 'done') return body.result;
throw new Error(s.status + ' ' + (body.error || body.status));
}
}/api/tcb/extract작업 상태·결과 조회
jobId 로 진행률을 확인하고, 끝나면 서류별 입력 항목을 받습니다.
3초 간격으로 조회하십시오. 작업을 만든 키와 같은 키를 x-clip-key 헤더로 보내야 합니다. 다른 키로 조회하면 없는 작업과 같게 404 가 옵니다.
처리 시간 · 조회는 즉시 응답합니다.
요청
| 이름 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
| x-clip-key | header | string | 필수 | 작업을 만든 키입니다.예: YOUR_API_KEY |
| job | query | string | 필수 | 작업 만들기 응답의 jobId 입니다.예: 7b0c2f4e-9a1d-4e3b-8c5f-2d6a1b3c4e5f |
응답 · 200
HTTP 200 이면 status 가 pending(진행률 포함) 또는 done(결과 포함)입니다.
object | object | object | object
{
"status": "done",
"result": {
"evalDate": "2026-10-26",
"files": [
{
"fileName": "company-docs.pdf",
"pageCount": 3,
"pages": [
{
"page": 1,
"type": "주주명부"
},
{
"page": 2,
"type": "4대보험 사업장 가입자 명부"
},
{
"page": 3,
"type": "손익계산서"
}
],
"documents": [
{
"type": "shareholders",
"pages": [
1
],
"company": {
"value": "가나다테크 주식회사",
"page": 1,
"boxes": [
[
0.61,
0.82,
0.78,
0.84
]
]
},
"bizNo": {
"value": null
},
"asOf": {
"value": "2026-06-30",
"text": "2026년 06월 30일 현재",
"page": 1,
"boxes": [
[
0.38,
0.17,
0.62,
0.19
]
]
},
"totalShares": {
"value": 20000,
"text": "20,000",
"page": 1,
"boxes": [
[
0.4,
0.61,
0.49,
0.63
]
]
},
"holders": [
{
"name": {
"value": "홍길동",
"page": 1,
"boxes": [
[
0.08,
0.27,
0.15,
0.29
]
]
},
"relation": {
"value": "본인"
},
"shares": {
"value": 14000,
"text": "14,000",
"page": 1,
"boxes": [
[
0.41,
0.27,
0.49,
0.29
]
]
},
"ratio": {
"value": 70,
"text": "70%",
"page": 1,
"boxes": [
[
0.73,
0.27,
0.78,
0.29
]
]
},
"ratioComputed": false,
"corporate": false,
"largest": true
},
{
"name": {
"value": "(주)라마바",
"page": 1,
"boxes": [
[
0.08,
0.31,
0.17,
0.33
]
]
},
"relation": {
"value": null
},
"shares": {
"value": 6000,
"text": "6,000",
"page": 1,
"boxes": [
[
0.41,
0.31,
0.48,
0.33
]
]
},
"ratio": {
"value": 30,
"text": "30%",
"page": 1,
"boxes": [
[
0.73,
0.31,
0.78,
0.33
]
]
},
"ratioComputed": false,
"corporate": true,
"largest": false
}
],
"ratioSum": 100,
"others": null,
"warnings": [
"최대주주와의 관계가 문서에 없는 주주가 있습니다 — 입력 시 선택하세요."
]
},
{
"type": "insurance_roster",
"pages": [
2
],
"company": {
"value": "가나다테크 주식회사",
"page": 2,
"boxes": [
[
0.38,
0.27,
0.5,
0.28
]
]
},
"bizNo": {
"value": "123-45-67890",
"page": 2,
"boxes": [
[
0.38,
0.25,
0.48,
0.26
]
]
},
"issuedOn": {
"value": "2026-10-20",
"text": "2026-10-20 10:15",
"page": 2,
"boxes": [
[
0.55,
0.21,
0.66,
0.22
]
]
},
"issueNo": {
"value": "20261020000001",
"page": 2,
"boxes": [
[
0.24,
0.21,
0.36,
0.22
]
]
},
"headcount": 1,
"members": [
{
"no": "1",
"name": {
"value": "김철수",
"page": 2,
"boxes": [
[
0.42,
0.4,
0.46,
0.41
]
]
},
"birth": {
"value": "900101",
"page": 2,
"boxes": [
[
0.27,
0.4,
0.37,
0.41
]
]
},
"acquired": {
"pension": {
"value": null,
"text": "-",
"page": 2
},
"health": {
"value": "2026-03-02",
"text": "2026.03.02",
"page": 2,
"boxes": [
[
0.6,
0.4,
0.67,
0.41
]
]
},
"injury": {
"value": "2026-03-01",
"text": "2026.03.01",
"page": 2,
"boxes": [
[
0.72,
0.4,
0.79,
0.41
]
]
},
"employment": {
"value": "2026-03-01",
"text": "2026.03.01",
"page": 2,
"boxes": [
[
0.84,
0.4,
0.91,
0.41
]
]
}
},
"hiredOn": {
"value": "2026-03-01",
"text": "2026.03.01",
"page": 2,
"boxes": [
[
0.72,
0.4,
0.79,
0.41
]
]
},
"hiredFrom": "injury",
"newHire": true
}
],
"warnings": []
}
],
"otherPages": [
3
]
}
],
"failed": [],
"received": {
"shareholders": 1,
"insurance_roster": 1,
"iso_cert": 0
}
}
}오류
| 상태 | 언제 | 본문 예 |
|---|---|---|
| 404 | jobId 를 모르거나, 다른 키로 조회했거나, 만료(1시간)되었거나, 서버가 재시작되었을 때입니다. 작업을 다시 만드십시오. | {"status":"unknown"} |
| 401 | x-clip-key 헤더가 없을 때입니다. | {"error":"우측 상단에 API Key 를 입력해 주세요.","code":"api_key_required"} |
| 403 | 모든 파일이 실패했고 원인이 키 권한(OCR·키 추출) 부족일 때입니다. 실패 원인의 상태 코드가 그대로 옵니다(401·413·429·502 등). | {"status":"error","error":"Permission denied"} |
참고
- 이 기능의 결과는 끝난 뒤 1시간 동안 여러 번 조회할 수 있습니다. 한 번 읽으면 지워지는 다른 비동기 작업과 다릅니다.
- 일부 파일만 실패하면 작업은
done이고 실패한 파일은result.failed에 담깁니다. 모든 파일이 실패했을 때만error입니다. - 작업 정보는 서버 메모리에 있어 서버가 재시작되면 사라집니다(404
unknown).
예제
curl 'https://clip-nicednb-tcb.floaterlab.com/api/tcb/extract?job=JOB_ID' -H 'x-clip-key: YOUR_API_KEY's = requests.get(BASE + '/api/tcb/extract', headers=HEADERS, params={'job': job_id}, timeout=60)
body = s.json() # pending 이면 body.get('progress') — 끝난 파일들의 쪽 수