개발자 문서
개발자 문서

멀티계정 개발 규격과 예제

XBOSS 가 제공하는 함수와 고객사가 구현할 부분을 구분합니다.

0.1.2 개발 검증본(미리보기)입니다. 정식 배포·서명 및 실제 키와 테스트 자료를 사용한 전체 연동 검증은 완료되지 않았습니다. 예제는 승인된 테스트 자료로 확인한 뒤 고객사 환경에서 다시 검증하세요.

실행부터 확인하려면 빠른 시작, 실패·재시작 처리는 오류와 운영을 보세요. 아래 예제는 어떤 업종·어떤 고객사 프로그램에도 붙일 수 있는 연결 패턴입니다. 특정 샘플 앱의 화면·내부 API 이름을 따라 할 필요는 없습니다. 함수 안의 권한·DB·customer_id 매핑은 고객사가 구현합니다. SDK가 이를 대신하거나 새 고객관리 DB를 설치하지 않습니다.

1. 공통 키 설정 UI

연결·작업(Job) 코드를 붙이기 전에 회사 멀티계정 키를 안전하게 입력·검증할 공통 UI를 선택합니다. 고객 인증서·ID/PW는 고객사 화면에서 처리합니다. 다음 verify/save는 고객사 백엔드 함수이며 XBOSS 가 제공하는 URL 이름이 아닙니다. 백엔드는 로그인한 관리자의 회사(tenant)와 권한을 확인하고 위조 요청(CSRF)·허용 출처(origin)를 검사해야 합니다. 저장 시 설정 버전(revision)을 비교해 다른 관리자의 최신 변경을 덮어쓰지 않도록 하고 Secret을 보호 저장합니다. UI는 키를 저장하지 않습니다.

웹 키 설정 UI 설치와 호출

웹 UI 파일을 내려받고 고객사 프론트엔드 프로젝트에서 설치하세요. 이 패키지는 브라우저 ES module입니다. React 등 특정 프레임워크 전용이 아니며 고객사 서버 언어와 무관하게 사용할 수 있습니다. 화면을 담을 HTMLElement와 고객사의 verify/save 함수를 아래 연결 함수에 전달합니다.

npm을 사용하는 프로젝트는 프론트엔드 package.json이 있는 폴더에서 PowerShell을 여세요. 다운로드한 tgz 파일을 Shift+우클릭 → 경로로 복사한 뒤 아래 입력란에 붙여 넣습니다. npm을 쓰지 않는 프로젝트라면 tgz 안의 index.js를 고객사 웹서버에 고정 버전으로 올려 같은 함수를 불러옵니다 — 빌드가 필요 없는 ES module입니다.

powershell
$uiPackage = (Read-Host '다운로드한 tgz 전체 경로').Trim('"')
if (!(Test-Path './package.json')) { throw '고객사 프론트엔드 package.json 폴더에서 실행하세요.' }
if (!(Test-Path -LiteralPath $uiPackage -PathType Leaf)) { throw '다운로드한 파일 경로를 확인하세요.' }
npm install $uiPackage
if ($LASTEXITCODE -ne 0) { throw 'UI 패키지 설치 실패. 파일·네트워크·프로젝트 권한을 확인하세요.' }
typescript
import { mountXDataSettings, type SettingsHandler } from '@xdata/integration-settings-ui';

// 고객사 구현 함수를 인수로 받는 연결 함수입니다.
export function attachXDataSettings(
  container: HTMLElement,
  verify: SettingsHandler,
  save: SettingsHandler,
) {
  const widget = mountXDataSettings(container, { verify, save });
  return () => widget.destroy(); // 화면 해제 시 호출
}
UI 계약정확한 값 / 동작
verify(settings, context)settings={env_scope, client_id, client_secret}, context={signal: AbortSignal}
save(settings, context)동일 입력. 저장할 때 권한과 키를 다시 검증하고 설정 revision을 비교해 동시 수정을 막는 일은 고객사 서버가 합니다. UI는 revision을 보내지 않습니다
반환Promise<{ok: boolean}>. 실제 성공일 때만 {ok:true}. 원문 오류·키를 반환하지 않음
onSaved(result)선택 콜백. 저장이 성공했을 때만 result로 env_scope 하나를 전달
destroy()화면/컴포넌트 종료 시 정리. 요청 취소가 서버 저장 취소를 의미하지 않음

패키지는 고정 버전을 고객사에서 직접 호스팅합니다. 아래 예제는 관리자 페이지에 컨테이너 요소를 두고 패키지를 그 이름 그대로 import해 붙이는 모듈입니다. npm으로 설치했으므로 번들러가 패키지 이름을 해석합니다. React 등 화면 전환 시 attachXDataSettings의 반환 함수를 호출하세요. 검증·저장이 실제로 성공했을 때만 콜백에서 ok:true를 반환합니다.

javascript
// 고객사 로그인된 관리자 페이지에 <div id="xdata-settings"></div>를 두고 이 모듈을 불러옵니다.
import { mountXDataSettings } from '@xdata/integration-settings-ui';

// 아래 URL은 예시입니다. 고객사가 정한 경로로 verify/save API를 구현하세요. XBOSS API 주소가 아닙니다.
// 서버는 세션·관리자 권한·허용 출처를 검사하고 저장 시 키를 재검증해야 합니다.
// 응답 계약: 성공 시 {"ok":true}, 실패 시 적절한 HTTP 오류 또는 {"ok":false}.
async function sendSettings(url, settings, { signal }) {
  const response = await fetch(url, {
    method: 'POST',
    credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(settings),
    signal,
  });
  if (!response.ok) return { ok: false };
  const result = await response.json();
  return { ok: result.ok === true };
}
const widget = mountXDataSettings(document.getElementById('xdata-settings'), {
  verify: (settings, context) => sendSettings('/api/xdata/settings/verify', settings, context),
  save: (settings, context) => sendSettings('/api/xdata/settings/save', settings, context),
});
window.addEventListener('pagehide', () => widget.destroy(), { once: true });

이 화면의 연결 확인·저장은 위 고객사 API를 구현한 뒤에만 성공합니다. CSRF 토큰을 사용하는 프로젝트라면 기존 요청 유틸리티로 헤더를 공급하세요. 설정 revision은 고객사 세션·서버 문맥에 결합하거나 기존 API 계약에 포함해야 합니다. UI는 성공을 가정하거나 브라우저 저장소에 키를 남기지 않습니다.

Windows 키 설정 UI 설치와 호출

