개발자 문서
개발자 문서

멀티계정 오류와 운영

실패, 응답 유실, 키 교체를 구분하고 같은 요청을 복구합니다.

빠른 시작, 개발 규격, 사용자 업무 실행 UI 예시를 먼저 보세요. SDK는 읽기와 쓰기 모두 자동 HTTP 재시도를 하지 않습니다. 아래 기준으로 고객사 호출자가 판단합니다.

정상 플로우 체크리스트

  1. 키·환경 확인

    멀티계정 키의 env_scope와 콘솔 키 관리의 허용 환경이 일치하고 catalog 조회가 성공합니다.

  2. 기관 준비물

    연결 전 기관별 준비물에서 provider별 재료·VID·계좌 입력을 확인합니다.

  3. 고객 연결

    operation_id 저장 → link → account_link_id를 고객·기관·환경과 매핑 → probe로 ACTIVE 확인합니다.

  4. 작업(Job) 실행

    업무 요청 키 저장 → account_link_id 지정 → job_id 저장 → SUCCEEDED 후 결과를 업무 DB에 한 번만 반영합니다. 같은 키에 같은 본문으로 다시 보내면 새 작업을 만들지 않고 이미 접수된 작업을 그대로 돌려줍니다(HTTP 200).

  5. 막히면

    아래 오류 표와 등록·작업 복구 절을 따르고, 새 ID를 만들기 전에 원래 요청의 접수 여부를 확인합니다.

1. 오류별 다음 행동

코드 / 증상다음 행동
INVALID_BASE_URL빠른 시작의 API 주소를 복사하세요. 콘솔 주소·/v1·쿼리를 붙이지 않습니다.
HTTPS_REQUIRED고객용 API는 HTTPS 주소를 사용합니다.
INVALID_ENVIRONMENTenv_scope는 sandbox / real_test / production 중 하나여야 합니다(SDK가 HTTP 호출 전에 확인). 데모는 real_test입니다.
403먼저 콘솔 키 관리에서 이 키의 허용 환경을 확인합니다(데모=real_test). 다음으로 오류 code에 따라 기본정보의 약관 동의 또는 상품 이용 권한을 확인합니다.
401 AUTH_CREDENTIAL_INVALID키 조합과 폐기 상태 확인. secret 재조회 대신 재발급. SDK가 client_id·client_secret 형식을 보고 내는 INVALID_CLIENT_CREDENTIAL은 HTTP 호출 전에 나는 별개 오류입니다
INVALID_OPERATION_IDSDK new_operation_id()로 만든 원래 등록 ID 사용. 업무 요청 키와 혼동 금지
INVALID_IDEMPOTENCY_KEY작업 키는 공백 없는 ASCII 1~128자. 같은 업무는 기존 키 유지
409 IDEMPOTENCY_KEY_CONFLICT같은 요청 키를 내용이 다른 요청에 썼습니다. 먼저 원래 요청의 본문이 실수로 바뀌었는지 확인합니다. 같은 업무의 재전송이면 원래 본문과 키를 그대로 보내고, 별도의 새 업무로 확정한 경우에만 새 키를 만듭니다. 재시도 계약 참고. 등록 요청은 처음 보낸 지 24시간이 지난 operation_id를 다시 보낼 때도 같은 코드입니다(SDK 오류에는 문장이 담기지 않으므로 보낸 시각으로 구분) — 연동 상태를 먼저 확인한 뒤 필요하면 새 operation_id로 다시 요청합니다
409 RESOURCE_CONFLICT (link_id_password, LinkIdPasswordAsync 호출 시)기관 아이디와 비밀번호 연결을 같은 아이디에 다른 비밀번호로 다시 등록하면서 confirm을 켜지 않았을 때입니다. 옛 비밀번호로 되돌릴 수 없고 그 연결로 열어 둔 기관 세션이 모두 끊기므로 서버가 바꾸려는 것이 맞다는 명시 확인을 요구합니다. 0.1.2 SDK의 link_id_password와 LinkIdPasswordAsync는 confirm 인자를 받습니다(기본은 꺼짐) — 고객의 새 비밀번호를 확인했으면 같은 operation_id로 confirm=True(파이썬) 또는 confirm: true(닷넷)를 실어 같은 호출을 다시 합니다. XDataError(파이썬)는 code·http_status·outcome_unknown·request_id·trace_id·retry_after를, XDataException(닷넷)은 Code·Status·OutcomeUnknown·RequestId·TraceId·RetryAfter만 담고 원문 error.reason은 전달하지 않으므로, 이 409는 reason 문자열이 아니라 link_id_password나 LinkIdPasswordAsync를 불렀다는 사실로 구분하세요(원문 HTTP 응답의 error.reason은 개발 규격 8절의 HTTP 방식으로 직접 호출할 때만 보입니다). 인증서 기관은 다른 인증서가 새 연결로 등록되므로 이 오류가 나지 않습니다
HTTP_ERRORSDK가 응답의 code를 공개 오류 코드 목록에서 알아보지 못했을 때 붙이는 대체값입니다. http_status·request_id·trace_id를 보존하고 브라우저에 secret·인증서 원문을 노출하지 않음. 예를 들어 요금제의 연동 고객 수 한도를 넘겨 연결을 더 만들면 409 ACCOUNT_LINK_QUOTA_EXCEEDED인데 SDK에서는 http_status 409인 HTTP_ERROR로 보입니다 — 오류 코드
400 VALIDATION_INVALID_FIELD (해제 사유 거부)unlink_reason은 user_request / admin_action / security_incident 중 하나를 씁니다. 인증서 연결에 그 밖의 문자열(예: e2e_relink)을 보내면 400입니다. 고객사 UI·운영 스크립트 enum을 맞춤
UNSUPPORTED_CREDENTIAL_KIND / CONTRACT / INVALID_CREDENTIAL_INPUT실제 기관 목록과 input_contract 확인. 임의 형식으로 우회하지 않음
TRANSPORT_ERROR / INVALID_RESPONSE / 5xx오류의 outcome_unknown이 true이면 접수 여부가 불명확합니다. 새 ID를 만들지 말고 아래 2단계 등록 복구 또는 3단계 작업 복구를 따릅니다.
429한도 초과(POLICY_QUOTA_EXCEEDED)인지 호출 속도(POLICY_RATE_LIMIT_EXCEEDED)인지 code로 구분하고 업무 상태를 확인한 뒤 재시도를 판단합니다. 새 요청 키를 만들지 않습니다. 이 응답에는 대기 시간이 실리지 않으므로 error.retry_after는 비어 있습니다
로컬 FileExistsError기존 journal 또는 submitted 기록 확인. 파일 삭제로 새 요청을 만들지 않음

