기업 리스크 브리프 (MCP 도구)
사업자등록번호 하나로 사업자상태, 진위확인, 나라장터 제재, 건설업, 폐기물 행정처분 다섯 공공데이터 조회를 한 번의 MCP 호출로 묶어 받는 도구의 준비, 요청, 응답, 오류 안내입니다.
이 도구가 하는 일
사업자등록번호 하나를 기준으로 공공데이터 다섯 조회(사업자상태, 진위확인, 나라장터 제재, 건설업 행정처분, 폐기물 행정처분)를 한 번의 호출로 실행해 하나의 요약으로 돌려주는 MCP 도구입니다. 다섯 조회는 함께 제출되어 가장 오래 걸리는 쪽만큼만 기다립니다. 다섯 조회 모두 XBOSS 의 플랫폼 키로 도니 고객의 기관 계정을 연동할 필요가 없습니다.
이 문서에서는 다섯 조회 하나하나를 leg 이라고 부릅니다. 응답의 legs 에는 leg 마다 원래 조회의 결과가 그대로 담기고, computed 에는 다섯 결과를 사업자 하나 기준으로 간추린 값이 담깁니다.
| 도구 이름 | company_info_pack__brief_v1__generate |
|---|---|
| 호출 방식 | tools/call (JSON-RPC 2.0) |
| 엔드포인트 | POST https://api.xdata.kr/mcp |
| 인증 | Authorization: Basic |
| 환경 헤더 | X-MCP-Env-Scope (선택) |
| 최대 대기 | 조회 하나 60초, 호출 전체 90초 |
이 도구는 MCP 로 부를 수 있습니다. REST 로 부르는 법은 아직 이 페이지에 없습니다(준비 중입니다). 다섯 조회를 REST 로 따로 부르는 방법도 아직 개발자 문서에 없습니다(공공데이터 조회 페이지가 준비 중입니다). 주소, 인증, 환경 헤더는 MCP 연동 개요와 같습니다.
다섯 조회(leg)
| leg | 원자 액션 | 따로 부르는 MCP 도구 |
|---|---|---|
| 사업자상태 | opendata.nts.status | opendata__nts__status |
| 진위확인 | opendata.nts.validate | opendata__nts__validate |
| 나라장터제재 | opendata.g2b.sanction | opendata__g2b__sanction |
| 건설행정처분 | opendata.kiscon.sanction | opendata__kiscon__sanction |
| 폐기물행정처분 | opendata.env.waste_sanction | opendata__env__waste_sanction |
다섯 조회는 개발자 문서에 아직 자기 페이지가 없습니다(공공데이터 조회 페이지 준비 중). 위 표의 「따로 부르는 MCP 도구」는 같은 조회를 이 팩 없이 단독으로 부르고 싶을 때 쓰는 이름입니다 - 인자와 응답 모양은 이 팩과 다를 수 있습니다.
부르기 전에 준비할 것
- MCP 서버를 연결합니다
MCP 연동 개요의 순서대로 AI 앱이나 MCP 클라이언트에 XBOSS MCP 서버를 연결합니다. 기관 연동이나 자산 선택 같은 사전 준비는 없습니다.
요청
도구 이름과 arguments 를 담아 tools/call 을 보냅니다. bizno 는 다섯 조회 모두의 기준이 되고, start_dt, p_nm 은 진위확인 조회가, sDate, eDate 는 건설행정처분 조회가 씁니다. corp_name 은 건설행정처분 조회를 보조하는 선택 항목입니다.
| 필드 | 타입 | 필수 | 설명 | 예시 |
|---|---|---|---|---|
| bizno | string | 필수 | 조회할 사업자등록번호입니다(10자리, 하이픈 없이). 다섯 조회 모두 이 번호를 기준으로 합니다. | 1234567890 |
| start_dt | string | 필수 | 진위확인 조회에 쓰는 개업일입니다(YYYYMMDD). 보내신 사업자등록번호와 이 개업일이 실제와 다르면 진위확인 결과도 달라집니다. | 20200101 |
| p_nm | string | 필수 | 진위확인 조회에 쓰는 대표자 이름입니다. 사업자등록증의 대표자명과 같아야 합니다. | 홍길동 |
| sDate | string | 필수 | 건설행정처분 조회 기간의 시작일입니다(YYYYMMDD). | 20200101 |
| eDate | string | 필수 | 건설행정처분 조회 기간의 종료일입니다(YYYYMMDD). 시작일과 같거나 그 이후여야 합니다. | 20261231 |
| corp_name | string | 선택 | 건설행정처분 조회에서 이름을 맞춰 보는 선택 항목입니다(상호명). 생략할 수 있습니다. | — |
| account_link_id | - | 받지 않음 | 받지 않는 칸입니다. 보내도 무시됩니다. 다섯 조회 모두 플랫폼 키로 도는 opendata 조회라 고객의 기관 연동이 필요 없습니다. | — |
| idempotency_key | string | 선택 | 이 실행의 멱등 키입니다(비어 있지 않은 문자열, 앞뒤 공백은 지우고 128자까지 씁니다). 같은 키와 같은 조회 조건으로 다시 부르면 새 작업을 만들지 않고 이전 작업을 그대로 돌려받아 같은 결과가 옵니다. 같은 키로 조회 조건을 바꾸면 조회가 오류 번호 -32602(error_code IDEMPOTENCY_KEY_CONFLICT)로 실패하니, 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고 새 요청이면 새 키를 쓰세요. 넣지 않으면 서버가 호출마다 새 키를 만들어 매번 새 작업으로 처리합니다. | brief-2026-09 |
# 기업정보 브리프 호출
# echo -n "<client_id>:<client_secret>" | base64
export MCP_URL="https://api.xdata.kr/mcp"
export MCP_AUTH_B64="<base64_결과_붙여넣기>"
curl -sS -X POST "$MCP_URL" \
-H "Authorization: Basic $MCP_AUTH_B64" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "company_info_pack__brief_v1__generate",
"arguments": {
"bizno": "1234567890",
"start_dt": "20200101",
"p_nm": "홍길동",
"sDate": "20200101",
"eDate": "20261231",
"idempotency_key": "brief-2026-09"
}
}
}'응답 읽기
호출이 성공하면 result.content[0].text 에 응답 본문이 JSON 문자열로 담겨 옵니다. JSON.parse 같은 함수로 풀어 씁니다. 데모 환경에서는 남은 호출량이 두 번째 content 블록과 _meta 에 함께 실립니다(MCP 연동 개요의 「응답에 실리는 _meta」).
| 필드 | 타입 | 있음 | 설명 |
|---|---|---|---|
| composite | boolean | 항상 | 항상 true 입니다. 일반 도구 결과와 이 칸으로 구분합니다. |
| tool_name | string | 항상 | 호출한 MCP 도구 이름입니다. |
| provider | string | 항상 | 팩의 provider 이름입니다. |
| action | string | 항상 | 팩의 action 이름입니다. |
| idempotency_key | string | 항상 | 이 실행의 멱등 키입니다. arguments 에 idempotency_key 를 넣었으면 그 값(앞뒤 공백을 지우고 128자까지), 넣지 않았으면 서버가 만든 값입니다. |
| partial | boolean | 항상 | 보고가 완전하지 않으면 true 입니다. leg 하나라도 성공하지 못했거나 계산을 하지 못했을 때입니다. true 여도 받은 자료는 그대로 쓸 수 있습니다 - 무엇이 비었는지는 legs 와 computed 가 알려 줍니다. |
| legs | array | 항상 | leg 다섯(사업자상태, 진위확인, 나라장터제재, 건설행정처분, 폐기물행정처분)의 원본 결과입니다. 정의 순서로 옵니다. |
| legs[].leg_index | number | 항상 | leg 의 순번입니다(0 부터, 정의 순서). |
| legs[].provider | string | 항상 | 이 leg 이 부른 기관입니다. |
| legs[].action | string | 항상 | 이 leg 이 실행한 원자 액션입니다. |
| legs[].label_ko | string | 항상 | 이 leg 을 화면에 보일 한국어 이름입니다. |
| legs[].status | string | 항상 | leg 의 결과 상태입니다. SUCCEEDED, FAILED, SKIPPED 중 하나입니다. SKIPPED 는 진위확인 leg 이 필수 바깥 인자(start_dt, p_nm)를 못 받았을 때만 나옵니다. |
| legs[].job_id | string | null | 항상 | 이 leg 이 실행한 개별 작업의 아이디입니다. 제출하지 못한 leg 은 null 입니다. |
| legs[].result | object | null | 항상 | 성공한 leg 의 결과 본문입니다 - 그 원자 액션(opendata)의 응답과 같습니다. 실패했거나 건너뛰었으면 null 입니다. |
| legs[].error | object | null | 항상 | 실패한 leg 의 오류입니다. 성공했으면 null 입니다. |
| legs[].error.code | number | 조건부 | 오류 번호입니다(JSON-RPC 코드). |
| legs[].error.message | string | 조건부 | 이 leg 의 오류 문장입니다. 프로그램의 분기에는 code 와 data.error_code 를 쓰고, 문장은 사람이 읽는 용도로만 쓰세요. |
| legs[].error.data | object | null | 조건부 | 오류 종류별 부가 정보입니다. 보통 error_code 가 들어 있고 종류에 따라 다른 칸이 더해집니다. 부가 정보가 없는 오류는 이 칸이 없거나 null 입니다. 시간 초과일 때의 칸은 「시간 초과」 절의 표에 있습니다. |
| succeeded_count | number | 항상 | 성공한 leg 수입니다. |
| failed_count | number | 항상 | 성공하지 못한 leg 수입니다. |
| computed | object | null | 항상 | 다섯 leg 을 사업자 하나 기준으로 요약한 칸입니다. 계산을 하지 못했으면 null 이고 이유는 computed_error 에 있습니다. |
| computed.bizno | string | 조건부 | 요청한 사업자등록번호를 그대로 돌려줍니다. |
| computed.as_of | string | 조건부 | 이 요약을 만든 시각입니다(UTC). |
| computed.sDate | string | 조건부 | 건설행정처분 조회 시작일입니다. 보내지 않았으면 null 입니다. |
| computed.eDate | string | 조건부 | 건설행정처분 조회 종료일입니다. 보내지 않았으면 null 입니다. |
| computed.legs | array | 조건부 | 다섯 leg 결과를 계산이 다시 간추린 칸입니다(leg_index, provider, action, status, job_id, match_count, 실패하거나 건너뛴 leg 은 error). |
| computed.legs[].leg_index | number | 조건부 | 봉투의 legs 와 같은 순번입니다. |
| computed.legs[].provider | string | 조건부 | 이 leg 이 부른 기관입니다(opendata). |
| computed.legs[].action | string | 조건부 | 이 leg 이 실행한 원자 액션입니다. |
| computed.legs[].status | string | 조건부 | 이 leg 의 상태입니다. SUCCEEDED, FAILED, SKIPPED 중 하나입니다. |
| computed.legs[].job_id | string | null | 조건부 | 이 leg 이 실행한 개별 작업의 아이디입니다. 제출하지 못했으면(SKIPPED) null 입니다. |
| computed.legs[].match_count | number | 조건부 | 성공한 leg 의 결과에 matches 배열이 있으면 그 길이입니다. |
| computed.legs[].error | object | 조건부 | 실패하거나 건너뛴 leg 의 오류입니다(code, message, data). |
| computed.legs[].error.code | number | 조건부 | 오류 번호입니다. |
| computed.legs[].error.message | string | 조건부 | 이 leg 의 오류 문장입니다. 프로그램의 분기에는 code 와 data 안의 error_code 를 쓰고, 문장은 사람이 읽는 용도로만 쓰세요. |
| computed.legs[].error.data | object | 조건부 | 오류 종류별 부가 정보입니다. 봉투 위쪽의 legs[].error.data 와 같은 값입니다(계산이 leg 오류를 그대로 옮깁니다) - 그 칸의 설명을 따릅니다. |
| computed.flags | object | 조건부 | 다섯 조회 각각을 한 낱말로 요약한 칸입니다 - 사업자상태, 진위확인, 나라장터제재, 건설행정처분, 폐기물행정처분 다섯 키가 늘 있습니다. |
| computed.flags.nts_status | object | 조건부 | 사업자상태 조회의 요약입니다(status, claim, 성공했으면 match_count 도). |
| computed.flags.nts_status.status | string | 조건부 | 사업자상태 조회 leg 의 상태입니다 - SUCCEEDED 또는 MISSING(성공하지 못함) 또는 SKIPPED 입니다. |
| computed.flags.nts_status.match_count | number | 조건부 | claim 이 matches_reported 일 때만 있습니다 - 그 조회의 matches 배열 길이입니다. |
| computed.flags.nts_status.claim | string | 조건부 | 그 결과를 어떻게 읽어야 하는지입니다 - matches_reported(정상 조회), payload_without_matches_list(모양이 다름), unavailable(못 읽음) 중 하나입니다. |
| computed.flags.nts_validate | object | 조건부 | 진위확인 조회의 요약입니다. 그 leg 이 SKIPPED 면 status 가 SKIPPED 이고 claim 은 unavailable 입니다. |
| computed.flags.nts_validate.status | string | 조건부 | 진위확인 조회 leg 의 상태입니다 - SUCCEEDED 또는 MISSING 또는 SKIPPED 입니다. 필수 바깥 인자(start_dt, p_nm)가 없으면 SKIPPED 입니다. |
| computed.flags.nts_validate.match_count | number | 조건부 | claim 이 matches_reported 일 때만 있습니다. |
| computed.flags.nts_validate.claim | string | 조건부 | 그 결과를 어떻게 읽어야 하는지입니다 - matches_reported, payload_without_matches_list, unavailable 중 하나입니다. |
| computed.flags.g2b_sanction | object | 조건부 | 나라장터제재 조회의 요약입니다. |
| computed.flags.g2b_sanction.status | string | 조건부 | 나라장터제재 조회 leg 의 상태입니다 - SUCCEEDED 또는 MISSING 입니다(이 leg 은 SKIPPED 되지 않습니다 - 바깥 인자 리셰이프가 없습니다). |
| computed.flags.g2b_sanction.match_count | number | 조건부 | claim 이 matches_reported 일 때만 있습니다. |
| computed.flags.g2b_sanction.claim | string | 조건부 | matches_reported, payload_without_matches_list, unavailable 중 하나입니다. |
| computed.flags.kiscon_sanction | object | 조건부 | 건설행정처분 조회의 요약입니다. |
| computed.flags.kiscon_sanction.status | string | 조건부 | 건설행정처분 조회 leg 의 상태입니다 - SUCCEEDED 또는 MISSING 입니다. |
| computed.flags.kiscon_sanction.match_count | number | 조건부 | claim 이 matches_reported 일 때만 있습니다. |
| computed.flags.kiscon_sanction.claim | string | 조건부 | matches_reported, payload_without_matches_list, unavailable 중 하나입니다. |
| computed.flags.waste_sanction | object | 조건부 | 폐기물행정처분 조회의 요약입니다. |
| computed.flags.waste_sanction.status | string | 조건부 | 폐기물행정처분 조회 leg 의 상태입니다 - SUCCEEDED 또는 MISSING 입니다. |
| computed.flags.waste_sanction.match_count | number | 조건부 | claim 이 matches_reported 일 때만 있습니다. |
| computed.flags.waste_sanction.claim | string | 조건부 | matches_reported, payload_without_matches_list, unavailable 중 하나입니다. |
| computed.coverage_notes | array | 조건부 | 조회 결과를 읽을 때 반드시 알아야 하는 한국어 안내 네 문장입니다 - 빈 결과가 무혐의를 뜻하지 않는다는 것을 포함합니다. |
| computed_error | object | null | 항상 | 계산을 하지 못했을 때만 값이 있고(computed 는 null), 그 밖에는 null 입니다. |
| computed_error.reason | string | 조건부 | 계산하지 못한 이유입니다. 값마다 뜻과 결과(보고가 partial 로 오는지, 오류로 끝나는지)는 「시간 초과와 계산 실패」 절의 표에 있습니다. |
| computed_error.message | string | 조건부 | 이유를 설명하는 문장입니다. 마감으로 계산을 건너뛴 봉투(deadline_exhausted)는 한국어 안내 문장이고, 계산에 필요한 leg 자료가 없는 봉투(dependency_missing)는 어느 leg 의 결과가 없는지 알리는 영어 진단 문장입니다. 사람이 읽는 문장이니 프로그램의 분기에는 reason 을 쓰세요. |
아래는 다섯 조회가 모두 성공했을 때의 응답 본문입니다. legs[].result 는 원래 조회의 응답과 같아서 예시에서는 줄여 적었습니다.
{
"composite": true,
"tool_name": "company_info_pack__brief_v1__generate",
"provider": "company_info_pack",
"action": "company_info_pack.brief_v1.generate",
"idempotency_key": "brief-2026-09",
"partial": false,
"legs": [
{
"leg_index": 0,
"provider": "opendata",
"action": "opendata.nts.status",
"label_ko": "사업자상태",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10",
"result": {
"...": "opendata.nts.status 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 1,
"provider": "opendata",
"action": "opendata.nts.validate",
"label_ko": "진위확인",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN11",
"result": {
"...": "opendata.nts.validate 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 2,
"provider": "opendata",
"action": "opendata.g2b.sanction",
"label_ko": "나라장터제재",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN12",
"result": {
"...": "opendata.g2b.sanction 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 3,
"provider": "opendata",
"action": "opendata.kiscon.sanction",
"label_ko": "건설행정처분",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN13",
"result": {
"...": "opendata.kiscon.sanction 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 4,
"provider": "opendata",
"action": "opendata.env.waste_sanction",
"label_ko": "폐기물행정처분",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN14",
"result": {
"...": "opendata.env.waste_sanction 의 응답과 같습니다"
},
"error": null
}
],
"succeeded_count": 5,
"failed_count": 0,
"computed": {
"bizno": "1234567890",
"as_of": "2026-09-29T00:00:00Z",
"sDate": "20200101",
"eDate": "20261231",
"legs": [
{
"leg_index": 0,
"provider": "opendata",
"action": "opendata.nts.status",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10",
"match_count": 0
},
{
"leg_index": 1,
"provider": "opendata",
"action": "opendata.nts.validate",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN11",
"match_count": 0
},
{
"leg_index": 2,
"provider": "opendata",
"action": "opendata.g2b.sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN12",
"match_count": 0
},
{
"leg_index": 3,
"provider": "opendata",
"action": "opendata.kiscon.sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN13",
"match_count": 0
},
{
"leg_index": 4,
"provider": "opendata",
"action": "opendata.env.waste_sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN14",
"match_count": 0
}
],
"flags": {
"nts_status": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"nts_validate": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"g2b_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"kiscon_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"waste_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
}
},
"coverage_notes": [
"조회 결과가 비어 있다고 해서 제재 이력이 없다고 단정하지 마세요. '확인된 제재 없음' 과 '제재가 없음' 은 다릅니다.",
"건설업 행정처분은 요청하신 조회 기간 안만 봅니다. 기간 밖의 이력은 이 결과에 들어 있지 않습니다.",
"환경(폐기물) 제재는 수집된 범위 안에서만 조회됩니다. 비어 있다는 것이 곧 이력이 없다는 뜻은 아닙니다.",
"사업자 진위확인은 보내 주신 개업일과 대표자명을 기준으로 한 결과입니다. 그 값이 다르면 결과도 달라집니다."
]
},
"computed_error": null
}조회 결과가 비어 있다고 해서 제재나 처분 이력이 없다고 단정하지 마세요. computed.coverage_notes 에 이 판단을 돕는 한국어 안내 네 문장이 함께 옵니다 - 빈 결과와 「확인된 제재 없음」은 다르고, 건설업 행정처분과 환경 제재는 조회 기간과 수집 범위 안에서만 보인다는 것을 포함합니다.
진위확인 조회를 건너뛰었을 때
start_dt 나 p_nm 을 비워 보내면(빈 문자열) 스키마는 통과하지만 진위확인 leg 만 건너뜁니다(legs[].status 가 SKIPPED). 나머지 네 조회는 그대로 진행되고 응답은 오류가 아니라 성공이며 partial 이 true 입니다. computed.flags.nts_validate 는 {"status": "SKIPPED", "claim": "unavailable"} 로 옵니다.
{
"composite": true,
"tool_name": "company_info_pack__brief_v1__generate",
"provider": "company_info_pack",
"action": "company_info_pack.brief_v1.generate",
"idempotency_key": "brief-2026-09",
"partial": true,
"legs": [
{
"leg_index": 0,
"provider": "opendata",
"action": "opendata.nts.status",
"label_ko": "사업자상태",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10",
"result": {
"...": "opendata.nts.status 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 1,
"provider": "opendata",
"action": "opendata.nts.validate",
"label_ko": "진위확인",
"status": "SKIPPED",
"job_id": null,
"result": null,
"error": {
"code": -32004,
"message": "Composite dependency unavailable: p_nm is required for nts.validate",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 1
}
}
},
{
"leg_index": 2,
"provider": "opendata",
"action": "opendata.g2b.sanction",
"label_ko": "나라장터제재",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN12",
"result": {
"...": "opendata.g2b.sanction 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 3,
"provider": "opendata",
"action": "opendata.kiscon.sanction",
"label_ko": "건설행정처분",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN13",
"result": {
"...": "opendata.kiscon.sanction 의 응답과 같습니다"
},
"error": null
},
{
"leg_index": 4,
"provider": "opendata",
"action": "opendata.env.waste_sanction",
"label_ko": "폐기물행정처분",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN14",
"result": {
"...": "opendata.env.waste_sanction 의 응답과 같습니다"
},
"error": null
}
],
"succeeded_count": 4,
"failed_count": 1,
"computed": {
"bizno": "1234567890",
"as_of": "2026-09-29T00:00:00Z",
"sDate": "20200101",
"eDate": "20261231",
"legs": [
{
"leg_index": 0,
"provider": "opendata",
"action": "opendata.nts.status",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10",
"match_count": 0
},
{
"leg_index": 1,
"provider": "opendata",
"action": "opendata.nts.validate",
"status": "SKIPPED",
"job_id": null,
"error": {
"code": -32004,
"message": "Composite dependency unavailable: p_nm is required for nts.validate",
"data": {
"error_code": "COMPOSITE_DEPENDENCY_UNAVAILABLE",
"leg_index": 1
}
}
},
{
"leg_index": 2,
"provider": "opendata",
"action": "opendata.g2b.sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN12",
"match_count": 0
},
{
"leg_index": 3,
"provider": "opendata",
"action": "opendata.kiscon.sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN13",
"match_count": 0
},
{
"leg_index": 4,
"provider": "opendata",
"action": "opendata.env.waste_sanction",
"status": "SUCCEEDED",
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN14",
"match_count": 0
}
],
"flags": {
"nts_status": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"nts_validate": {
"status": "SKIPPED",
"claim": "unavailable"
},
"g2b_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"kiscon_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
},
"waste_sanction": {
"status": "SUCCEEDED",
"match_count": 0,
"claim": "matches_reported"
}
},
"coverage_notes": [
"조회 결과가 비어 있다고 해서 제재 이력이 없다고 단정하지 마세요. '확인된 제재 없음' 과 '제재가 없음' 은 다릅니다.",
"건설업 행정처분은 요청하신 조회 기간 안만 봅니다. 기간 밖의 이력은 이 결과에 들어 있지 않습니다.",
"환경(폐기물) 제재는 수집된 범위 안에서만 조회됩니다. 비어 있다는 것이 곧 이력이 없다는 뜻은 아닙니다.",
"사업자 진위확인은 보내 주신 개업일과 대표자명을 기준으로 한 결과입니다. 그 값이 다르면 결과도 달라집니다."
]
},
"computed_error": null
}사업자등록번호를 비워 보냈을 때
bizno 를 빈 문자열로 보내면 다섯 조회의 성공 여부와 무관하게 요약(computed)을 만들지 못합니다 - computed 가 null 이고 computed_error.reason 이 dependency_missing 입니다. 받은 leg 결과는 그대로 legs 에 남고 partial 은 true 입니다.
| computed_error.reason | 결과 | 뜻 |
|---|---|---|
| deadline_exhausted | 보고가 오고 partial | 호출 전체 마감 시간이 다 돼 계산을 건너뛰었습니다. 받은 자료는 legs 에 있고 봉투는 partial 로 옵니다(computed 는 null). |
| dependency_missing | 보고가 오고 partial | 계산에 필요한 leg 자료가 없습니다. 받은 자료는 legs 에 있고 봉투는 partial 로 옵니다. |
| validation_failed | 오류로 끝남 | 계산 중 값 검증에서 실패했습니다(우리 쪽 결함). 봉투를 주지 않고 오류로 끝나며 받은 leg 결과는 오류의 data 에 실립니다. |
| builder_raised | 오류로 끝남 | 계산 중 예외가 났습니다(우리 쪽 결함). 봉투를 주지 않고 오류로 끝나며 받은 leg 결과는 오류의 data 에 실립니다. |
시간 초과
조회 하나는 제출한 뒤 최대 60초까지 기다리고, 호출 전체는 최대 90초 안에 끝납니다. 60초 안에 끝나지 않은 조회는 그 조회만 시간 초과로 실패하고 나머지 결과는 그대로 옵니다. 그 조회의 error.code 는 -32003 이고 error.data 에 다음 칸이 옵니다. 다섯 조회는 모두 읽기라 다시 불러도 됩니다.
| error.data 의 칸 | 뜻 |
|---|---|
| job_id | 시간 안에 끝나지 않은 작업의 아이디입니다. 작업은 서버에서 계속 돌 수 있습니다. |
| timeout_seconds | 이 조회를 기다린 한도(초)입니다. 보통 60 이고, 호출 전체 마감(90초)이 가까웠으면 남은 시간이라 더 작습니다(소수일 수 있습니다). |
| safe_to_retry | 다시 불러도 되는지 알려 줍니다. 다섯 조회는 모두 읽기라 true 입니다. |
| status_url | 그 작업의 결과를 GET 으로 읽을 수 있는 주소입니다(REST 로 연동한 서비스에서 같은 API 키를 씁니다). |
{
"code": -32003,
"message": "요청이 60초 안에 끝나지 않았습니다(job job_01JC8YH8R0E1ABCDEFGHJKMN10). 서버가 바쁠 수 있습니다. 같은 API 키로 연동한 서비스는 GET https://api.xdata.kr/v1/jobs/job_01JC8YH8R0E1ABCDEFGHJKMN10 로 결과를 읽을 수 있습니다. 잠시 뒤 다시 시도하거나, 요청 범위를 줄일 수 있으면 줄이세요.",
"data": {
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10",
"timeout_seconds": 60,
"safe_to_retry": true,
"status_url": "https://api.xdata.kr/v1/jobs/job_01JC8YH8R0E1ABCDEFGHJKMN10"
}
}호출 전체 마감(90초)에 조회를 제출하거나 기다리기 시작하기 전이었다면 그 조회의 error.data.error_code 가 COMPOSITE_TIMEOUT 이고 error.data 에 다음 칸이 옵니다. error.message 는 사람이 읽는 한국어 문장이라 분기에는 error.data.error_code 를 쓰세요.
| error.data 의 칸 | 뜻 |
|---|---|
| error_code | 항상 COMPOSITE_TIMEOUT 입니다. 이 값으로 호출 전체 마감을 가려냅니다. |
| timeout_seconds | 호출 전체 대기 한도(초)입니다. |
| phase | 마감이 된 단계의 이름입니다(진단용 - 분기에 쓰지 않습니다). |
| submitted | 그 조회의 작업을 이미 제출했는지 알려 줍니다. true 면 job_id 가 함께 옵니다. |
| job_id | 제출한 작업의 아이디입니다. 제출하지 못했으면 이 칸이 없습니다. |
{
"code": -32003,
"message": "요청이 90초 안에 끝나지 않았습니다. 일부 조회는 서버에서 계속 진행 중입니다. 같은 API 키로 연동한 서비스는 GET https://api.xdata.kr/v1/jobs/job_01JC8YH8R0E1ABCDEFGHJKMN10 로 결과를 읽을 수 있습니다. 잠시 뒤 다시 시도해 주세요. 조회 기간이나 범위를 줄일 수 있으면 줄이면 더 빨리 끝납니다.",
"data": {
"error_code": "COMPOSITE_TIMEOUT",
"timeout_seconds": 90,
"phase": "Job polling",
"submitted": true,
"job_id": "job_01JC8YH8R0E1ABCDEFGHJKMN10"
}
}같은 요청을 다시 보낼 때
arguments 에 idempotency_key 를 문자열로 넣으면 그 값이 이 실행의 멱등 키가 됩니다(앞뒤 공백은 지우고 128자까지 씁니다). 같은 키와 같은 조회 조건으로 다시 부르면 새 작업을 만들지 않고 이전 작업을 그대로 돌려받아 같은 결과가 옵니다. 이전 작업이 실패했다면 실패한 결과가 그대로 오고, 아직 진행 중이면 그 작업을 이어서 기다립니다. 다시 시도하려면 새 키를 쓰세요.
같은 키로 조회 조건(인자)을 바꿔 부르면 조회가 실패합니다. 그 조회의 오류는 code 가 -32602 이고 data.error_code 가 IDEMPOTENCY_KEY_CONFLICT 입니다. 인자는 다섯 조회에 모두 전달되므로 대개 다섯 조회가 모두 이렇게 실패하고, 그러면 호출이 JSON-RPC error(번호 -32602)로 끝나며 조회별 오류는 error.data.legs[] 에 있습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 넣지 마세요(넣지 않으면 서버가 만듭니다). 오류의 safe_to_retry 는 false 입니다 - 같은 인자로 다시 불러도 같은 오류가 납니다.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"composite": true,
"tool_name": "company_info_pack__brief_v1__generate",
"idempotency_key": "brief-2026-09",
"legs": [
{
"leg_index": 0,
"provider": "opendata",
"action": "opendata.nts.status",
"label_ko": "사업자상태",
"status": "FAILED",
"job_id": null,
"result": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 1,
"provider": "opendata",
"action": "opendata.nts.validate",
"label_ko": "진위확인",
"status": "FAILED",
"job_id": null,
"result": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 2,
"provider": "opendata",
"action": "opendata.g2b.sanction",
"label_ko": "나라장터제재",
"status": "FAILED",
"job_id": null,
"result": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 3,
"provider": "opendata",
"action": "opendata.kiscon.sanction",
"label_ko": "건설행정처분",
"status": "FAILED",
"job_id": null,
"result": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
},
{
"leg_index": 4,
"provider": "opendata",
"action": "opendata.env.waste_sanction",
"label_ko": "폐기물행정처분",
"status": "FAILED",
"job_id": null,
"result": null,
"error": {
"code": -32602,
"message": "같은 idempotency_key 로 내용이 다른 요청이 이미 들어왔습니다. 같은 요청을 다시 보내는 것이면 처음 보낸 인자 그대로 보내고, 새 요청이면 idempotency_key 를 새 값으로 바꾸거나 아예 넣지 마세요(넣지 않으면 서버가 만듭니다).",
"data": {
"error_code": "IDEMPOTENCY_KEY_CONFLICT",
"safe_to_retry": false
}
}
}
]
}
}
}키를 넣지 않으면 서버가 호출마다 새 키를 만들어 매번 새 작업으로 처리합니다. 응답의 idempotency_key 에는 실제로 쓴 키가 실립니다.
호출량
일일 한도가 있는 환경(데모)에서는 이 도구를 한 번 부를 때 새 작업이 만들어지면 1회로 셉니다. 다섯 조회를 각각 따로 부르면 5회입니다. 새 작업이 만들어지지 않은 호출은 세지 않습니다(제출 전에 모두 막혔거나 같은 멱등 키로 이전 작업을 돌려받은 경우).