Windows UI 파일과 .NET SDK 파일을 downloaded-packages에 놓고 .NET 8 Windows Forms 프로젝트에서 두 패키지를 모두 설치합니다. Windows UI 패키지는 SDK 패키지를 함께 설치해 주지 않으므로 아래 예제의 XDataClient를 쓰려면 SDK도 직접 추가해야 합니다. 다른 데스크톱 UI 프레임워크에 그대로 삽입할 수 있다는 뜻은 아닙니다.

powershell
dotnet add package XData.Settings.Windows --version 0.1.0 --source ./downloaded-packages
dotnet add package XData.MultiAccount --version 0.1.2 --source ./downloaded-packages

아래 Windows 예제는 기존 설정 버튼의 이벤트에서 호출할 코드입니다. RequireCurrentAdministratorAsync·apiBase·storePath는 고객사에서 전달해야 하며 단독 실행 파일은 아닙니다. ZIP의 windows-settings/README.md에도 같은 호출 방식이 있습니다. 두 콜백은 SettingsInput과 CancellationToken을 받고 Task<bool>을 반환합니다. 고객사 설정 메뉴에서 대화상자를 열고, 검증·저장 함수를 전달하세요. 사용자가 자기 키를 입력하는 저장 예제는 Windows의 현재 사용자 전용 암호화(DPAPI)를 사용합니다. 다른 Windows 계정이나 PC로 파일만 옮겨 사용할 수 없습니다. 경로와 파일 접근 권한(ACL)은 고객사가 관리합니다. 공용 회사 secret을 앱에 내장하지 않습니다.

csharp
using XData.MultiAccount;
using XData.Settings.Windows;
// 기존 WinForms 설정 버튼 이벤트에서 호출합니다.
// storePath: 현재 사용자만 접근하는 파일의 절대 경로(상대 경로면 ArgumentException)
var store = new ProtectedKeyStore(storePath);
var revision = store.Read()?.Revision ?? 0;
async Task<bool> Verify(SettingsInput input, CancellationToken token) {
    await RequireCurrentAdministratorAsync(token); // 고객사의 관리자 권한 확인
    using var client = new XDataClient(new Uri(apiBase),
        input.Environment, input.ClientId, input.Secret);
    await client.GetProviderCatalogAsync(token);
    return true;
}
using var dialog = new SettingsDialog(Verify, async (input, token) => {
    await Verify(input, token);
    revision = store.Save(input, revision);
    return true;
});
dialog.ShowDialog();

2. 호출 언어 선택

XBOSS API 를 호출할 서버 또는 설치 프로그램의 언어를 선택하세요. 웹 화면이 React인지, 일반 HTML인지는 이 선택과 별개입니다. 예를 들어 React + Python 서버라면 Python을 선택합니다. 브라우저에서 회사 secret으로 XBOSS 를 직접 호출하지 않습니다.

API를 호출하는 언어연동 방법바로 시작
Python 3.11 이상Python SDK설치와 첫 호출
C# / .NET 8.NET SDK — 서버·설치 프로그램 공통설치와 첫 호출
Java / Node.js / 기타 언어현재 제공 SDK 없음. 서버에서 HTTP 규격으로 구현HTTP 인증과 호출 순서

키 설정 UI 선택 (1단계 참고)

위 1단계에서 고른 공통 UI와 별도로, client_id·client_secret을 입력할 화면을 사용할지 다시 확인하세요. UI는 API 호출 SDK를 대체하지 않습니다. 아래에서 해당하는 하나만 확인하면 됩니다.

키를 설정할 화면선택할 안내
웹 관리자 화면웹 UI 설치·호출·콜백
Windows .NET 8 WinFormsWindows UI 설치·호출
기존 키 설정 화면·안전한 설정 저장소 사용추가 UI 패키지 불필요. 선택한 SDK/HTTP 규격으로 검증·저장 연결

현재는 고정 버전 파일을 내려받아 설치하는 개발 배포본입니다. 공개 pip/NuGet/npm 저장소에서 패키지 이름만으로 설치하는 배포를 제공한다고 안내하지 않습니다. 웹 UI는 고객사 백엔드에 연결하며 고객 인증서·ID/PW 입력 화면은 포함하지 않습니다.

3. 식별자와 저장 책임

이름생성자 / 쓰임고객사가 유지할 것
customer_id고객사 / 기존 단위 식별자tenant와 접근 권한. 거래처·회원·사업장 등 고객사 업무 모델의 ID. XBOSS 회원 ID가 아닙니다.
client_id / client_secretXBOSS 승인 / API 인증환경·설정 revision과 안전한 키 저장소
operation_idSDK new_operation_id() / 고객 연결 요청전송 전에 고객·기관·환경과 저장. 같은 등록 복구에 재사용
account_link_idXBOSS 연결 응답 / 기관 연결 식별자tenant + customer + environment + provider 매핑
request_key예제의 변수명 / SDK idempotency_key에 전달새 업무에 새 값, 같은 업무 재전송에는 같은 값과 본문. 인증키가 아닙니다.
request_reference고객사 / 화면·로그용 업무 이름customer_id와 함께 저장하는 사람이 읽는 참조. 새 실행에는 새 값, 같은 reference로는 Job을 두 번 만들지 않음. request_key와 별개입니다.
job_idXBOSS 작업 응답 / 상태·결과 조회고객·업무 요청에 귀속시켜 저장

4. Python SDK 생성과 키 확인

다음 코드는 SDK 설치 후 독립 실행할 수 있습니다. 저장할 키와 환경을 그대로 사용해 기관 목록 조회가 성공하는지 확인합니다. 이는 해당 API 접근 확인이며, 모든 상품 권한이나 기관 로그인 검증은 아닙니다. 설정 저장 시 고객사 서버는 관리자 권한을 다시 확인하고 동일 키·환경을 재검증해야 합니다.

처음 설치한다면 빠른 시작의 2단계에서 다운로드·압축 해제·PowerShell 열기·설치 확인을 먼저 완료하세요. 이미 빠른 시작 3단계에서 catalog를 확인했다면 이 절을 건너뛸 수 있습니다. 이미 실행 중인 프로그램에 설치할 경우 그 서버를 실행하는 Python을 사용합니다. 아래에서는 빠른 시작에서 만든 .venv를 사용합니다. 코드 편집기에서 새 파일을 만들고 아래 Python 코드를 붙여 넣어 실습 폴더에 verify_key.py로 저장하세요. 메모장을 사용한다면 파일 형식은 모든 파일, 이름은 verify_key.py, 인코딩은 UTF-8로 선택해 .py.txt가 되지 않게 합니다.

powershell
./.venv/Scripts/python.exe -c "import sys; from xdata_multi_account import XDataClient; print(sys.executable); print('SDK 설치 확인 완료')"
python
from getpass import getpass
from xdata_multi_account import XDataClient, XDataError