VID(random_enc)가 인증서와 짝이 맞지 않을 때

연결은 성공했으나 로그인 작업이 실패한다면, 원인 가운데 하나는 random_enc(VID)가 link에 넣은 인증서 bytes와 짝이 맞지 않는 경우입니다. 서버는 연결할 때 random_enc가 있는지만 확인하고 그 값이 이 인증서의 것인지는 확인하지 않으므로, 짝이 맞지 않아도 연결은 만들어집니다. PKI Agent·고객사 인증 모듈 등 어떤 경로를 쓰든, 화면에서 고른 인증서·VID를 뽑은 인증서·DB에서 읽은 cert bytes가 달라지면 같은 문제가 생길 수 있습니다. 이름·발급자만 보고 자동 매칭한 경우도 포함됩니다. 해결: 동일 cert bytes로 VID를 다시 획득 → 해제 후 새 operation_id로 재연결. 해제한 연결과 같은 인증서로 다시 연결하면 서버가 이전 account_link_id를 그대로 돌려줄 수 있습니다 — 응답으로 온 값을 그대로 저장하고, 새 값이 올 것으로 가정하지 마세요. 기관별 준비물의 VID 경로를 참고하세요.

멱등 재연결(IDEMPOTENT_MATCH)과 stale VID

같은 provider·같은 인증서로 link를 다시 호출하면 XBOSS가 기존 account_link_id를 반환할 수 있습니다. 이 응답은 새 random_enc로 VID를 갱신하지 않습니다. 예전 VID가 남아 로그인 작업만 실패하는 경우, probe 성공만으로 연결을 유지하지 말고 unlink 후 새 operation_id로 재연결하세요. 화면에 「이미 연결됨」이 나와도 로그인 확인 작업이 실패하면 이 절차를 따릅니다.

증상원인 후보다음 행동
link 성공·같은 account_link_id멱등 매칭으로 기존 연결 재사용로그인 작업 성공 여부로 판단. 실패 시 unlink·재연결
로그인만 VID 불일치stale random_enc 또는 cert bytes 불일치동일 cert bytes로 VID 재획득 → unlink → 새 operation_id → link
unlink 거부(진행 중 작업)해당 account_link_id로 작업이 QUEUED 또는 RUNNING작업 완료·실패 확인 후 해제

2. 등록 요청 복구