try:
    with XDataClient(
        base_url="https://api.xdata.kr",
        env_scope=input("환경 (샌드박스 sandbox / 데모 real_test / 정식 production): ").strip(),
        client_id=input("Client ID: ").strip(),
        client_secret=getpass("Client secret: "),
    ) as client:
        providers = client.get_provider_catalog()
        print("catalog_received:", len(providers))
except XDataError as error:
    print(error.code, error.http_status, error.request_id, error.trace_id)
    raise SystemExit(2)
powershell
./.venv/Scripts/python.exe ./verify_key.py

catalog_received와 항목 수가 나오면 첫 호출 확인이 끝납니다. Python 연결 함수는 아래 5단계, 완전한 실행 파일은 빠른 시작을 사용하세요.

.NET SDK 설치와 호출

.NET SDK와 예제 ZIP을 받으세요. 파일 탐색기에서 ZIP을 모두 압축 풀기한 뒤 dotnet-example 폴더가 보이는 위치를 여세요. 그 위치에 downloaded-packages 폴더를 만들고 nupkg를 넣으세요. 탐색기 주소 표시줄에 powershell을 입력하면 해당 위치에서 터미널이 열립니다. dotnet --list-sdks에 8.로 시작하는 줄(.NET 8 SDK)이 있는지 확인한 뒤 다음을 실행합니다.

powershell
dotnet restore ./dotnet-example --source ./downloaded-packages
dotnet run --project ./dotnet-example --no-restore -- help

help가 나오면 설치 확인이 끝납니다. 아래 명령은 현재 PowerShell 프로세스에만 입력값을 설정하고 실행 후 Secret을 제거합니다. 데모 키는 real_test를 입력하세요. 기존 프로젝트에서는 서버 실행 계정이 접근하는 비밀 저장소를 사용하며 소스·공용 배포 파일에 Secret을 넣지 않습니다.

powershell
$env:XDATA_API_BASE = 'https://api.xdata.kr'
$env:XDATA_ENV = Read-Host '환경: 샌드박스 sandbox / 데모 real_test / 정식 production'
$env:XDATA_CLIENT_ID = Read-Host 'Client ID'
$secureSecret = Read-Host 'Client Secret' -AsSecureString
try {
  $env:XDATA_CLIENT_SECRET = [System.Net.NetworkCredential]::new('', $secureSecret).Password
  dotnet run --project ./dotnet-example --no-restore -- catalog
} finally {
  Remove-Item Env:XDATA_CLIENT_SECRET
  $secureSecret.Dispose()
}

기관 목록 JSON이 나오면 첫 호출 확인이 끝납니다. 같은 Program.cs에 operation-id → link-certificate 또는 link-id-password → probe 실행 코드가 있습니다. 기존 프로젝트에는 아래 함수처럼 SDK를 호출하세요. 입력값은 고객사의 안전한 설정 저장소에서 전달합니다.

csharp
using XData.MultiAccount;

static async Task VerifyKeyAsync(
    string apiOrigin, string environment,
    string clientId, string clientSecret)
{
    using var client = new XDataClient(
        new Uri(apiOrigin), environment, clientId, clientSecret);
    await client.GetProviderCatalogAsync();
}

5. 고객 자격증명 연결 함수

아래는 독립 실행 파일이 아니라 고객사 백엔드에서 호출할 함수입니다. 이미 보관한 인증서·ID/PW를 권한 확인 후 읽어 SDK에 넘깁니다. 파일 재업로드 UI는 필수가 아닙니다. .NET은 ZIP의 Program.cs 예제를 참고하세요. client는 위에서 생성한 인스턴스입니다. provider_entry는 get_provider_catalog()에서 고른 기관 항목입니다. 이 목록은 환경과 관계없이 같으며, 목록에 없는 기관은 연결할 수 없습니다. operation_id는 XDataClient.new_operation_id()로 만들고 전송 전 customer_id·기관·환경과 함께 저장하세요. 성공 응답의 account_link_id를 해당 customer_id에 매핑합니다.

연결 전 기관별 준비물에서 재료·VID·계좌 입력을 확인하세요. random_enc가 필수인 기관은 준비물의 VID 경로 중 하나로 연결 직전에 같은 인증서에 대한 VID를 확보합니다.

5-1. 저장된 인증서로 연결 (공통 경로)

대부분의 고객사 프로그램은 등록 시점에 인증서를 이미 저장합니다. 연결 시에는 저장소에서 cert·key bytes와 비밀번호를 읽고, random_enc가 필요하면 그 bytes에 대해 고객사 인증 PC·모듈에서 VID를 획득한 뒤 link_certificate에 함께 전달합니다. 아래 connect_certificate는 SDK의 정식 연결 함수 link_certificate를 감싼 고객사 함수 예시입니다. Python SDK가 내보내는 이름은 XDataClient·XDataError·encode_login_secret·LocalPkiAgentClient·PkiCertificateMaterial 다섯이며(.NET은 XDataClient·XDataException·CredentialCodec·LocalPkiAgentClient·PkiCertificateMaterial), 인증서 연결은 XDataClient의 link_certificate입니다. random_enc는 연결 직전에만 계산하고 로그·HTTP 응답에 남기지 마세요.

python
# 고객사 서버의 연결 함수. 인증서 평문을 HTTP 응답이나 로그로 반환하지 않습니다.
def connect_certificate(client, provider, operation_id,
                        certificate_bytes, private_key_bytes, password, random_enc=None):
    return client.link_certificate(
        provider=provider, operation_id=operation_id,
        certificate=certificate_bytes, private_key=private_key_bytes,
        password=password, random_enc=random_enc,
    )

from xdata_multi_account import encode_login_secret

def connect_login(client, provider_entry, operation_id, login_id, values):
    # password-text-v1: values는 비밀번호 문자열
    # canonical-json-v1: values는 secret_schema에 맞는 dict
    secret = encode_login_secret(provider_entry, values)
    return client.link_id_password(
        provider=provider_entry["provider"], operation_id=operation_id,
        login_id=login_id, login_password=secret,
    )

VID 획득 구현은 고객사 책임입니다. Windows 인증서 PC에서는 MagicLine 등 기관 모듈을 link에 넣을 cert bytes에 대해 호출하고, 그 외에는 고객사 NPKI·보안모듈을 사용합니다. 서버는 VID가 있는지만 확인하고 그 VID가 이 인증서의 것인지는 확인하지 않으므로, 저장 cert 경로(A)에서는 PKI Agent가 스캔해 고른 인증서에서 얻은 VID를 다른 cert bytes에 재사용하지 마세요. SDK는 random_enc 문자열을 받아 암호화해 전송만 합니다. link_certificate_from_pki_agent는 그 과정을 Agent가 대신하는 선택적 Windows 편의 함수이며, 저장소 연동의 대체가 아닙니다.

5-2. Windows PKI Agent로 스캔·연결 (선택)

인증서를 아직 저장하지 않았거나, Windows PC에서 NPKI를 스캔해 고르는 UI를 쓸 때만 이 경로를 추가합니다. PKI Agent 1.2.3 이상이 실행 중이어야 합니다. sources·candidates를 화면에 보여 사용자가 하나를 고르게 하고, candidate_id와 비밀번호를 자동 연결 함수에 넘깁니다. 후보 항목에서 SDK가 검사하는 값은 candidate_id와 candidate_type(npki_pair · pkcs12) 둘이며, 화면에 함께 보여 줄 항목은 Agent 응답을 직접 확인하세요. 첫 항목 자동 선택·실패 시 다른 인증서로 우회·이름만 맞추기는 금지입니다.

python
from xdata_multi_account import LocalPkiAgentClient

def list_windows_certificates():
    # 반환된 후보를 고객사 화면에 보여 주고 사용자가 하나를 고르게 합니다.
    with LocalPkiAgentClient() as agent:
        return [
            {"source": source, "candidates": agent.scan_source(source["source_id"])}
            for source in agent.list_sources()
        ]

def connect_selected_windows_certificate(
    client, provider, operation_id, candidate_id, certificate_password
):
    # operation_id는 호출 전에 고객·기관·환경과 함께 저장해야 합니다.
    with LocalPkiAgentClient() as agent:
        return client.link_certificate_from_pki_agent(
            provider=provider,
            pki_agent=agent,
            candidate_id=candidate_id,
            password=certificate_password,
            operation_id=operation_id,
        )
csharp
using XData.MultiAccount;

// 기존 화면에서 sources와 candidates를 보여주고 사용자가 하나를 선택합니다.
using var agent = new LocalPkiAgentClient();
var sources = await agent.ListSourcesAsync(cancellationToken);
var candidates = await agent.ScanSourceAsync(selectedSourceId, cancellationToken);

// operationId는 호출 전에 고객·기관·환경과 함께 저장해야 합니다.
var result = await client.LinkCertificateFromPkiAgentAsync(
    provider: selectedProvider,
    pkiAgent: agent,
    password: certificatePassword,
    operationId: operationId,
    candidateId: selectedCandidateId,
    cancellation: cancellationToken);

LocalPkiAgentClient는 같은 PC의 루프백 주소(HTTP)만 허용하며 다른 호스트로는 연결하지 않습니다. Agent·MagicLine에서 VID를 얻지 못하면 연결을 중단하고 오류를 표시합니다. 인증서 비밀번호·개인키·VID 원문을 브라우저, 로그, MCP 인수로 전달하지 마세요.

ID/PW 연결은 기관마다 입력 규격이 다릅니다. input_contract.format은 비밀번호 한 개를 받는지 여러 필드를 받는지, secret_schema.properties/required는 필드 이름과 필수 여부를 뜻합니다. 복합 입력의 필드를 기관명으로 추측하지 마세요. encode_login_secret은 고정값(const)을 채우고 형식에 맞지 않는 입력을 거부합니다. password-text-v1 기관은 connect_login(client, provider_entry, operation_id, login_id, password)로 호출합니다. 여러 필드가 필요한 기관은 해당 기관의 실제 스키마에 맞는 dict를 전달하세요. 목록에 없는 인증 방식은 사용할 수 없습니다. SDK는 is_primary=false로 등록하며 공개키 조회·암호화를 처리합니다. 인증 암호화와 연결 API도 참고하세요.

반환 / 상태사용 방법
연결 응답 account_link_id, provider고객사 매핑 저장. 정확한 전체 응답은 HTTP 계약 참조
probe_connection(id)의 status=ACTIVE현재 활성 여부만 확인. 기관 로그인 성공 보장은 아님
Job 응답 job_id, status, provider, action업무 요청과 저장. 접수와 완료를 구분
get_job_result(id)의 dict상품별 결과 계약에 맞게 변환·저장. 공통 고객관리 테이블을 반환하지 않음

6. 내 업무에 Job 붙이기

실행 예제 ZIP의 first_job.py는 실행형, customer_workflow.py는 고객사 함수 연결형입니다. 후자의 CustomerWorkflow는 고객사의 기존 함수들을 연결하는 예제 클래스입니다. 먼저 고객 권한 확인 → 연결 조회 → 중복 방지 키 저장 → Job 제출·ID 저장 → 결과 조회·업무 반영 순서로 연결하세요. 아래 표는 자동 제공되는 DB 기능이 아니라 고객사가 작성할 함수의 입력·반환 계약입니다. request_key는 UUID 같은 고유값을 생성해 저장하되, SDK 허용 형식은 공백 없는 ASCII 1~128자입니다.

콜백입력 → 반환고객사 구현 조건
authorize_customer(actor, customer) → None현재 tenant·담당 권한 확인, 거부 시 예외
load_connection(customer) → CustomerConnectionaccount_link_id·provider·action·params·encrypted_fields. 고객의 현재 업무 입력을 구성합니다. 환경·tenant는 서버 문맥에 묶고 모호한 매핑 거부
reserve_request(customer, request_reference) → str업무 의도·설정 revision에 요청 키 원자적 저장. 재시도에는 같은 값
save_job / load_job(customer, reference, job_id) → None / (customer, reference) → str현재 고객과 업무에 귀속되는 ID만 저장·조회
save_business_result(customer, reference, result) → None업무 포맷으로 변환, 동일 결과 중복 반영 방지

업무 이름(request_reference)과 멱등 키(request_key)

CustomerWorkflow 예제는 reserve_request가 두 값을 함께 다룹니다. request_reference는 고객사 DB·화면에 보이는 업무 이름(예: 홈택스-로그인-확인-2026-09-18-193854820)이고, reserve_request가 반환하는 request_key는 XBOSS 의 Idempotency-Key로 쓰는 내부 키입니다. reserve_request는 같은 customer_id + request_reference에는 같은 request_key를 돌려주므로(재시도에 같은 키를 쓰기 위해서입니다), 같은 request_reference를 다시 쓰면 새 작업이 만들어지지 않습니다. XBOSS 는 같은 Idempotency-Key에 같은 본문이면 HTTP 200으로 이미 접수된 작업을 그대로 돌려주고(새 작업은 202), 같은 키에 본문이 다를 때만 409 IDEMPOTENCY_KEY_CONFLICT입니다. 다시 보낸 요청도 요금제와 한도 검사를 먼저 거치므로 데모의 일일 한도를 다 쓴 뒤에는 200이 아니라 429가 옵니다 — 그때는 저장한 job_id로 조회하세요. 그러므로 사용자가 「다시 실행」을 누를 때는 새 request_reference를 만들어 새 request_key를 받고, 네트워크 재시도·서버 재시작으로 같은 업무를 이어갈 때만 같은 reference와 저장된 request_key를 재사용하세요. 같은 reference의 재실행을 고객사 서버가 따로 거부하도록 만들 수도 있으며, 그 거부의 상태 코드와 오류 이름은 고객사가 정합니다. 규칙은 멱등 키를 보세요.