등록 전 저장한 operation_id를 24시간 복구 범위 내에서 조회합니다. 24시간을 넘긴 기록이 남아 있어도 재실행이 허용된다는 뜻은 아닙니다. COMPLETED는 과거 결과이며 probe로 현재 상태를 확인해야 합니다. UNKNOWN·404는 미실행 증거가 아닙니다. 서버·환경·키 소유자(등록할 때 쓴 키의 소유자)·원래 ID를 대조하고, 계속 불명확하거나 만료됐다면 아래 5단계의 문의 정보로 XBOSS에 접수 여부 확인을 요청하세요. 확인될 때까지 새 연결 요청을 보내지 않습니다. 키·입력 원문은 보내지 않습니다.

powershell
# ZIP을 푼 폴더. 새 창에서도 필요한 값들을 모두 지정합니다.
$apiBase = Read-Host '원래 API origin'
$clientId = Read-Host '등록할 때 쓴 키와 같은 사람이 신청해 받은 현재 유효한 client_id'
$customerId = Read-Host '원래 고객 ID'
$provider = Read-Host '원래 기관 코드 (예: hometax)'
$environment = Read-Host '원래 환경: sandbox / real_test / production'
if ($environment -notin @('sandbox', 'real_test', 'production')) { throw '원래 환경을 확인하세요.' }
$journal = Read-Host '보관한 연결 journal 경로'
$argsForRecovery = @(
  'recover', '--journal', $journal,
  '--base-url', $apiBase, '--env', $environment,
  '--client-id', $clientId,
  '--customer', $customerId, '--provider', $provider
)
./.venv/Scripts/python.exe ./python/examples/recover_connection.py @argsForRecovery

3. 작업 복구는 등록 복구와 다릅니다

job_id가 있으면 상태와 결과를 조회합니다. 응답을 잃어 job_id가 없다면 고객사 구현에서는 같은 조직, 원래 환경에서 원래 Idempotency-Key(request_key)와 동일 요청 본문을 사용합니다. 화면 「진행 상태 확인」은 이 복구가 아니라 저장된 job_id로 GET만 하는 재조회입니다. 사용자 「다시 실행」은 새 request_reference와 새 request_key로 새 작업입니다 — 개발 규격 §7 재조회, 복구, 새 실행. 기존 작업의 보존과 조회 가능 여부가 불명확하면 먼저 XBOSS에 확인합니다. 409이면 본문과 키 충돌을 확인하고 새 키로 자동 우회하지 않습니다. 기간 경과나 인증 문맥 변경 때는 운영 확인이 필요합니다. 빠른 시작의 first_job.py는 재전송 정책을 구현한 고객사 서버가 아니므로 제출 마커가 있으면 POST를 차단하고 운영 확인하도록 합니다. 재시도 계약을 참고하세요.

상태고객사 처리
QUEUED / RUNNING같은 작업 조회. 조회 횟수·간격·전체 대기 한도를 명시
SUCCEEDED결과 조회 후 기존 업무 DB에 한 번만 반영
FAILED / CANCELED / TIMEOUT오류와 업무 영향 확인. 자동 신규 요청 금지
원격 성공 / 로컬 저장 실패원래 응답으로 매핑·결과 저장 복구. 자동 unlink 금지

4. 연결 해제·재연결·키 교체

진행 중 작업을 먼저 확인한 뒤 대상 고객만 해제합니다. 아래 함수는 기존 고객 권한 검사 뒤 호출하는 SDK 연결점입니다. reason은 user_request / admin_action / security_incident 중 하나를 쓰세요(인증서 연결은 그 밖의 문자열을 400으로 거부합니다). 해제 후 조회의 404가 단독으로 소유권·비활성·없음을 구분해 주지는 않습니다. 재연결은 새 사용자 의도로 새 operation_id를 저장한 뒤 같은 등록 메서드를 호출하고 반환 ID와 ACTIVE 상태를 대조하세요.

대표로 지정한 자격증명과 다기관 fan-out 해제

한 인증서로 여러 기관이 연결된 경우, 대표(primary) account_link_id를 직접 unlink하면 거부됩니다(400 VALIDATION_INVALID_FIELD). 같은 인증서에 묶인 비-primary 연결을 한 건 해제하면 서버가 같은 인증서의 나머지 연결까지 함께 비활성화합니다 — 진행 중 작업이 있는 연결은 그대로 남고, 나머지 연결의 개별 처리가 실패해도 해제 응답에는 나타나지 않습니다. 그래서 목록을 미리 만들어 순서대로 해제하지 말고, 한 건 해제한 뒤 같은 고객의 나머지 account_link_id를 하나씩 probe해 남은 연결을 확인하세요(해제된 연결은 404). 멀티계정 키에는 연결 목록 조회가 없습니다. 이미 비활성인 연결을 다시 해제하면 409 RESOURCE_CONFLICT입니다. SDK로 만든 연결은 항상 비-primary입니다. VID 재연결·인증서 교체 시에도 같은 방식을 적용합니다.