상황request_referencerequest_key
화면에서 첫 실행클릭 시마다 새 이름(날짜+시각 등). 날짜만 넣으면 같은 날 두 번째 실행이 같은 업무(같은 request_key)가 됩니다reserve_request가 새로 생성·저장
HTTP/전송 재시도(같은 업무)변경하지 않음저장된 값 재사용
결과 반영 완료 후 또 검증새 reference. 이전 Job 조회는 job_id로새 request_key

업무 이름을 기관명-로그인-확인-YYYY-MM-DD처럼 날짜만 넣으면 같은 날 두 번째 실행이 같은 업무(같은 request_key) 가 되어 새 작업이 만들어지지 않습니다. 화면 기본값은 placeholder로만 쓰고, 실제 제출 이름에는 시·분·초(또는 ms)를 포함하세요.

로그인 확인(session.login) 워크플로

데모·정식에서 홈택스 등은 catalog의 login_contract.action(예: hometax.session.login)으로 연결 검증 작업을 실행합니다. 샌드박스는 기관을 부르지 않고 모의 응답을 돌려줍니다(계정을 연결해 쓰는 기관 기준) — 이는 환경의 성질이고 특정 액션의 성질이 아닙니다. 실제 기관 로그인은 데모·정식에서만 일어납니다. 고객사 서버는 권한 확인 → 연결 조회 → request_reference·request_key 저장 → Job 제출 → 상태 폴링 → SUCCEEDED 후 get_job_result → 업무 DB 반영 순서를 한 서비스 함수로 묶는 것이 일반적입니다. probe_connection 성공만으로 로그인 성공을 안내하지 마세요.

단계고객사 동작비고
1authorize_customer / tenant·담당 권한customer_id 스코프
2load_connection → account_link_id·actionlogin_contract.action 사용
3reserve_request(reference) → request_key 저장reference는 화면·로그용 새 이름
4submit_job + save_jobjob_id 저장
5get_job 폴링(횟수·간격 한도)FAILED면 오류 봉투의 provider_error_ref 보존
6get_job_result + save_business_result동일 Job 중복 반영 방지

hometax.session.login 예제: 로그인 확인 결과 반영

다음은 홈택스 로그인 확인 Job이 SUCCEEDED했을 때 고객사 업무에 남기는 패턴 예제입니다. 상세 세무 데이터가 아니라 「이 customer_id·이 account_link_id로 로그인 검증에 성공했다」는 운영 기록입니다. nhis_edi.business.basic_info 예제와 같이 result 원문을 그대로 노출하지 않고 고객사 스키마로 변환하세요.

python
def session_login_summary(response, *, provider: str, action: str) -> dict:
    # response 는 get_job_result(job_id) 의 반환값입니다. GET /v1/jobs/{job_id} 의 상태 응답에는 result 가 없습니다.
    if response.get("status") != "SUCCEEDED":
        raise ValueError("성공한 Job 결과가 아닙니다.")
    # 상품별 result.data 형식은 API 계약을 따릅니다. 홈택스 로그인 확인은 success · message · user_type ·
    # account_link_id · session_registered · cookies_count 를 돌려주며 verified 칸은 없습니다.
    data = (response.get("result") or {}).get("data") or {}
    return {
        "kind": "session_login",
        "provider": provider,
        "action": action,
        "login_verified": data.get("success") is True,
        "session_registered": data.get("session_registered") is True,
    }

# 고객사 코드: summary = session_login_summary(client.get_job_result(job_id), provider="hometax", action="hometax.session.login")
# save_business_result(customer_id, request_reference, summary)

연결 해제(호스트 API 패턴)

unlink는 SDK를 고객사 백엔드에서만 호출합니다. 화면 버튼은 고객사 REST(예: POST /api/customers/{customer_id}/xdata/connections/{connection_id}/unlink)로 연결하고, 서버가 권한 확인 후 client.unlink_connection(account_link_id, reason="user_request")를 호출합니다. reason은 user_request / admin_action / security_incident 중 하나입니다. 해제 성공 시 로컬 connection 상태를 INACTIVE로 기록하세요. 같은 인증서로 여러 기관이 연결돼 있으면 한 건을 해제할 때 같은 인증서를 쓰는 나머지 연결도 함께 비활성화되지만, 진행 중인 작업이 있는 연결은 남고 나머지 연결의 개별 처리가 실패해도 해제 응답에는 나타나지 않습니다. 그러므로 해제 순서를 설계하지 말고 한 건을 해제한 뒤 같은 고객의 나머지 account_link_id를 하나씩 probe_connection으로 확인해(해제된 연결은 404) 로컬 상태를 맞추세요. 멀티계정 키에는 연결 목록 조회가 없습니다. 실패·재시작 처리는 오류와 운영을 보세요.

다른 상품으로 변경할 때는 문서 사이트의 상품 API에서 provider·action·params·필수 권한·응답 규격을 먼저 확인하세요. nhis_edi.session.login은 연결을 검증하는 로그인 확인 액션이지 범용 자료 수집 API가 아닙니다(샌드박스에서는 다른 액션과 마찬가지로 모의 응답입니다). 멀티계정에서는 일반 Job의 primary 자동선택 안내를 적용하지 말고 account_link_id를 항상 지정하세요.

nhis_edi 예제: Job 결과를 고객사 업무 데이터에 반영

다음은 nhis_edi.business.basic_info(건강보험 사업장 기본정보)의 result.data.business에서 회사명과 사업자번호를 꺼내는 패턴 예제입니다. 세무·대리 업무가 아닌 회사도 같은 방식으로 상품별 result를 자기 스키마에 매핑합니다. 홈택스 세금계산서 응답에는 적용하지 않습니다. SUCCEEDED 이후 get_job_result(job_id) 응답을 전달하세요. 아래 함수는 네트워크·DB를 하지 않습니다. 반환값을 고객사 저장 함수에 넘기고 동일 Job 결과를 중복 반영하지 마세요. 아래 예제는 데모·정식 응답을 전제합니다 — 샌드박스의 모의 응답에는 result.data.business가 없고 mock·action·params_echo만 들어 있어 이 함수가 실패합니다.

python
def business_fields(response, expected_business_number):
    if response.get('status') != 'SUCCEEDED':
        raise ValueError('성공한 Job 결과가 아닙니다.')
    result = response['result']
    if 'download_url' in result:
        raise ValueError('파일형 결과입니다. 결과 다운로드 계약으로 먼저 받아야 합니다.')
    business = result['data']['business']
    name = business['firm_name']
    number = business['business_registration_no']
    if not isinstance(name, str) or not name.strip() or not isinstance(number, str):
        raise ValueError('사업장 응답 형식을 확인하세요.')
    if number != expected_business_number:
        raise ValueError('이 customer_id에 기대한 사업자번호와 응답이 다릅니다.')
    return {'company_name': name, 'business_number': number}

# 고객사 코드에서: fields = business_fields(response, 해당_customer의_사업자번호)
# 기존 저장 함수에 customer_id, job_id, fields를 전달합니다.

7. 사용자가 업무를 실행하는 방법 (UI 예시)

XBOSS 는 Job API까지 제공합니다. 사용자가 보는 화면과 버튼은 고객사 프로그램입니다. 아래 A, B, C는 어떤 회사든 참고할 수 있는 권장 패턴 예시입니다. 메뉴 이름, 버튼 문구, DB 테이블 구조는 회사마다 다를 수 있습니다. 공통 규칙은 브라우저가 XBOSS 를 직접 호출하지 않고, 로그인한 담당자 세션으로 고객사 백엔드만 호출한 뒤, 서버가 §6 CustomerWorkflow(권한 → 연결 조회 → 업무 키 저장 → Job → 상태 확인 → 결과 반영)를 실행한다는 것입니다.

호출 구간 구분 (반드시 분리)

구간누가 호출예시비고
① 화면사용자 브라우저와 앱POST /api/customers/{id}/xdata/jobs고객사가 정한 URL. action, secret, 키 없음
② 고객사 서버고객사 백엔드CustomerWorkflow + SDK권한, template_id→action 매핑, DB 저장
③ XBOSS고객사 서버만POST /v1/jobs, GET /v1/jobs/{job_id}공식 HTTP 계약. 브라우저 금지

화면에는 업무 종류(사람이 읽는 이름) 와 업무 이름(실행 기록용) 만 보여 주세요. action, account_link_id, Idempotency-Key, request_key는 서버 내부입니다. 업무 이름은 §6의 request_reference(고객 화면용). 서버가 만드는 request_key는 멱등 재전송용이며 사용자에게 노출하지 않습니다. 업무 이름을 비우면 서버가 표시용 이름을 시각과 ms까지 포함해 자동 생성하세요. 자동 생성 이름과 request_key를 같은 값으로 쓰지 마세요.

공통 흐름 (패턴 A, B, C 동일)

  1. 거래처와 연결 선택

    담당자가 접근 가능한 customer_id와 ACTIVE connection을 고객사 DB에서 고릅니다. 화면의 「기관 연동」은 connection_id 등 고객사 식별자를 쓰고, XBOSS 의 account_link_id는 서버만 봅니다.

  2. 업무와 기간 입력

    사용자는 업무 종류(템플릿)와 기간 등을 고릅니다. 서버가 template_id를 provider, action, params로 바꿉니다. 필수 params와 권한은 해당 상품 API 계약을 따릅니다(예: 아래 홈택스 세금계산서 목록).

  3. 실행 요청

    고객사 API가 request_reference를 받거나 생성하고, reserve_request로 request_key를 저장한 뒤 submit_job합니다. 응답으로 job_id를 고객사 DB에 저장합니다.

  4. 진행 표시

    GET /v1/jobs/{job_id}의 status, progress, progress_message, retry_after_seconds로 화면을 갱신합니다. progress는 기관 실제 처리율이 아니라 상태와 경과 시간 기반 추정치입니다 — 작업 처리 흐름.

  5. 결과 반영

    SUCCEEDED 후 get_job_result로 받아 고객사 스키마로 변환해 저장합니다. 화면에는 요약과 건수만. 로그인 확인(session.login)과 자료 수집은 별도 업무로 기록합니다.

패턴 A — 거래처 상세 「기관 연동」에서 업무 실행

연결과 로그인 확인이 끝난 거래처 화면에 업무 실행 블록을 둡니다. 사용자는 action 이름을 알 필요가 없고, 화면에는 기관, 업무 종류, 기간, 업무 이름만 보입니다. 로그인 확인과 자료 수집은 같은 블록에 두어도 되고, 로그인 확인만 먼저 두는 것도 됩니다.

화면 예시 (거래처 > 기관 연동)

text
┌─ 기관 연동 – 엑스보스 (427-87-03367) ──────────────────────┐
│ 체크: 키 저장 ✓, 자격증명 ✓, 연결 ✓, 로그인 확인 ○         │
│                                                          │
│ [ 홈택스 로그인 확인까지 실행 ]    ← 연결 검증 (session.login) │
│                                                          │
│ ── 업무 실행 ───────────────────────────────────────────  │
│ 업무 종류  [ 세금계산서 매출 조회 ▼ ]                       │
│ 조회 기간  [ 2026-01-01 ] ~ [ 2026-01-31 ]               │
│ 업무 이름  [ 홈택스-매출-2026-01-엑스보스-0921134502 ]       │
│            (비우면 실행 시각으로 자동 생성)                 │
│ [ 실행 ]   [ 실행 내역 보기 ]                                │
│                                                          │
│ 최근 실행                                                 │
│ • 세금계산서 매출 조회, 완료, 14건 반영                     │
│ • 홈택스 로그인 확인,   완료, 연결 검증됨                   │
└──────────────────────────────────────────────────────────┘
화면 입력① 브라우저 → 고객사 API② 고객사 서버 → XBOSS
업무 종류template_id (예: hometax_sales_invoice)submit_job의 provider, action
조회 기간params.from_date, to_dateJob params (동일 키. 상품별 별칭은 API 계약 확인)
업무 이름request_reference (선택, 비우면 서버 생성)reserve_request + 고객사 DB와 실행 내역 표시
거래처URL 또는 body의 customer_idauthorize_customer
기관 연결connection_idload_connection → account_link_id

① 브라우저 → 고객사 API (예시)

text
POST /api/customers/c476ce7e-baa5-46e2-879c-6543b6174c98/xdata/jobs
Content-Type: application/json
Cookie: session=…

{
  "connection_id": "bddd13da-0000-4000-8000-000000000001",
  "template_id": "hometax_sales_invoice",
  "params": {
    "from_date": "2026-01-01",
    "to_date": "2026-01-31"
  },
  "request_reference": "홈택스-매출-2026-01-엑스보스-0921134502"
}

② 고객사 서버 → XBOSS (서버만. 템플릿 해석 후)