대상해제재연결
단일 기관만 연결해당 account_link_id unlink새 operation_id → link
동일 인증서 다기관한 건 unlink → 나머지 account_link_id를 probe로 확인각 기관별 새 operation_id·VID
VID만 갱신 필요위 방식으로 전부 해제됐는지 확인같은 cert bytes·새 VID로 기관별 재연결
python
def unlink_customer(client, account_link_id):
    # reason: "user_request" | "admin_action" | "security_incident"
    return client.unlink_connection(account_link_id, reason="user_request")

def verify_after_key_change(new_client, account_link_id):
    return new_client.probe_connection(account_link_id)

키 교체: 새 키 검증 → 보호 저장 준비 → 처음 읽었던 설정 버전(expected revision)이 현재 버전과 같을 때만 설정 전환 → 새 SDK 인스턴스 생성 → 구 인스턴스 종료. 고객 연결 매핑은 유지하고 새 키로 probe합니다. 고객 연결은 조직이 아니라 키의 소유자에게 묶입니다. 재발급은 조직의 소유자·관리자 누구나 신청할 수 있고, 승인하면 새 키의 소유자는 폐기되는 키의 소유자로 유지되므로 누가 신청해도 기존 account_link_id는 새 키에서 그대로 보입니다. 다만 두 경우에는 보이지 않습니다. 옛 키의 소유자가 이미 조직을 떠났다면 새 키는 재발급을 신청한 사람의 것이 되어 기존 account_link_id가 모두 404로 보이고 등록 복구 조회도 404가 되며, 재발급 뒤 키 소유자가 아닌 다른 관리자가 새로 등록한 연결도 이 키에서는 보이지 않습니다. 404에서 자동 삭제·재등록하지 말고 아래 5단계의 문의 정보로 알려 주세요. 재발급이 승인되면 구 키가 바로 폐기되어 새 키를 전달받기 전까지 중단될 수 있으며 SDK가 유예기간을 만들지 않습니다. 키 설정 삭제·키 폐기·기관 unlink·업무자료 삭제는 별개입니다.

5. 지원에 전달할 정보

환경·SDK 버전·안전한 오류 code·HTTP 상태·request_id·trace_id·발생 시각을 전달하세요. XBOSS 기술지원 문의로 접수하세요. 문의에는 등록 복구인지 작업 복구인지와 마지막 성공 단계를 함께 적습니다. secret·Authorization·인증서·개인키·기관 비밀번호·고객 결과 원문은 전달하지 않습니다. 아래 함수는 예외 객체를 안전한 진단값으로 바꾸는 고객사 연결 예제입니다.

python
from xdata_multi_account import XDataError

def safe_diagnostic(error: XDataError) -> dict:
    return {
        "code": error.code, "status": error.http_status,
        "outcome_unknown": error.outcome_unknown,
        "request_id": error.request_id, "trace_id": error.trace_id,
        "retry_after": error.retry_after,
    }

6. 배포와 적용 확인

예제 ZIP의 OPERATIONS.md에 동시 편집·키 회수·업그레이드 계약이 있습니다. manifest의 해시는 무결성 확인이며 공식 서명이 아닙니다. 매니페스트와 파일을 같은 사이트에서 함께 받았다면 해시가 맞아도 출처 인증은 되지 않습니다. 현재 버전은 0.1.2 개발 검증본(미리보기)이며 정식 배포·서명과 실제 키를 사용한 전체 연동 검증이 완료되지 않았습니다. 실제 서비스 도입 전 아래 항목을 고객사 환경에서 검증하세요.

  1. 첫 고객과 두 번째 고객

    샌드박스에서는 승인된 가짜 테스트 자료, 데모에서는 사용 허가를 받은 고객별 자료로 연결·작업 실행·결과 저장. 매핑과 결과가 섞이지 않아야 합니다.

  2. 종료·응답 유실·동시 요청

    기존 요청 ID와 작업을 복구하고 중복 요청·중복 결과 반영을 막는지 확인합니다.

  3. 권한 회수와 키 교체

    화면과 MCP 모두 현재 고객 권한을 적용하고 구 설정의 늦은 응답이 새 설정을 덮지 않게 합니다.

  4. 해제와 복원

    연결 상태·반환 ID·사용 계정 수를 확인합니다. 샌드박스 mock 통과와 실제 기관 검증은 별도로 기록합니다.