text
POST /v1/jobs
Authorization: Basic …
X-Env-Scope: real_test
Idempotency-Key: {reserve_request가 저장한 request_key}

{
  "provider": "hometax",
  "action": "hometax.etax.invoice.search_list",
  "account_link_id": "{연결 등록 응답의 account_link_id(UUID)}",
  "params": {
    "from_date": "2026-01-01",
    "to_date": "2026-01-31",
    "prh_sls_cl_cd": "01",
    "page_num": 1,
    "page_size": 10
  }
}

고객사 업무 템플릿 예시 (서버 DB와 설정)

화면 이름template_idaction서버가 넣는 params
홈택스 로그인 확인hometax_session_loginhometax.session.login(필수 없음. 선택 값은 상품 API 계약 확인)
세금계산서 매출 조회hometax_sales_invoicehometax.etax.invoice.search_listfrom_date, to_date, prh_sls_cl_cd: "01"(기본), page_num, page_size
세금계산서 매입 조회hometax_purchase_invoicehometax.etax.invoice.search_list동일 action, prh_sls_cl_cd: "02"
건강보험 사업장 기본정보nhis_edi_basic_infonhis_edi.business.basic_info(없음)

매출, 매입 세금계산서 목록은 같은 action hometax.etax.invoice.search_list이고, prh_sls_cl_cd로 구분합니다(01=매출, 02=매입, 미지정 시 01). 조회 기간은 from_date, to_date가 권장 키이고 날짜는 YYYY-MM-DD 또는 YYYYMMDD 형식으로 보냅니다. 시작일이 종료일보다 앞인지, 기간이 너무 길지 않은지 같은 범위 검증은 고객사 서버에서 하세요. page_num, page_size로 페이지를 나눕니다. 한 화면 실행이 한 페이지만 가져오면 나머지는 고객사 서버가 추가 Job을 제출하거나, 한 워크플로 안에서 페이지를 순회하는 정책을 정하세요. 상세 필드와 결과 형식은 홈택스 세금계산서 목록을 따르고, 건수와 반영 조건은 결과 스키마를 읽은 뒤 고객사 스키마로 변환하세요.

서버 처리 순서는 §6과 같습니다. 화면 응답에는 job_id, status, 요약만 돌려주고 XBOSS 의 result 원문, secret, request_key는 내리지 마세요.

패턴 B — 업무 현황(티켓)에서 「선택 항목 가져오기」

사무소 제품은 업무 카드(부가세 자료 수집, 4대보험 정리 등)에 체크리스트를 두고, 담당자가 항목을 고른 뒤 한 번에 기관에서 가져오게 하는 방식이 자연스럽습니다. 사용자에게 hometax.etax.invoice.search_list 같은 action 문자열을 노출하지 않습니다. 고객사 DB에 업무 템플릿(template_id → provider, action, 기본 params)을 두고, 화면은 사람이 읽는 이름만 보여 줍니다.

화면 예시 (업무 현황 > 카드)

text
┌─ 업무: 1월 부가세 자료 수집 ─────────────────────────────┐
│ 거래처: 엑스보스 (427-87-03367) – 담당: 김OO – [ 진행 중 ]   │
│                                                          │
│ 필요 자료                                                 │
│ ☑ 홈택스 매출 세금계산서 (2026-01)                        │
│ ☐ 홈택스 매입 세금계산서 (2026-01)                        │
│ ☐ 카드매출 요약                                           │
│                                                          │
│ [ 선택 항목 기관에서 가져오기 ]                             │
│                                                          │
│ 진행 로그                                                 │
│ 09:12 홈택스 매출 조회, 실행 중, job_01M2T…                │
│ 09:05 홈택스 로그인 확인, 완료                            │
└──────────────────────────────────────────────────────────┘

브라우저 → 고객사 API (예시)

text
POST /api/tasks/task_01K2ABCDEF/xdata/run-selected
Content-Type: application/json

{
  "customer_id": "c476ce7e-baa5-46e2-879c-6543b6174c98",
  "items": [
    {
      "template_id": "hometax_sales_invoice",
      "params": { "from_date": "2026-01-01", "to_date": "2026-01-31" },
      "request_reference": "업무-부가세-홈택스매출-2026-01-엑스보스-0921150001"
    }
  ]
}
체크리스트 표시 이름template_idactionparams (서버 병합)
홈택스 매출 세금계산서hometax_sales_invoicehometax.etax.invoice.search_listfrom_date, to_date, prh_sls_cl_cd: "01"
홈택스 매입 세금계산서hometax_purchase_invoicehometax.etax.invoice.search_listfrom_date, to_date, prh_sls_cl_cd: "02"
홈택스 로그인 확인hometax_session_loginhometax.session.login(필수 없음. 선택 값은 상품 API 계약 확인)

서버는 items를 순회하며 template_id로 connection, action, params를 해석하고, 항목마다 Job 1건을 제출합니다(또는 회사 정책에 따라 순차 큐). 각 항목은 서로 다른 request_reference와 새 request_key를 씁니다. 카드 기간(예: 2026-01)은 서버가 from_date, to_date로 바꿉니다. 진행 로그는 고객사 DB의 job_id, status, 요약으로 패턴 C와 동일하게 보여 줍니다.

패턴 C — 실행 중, 완료, 실행 내역 (비동기 Job UX)

Job은 제출 직후 job_id와 접수 상태(status)만 받고 기관 처리는 비동기입니다. 사용자가 버튼을 누른 뒤 진행 단계와 실행 내역 화면을 두지 않으면 「눌렀는데 안 되나?」로 이중 제출이 늘어납니다. 아래는 패턴 A, B 공통으로 쓸 수 있는 UI 예시입니다.

실행 중 (모달 또는 패널)

text
┌─ 홈택스 매출 세금계산서 조회 ────────────────────────────┐
│ ✓ 거래처 권한 확인                                        │
│ ✓ 요청 접수, job_01M2T1J3…                                │
│ ◐ 처리 중, 약 45% (추정), 인증 처리 중…                    │
│   다음 확인 2초 후 (retry_after_seconds)                  │
│ ○ 결과 반영                                               │
│                                                          │
│ [ 진행 상태 확인 ]     자동 갱신 (대기·처리 중일 때만)     │
│                                                          │
│ 이 창을 닫아도 고객사 서버에 job_id가 저장되어 있습니다.     │
│ 실행 내역이나 다음 로그인 때 이어서 조회할 수 있습니다.      │
└──────────────────────────────────────────────────────────┘

완료 후

text
┌─ 홈택스 매출 세금계산서 조회 ────────────────────────────┐
│ ✓ 완료, 매출 세금계산서 14건, 거래처 메모에 반영됨          │
│ [ 결과 요약 보기 ]   [ 같은 기간 다시 실행 ]                  │
└──────────────────────────────────────────────────────────┘

GET Job 응답 → 화면 (서버가 XBOSS 조회 후 전달)

XBOSS 필드화면에 보여 줄 것주의
status대기 중 / 처리 중 / 완료 / 실패 등 사용자 문구QUEUED, RUNNING, SUCCEEDED, FAILED 등 6종 — 작업 처리 흐름
progress「약 N% (추정)」 또는 막대기관 실제 진행률 아님. status와 경과 시간으로 서버가 추정
progress_message「인증 처리 중…」 등 단계 문구종료 시 null
retry_after_seconds「N초 후 다시 확인」폴링 간격 권장값. 고객사가 더 길게 잡을 수 있음
error.code실패 사유 요약(운영자 상세는 별도)모든 실패에 unlink 하지 않음 — 오류와 운영
화면 버튼고객사 API (예시)동작
진행 상태 확인GET /api/customers/{id}/xdata/jobs/{request_reference}저장된 job_id로 XBOSS 상태만 갱신. 새 Job 없음
자동 갱신위 GET을 retry_after_seconds~수 초 간격, 횟수 한도로QUEUED/RUNNING일 때만
같은 기간 다시 실행POST .../jobs + 새 request_reference사용자 새 실행. 새 request_key와 새 job_id
결과 요약 보기GET .../jobs/{request_reference}/summary고객사 DB 요약만

재조회, 복구, 새 실행 구분

상황식별자다음 행동
화면에서 진행만 다시 보기같은 request_reference와 job_id저장된 job_id로 GET 만 합니다 - 새 요청이 아니므로 Idempotency-Key, request_key 는 다시 보내지 않습니다(GET 은 그 헤더를 받지 않습니다)
제출 응답 유실, 접수 불명저장된 request_key + 동일 요청 본문Job 복구 §3. 새 Job 만들기 전 접수 확인. 데모의 일일 한도를 다 쓴 뒤에는 같은 키 재전송도 429라 저장한 job_id로 조회합니다
사용자가 「다시 실행」을 누르거나 실패 후 재시도새 request_reference + 새 request_keyPOST 고객사 실행 API. 이전 job_id는 내역 조회만
창 닫음, 브라우저 종료고객사 DB의 job_id와 request_reference서버가 저장해 두었으면 다음 GET으로 이어 감. 미저장이면 복구 절

실행 내역 (거래처 또는 설정)

text
┌─ XBOSS 실행 내역 – 엑스보스 ───────────────────────────────┐
│ 업무 이름                    상태   반영  시각              │
│ 홈택스-매출-2026-01-…        완료    ✓    오늘 09:14        │
│ 홈택스-로그인-확인-…         완료    ✓    오늘 09:05        │
│ 홈택스-매출-2026-01-…        실패    —    어제 (인증 불일치) │
│                                                          │
│ [ 상태 다시 확인 ]  [ 실패 건 재실행 ]  [ 상세(운영자) ]     │
└──────────────────────────────────────────────────────────┘
내역 버튼다음 행동
상태 다시 확인재조회: 같은 request_reference와 job_id로 GET만. 새 Job 없음
실패 건 재실행새 실행: 새 request_reference로 패턴 A/B. error.code와 error.provider_error_ref에 따라 연결, params, 권한을 먼저 확인. unlink는 운영 절에 해당할 때만
상세(운영자)job_id, error.code, error.provider_error_ref. HTTP 오류로 실패한 호출은 오류 객체의 request_id·trace_id도 함께. secret, 인증서, 결과 원문 제외

로그인 확인(session.login) 성공과 세금계산서, 신고 자료 수집 성공은 다른 업무입니다. 연결 검증만 끝났다고 자료 조회까지 완료했다고 안내하지 마세요. 패턴 A의 드롭다운과 패턴 B의 체크리스트에 연결 검증과 실제 상품 action을 구분해 넣으세요.

8. SDK 없이 HTTP로 연결

고객사 서버에서 Authorization: Basic base64(UTF-8 client_id:client_secret), X-Env-Scope에는 발급받은 환경(샌드박스 sandbox / 데모 real_test / 정식 production)을, JSON 쓰기에는 Content-Type: application/json을 사용합니다. Base64는 암호화가 아닙니다. HTTPS가 필요하며 브라우저에 Basic 키를 노출하지 않습니다. 등록 복구는 키를 경로에 넣지 않습니다 — 등록할 때 보낸 것과 같은 Idempotency-Key 헤더 값으로 조회합니다. 인증, 암호화, 실제 OpenAPI 계약을 기준으로 구현하세요.

순서HTTP다음에 사용할 값
기관 입력 규격GET /v1/credentials/linkable-providersprovider / credential_kind / input_contract
암호화 공개키GET /v1/crypto/public-key암호화 명세에 따른 입력 생성. 인증서 Base64만 보내는 방식 아님
연결 등록POST /v1/credentials/link + Idempotency-Key: operation_idis_primary=false, 기관별 암호화 필드 → account_link_id. 기관 아이디와 비밀번호를 같은 아이디에 다른 비밀번호로 다시 등록할 때만 confirm: true를 함께 보냅니다(0.1.2 SDK의 link_id_password와 LinkIdPasswordAsync도 confirm 인자로 이 값을 보낼 수 있습니다 — 기본은 꺼져 있습니다)
등록 복구GET /v1/credentials/link-operations + Idempotency-Key: operation_idCOMPLETED / UNKNOWN. 현재 ACTIVE는 별도 조회
고객 업무POST /v1/jobs + Idempotency-Key: 업무 요청 키provider, action, account_link_id, params → job_id
상태 / 결과GET /v1/jobs/{job_id} 및 /resultSUCCEEDED 후 상품 결과 사용
연결 해제POST /v1/credentials/{account_link_id}/unlinkunlink_reason=user_request 등 허용 enum. Job 진행 중이면 거부될 수 있음

9. MCP에 같은 업무 제공

customer_workflow.py의 register_mcp_tools(mcp, workflow, current_actor)는 고객사의 기존 인증된 MCP 서버에 도구를 등록합니다. MCP 서버, OAuth, AI 클라이언트 설정을 자동 설치하지 않습니다. current_actor는 인증 세션에서 얻고 AI 인수로 받지 않습니다. AI에는 고객 참조와 업무 참조만 받고, 권한, 매핑, Job, 결과 저장은 일반 화면과 동일한 CustomerWorkflow를 사용하세요. XBOSS MCP 직접 호출은 다른 방식입니다. 그 페이지는 고객사 MCP 도구를 만드는 예제가 아니라 이미 제공되는 XBOSS MCP 를 호출하는 예제이므로 이어서 붙이는 코드로 혼용하지 마세요.