---
title: "Connected ID (account_link_id) 발급 — 사용자 인증서 연동"
method: POST
path: "/v1/credentials/link"
tags: ["CredentialLink"]
---

# Connected ID (account_link_id) 발급 — 사용자 인증서 연동

`POST /v1/credentials/link`

## Request

- Base URL: `https://api.xdata.kr` (the document declares no server; this is the origin it was published from)
- URL: `https://api.xdata.kr/v1/credentials/link`
- Auth: one of:
  - HTTP basic
  - OAuth 2 (authorization code, authorization URL `https://api.xdata.kr/oauth/authorize`, token URL `https://api.xdata.kr/oauth/token`)

`POST /v1/credentials/link` 자격증명 연결 단일 진입점.

JWT(console_user) 또는 desktop_agent client_credential 로 호출한다.
정책 평가 + user_credentials
INSERT + `account_link.linked` audit 단일 트랜잭션. 레거시 `register/v1` 도
동일 `_run_credential_link_pipeline` 경유. `register_user_certificate()`
우회 0건.

응답 분기:
    - LINKED                                   -> 201 Created + 2필드 envelope
    - IDEMPOTENT_MATCH / REACTIVATED / UPDATED -> 200 OK + 2필드 envelope
    - 정책 거부 (AuthPolicyError)            -> 401/402/403/422/429
    - Service 거부 (AccountLinkLinkError)    → 5 error_code 정밀 매핑
    - RSA/필드 검증 ValueError               → 400 VALIDATION_INVALID_FIELD
    - 그 외 예외                              → 500 INTERNAL_ERROR

같은 신원으로 다시 부를 때 무엇이 되는지는 **자재 값**이 정한다(결정 큐 D-76,
사용자 결정 2026-09-20 · 선언은
`identity_credential_link_lifecycle.MATERIAL_REPLACEMENT_BY_CREDENTIAL_KIND`):

    - 값이 같다                 -> ``IDEMPOTENT_MATCH``. 쓰기 0 · 감사 0 · 기관 세션 유지.
    - 값이 다르다 (인증서)      -> 지문이 자재 자체라 애초에 다른 연동이 된다.
    - 값이 다르다 (동의 왕복)   -> ``UPDATED``. 동의 화면이 곧 명시 확인이다.
    - 값이 다르다 (ID/비밀번호) -> ``confirm: true`` 가 없으면 **409 RESOURCE_CONFLICT**
      (``MATERIAL_REPLACEMENT_NOT_CONFIRMED``). 있으면 ``UPDATED``.

``UPDATED`` 는 전에 이 목록에 없었다 - 덮어쓰기가 문서화되지 않은 조용한 200 이었다.

`Idempotency-Key` 는 선택이다. 헤더가 없으면 위 분기가 그대로다. 헤더를 주면 첫 응답이
저장되고 같은 키의 재전송은 그 status/body 를 그대로 재생하며 `X-Idempotency-Replay: true`
를 붙인다(IDEMPOTENCY_STANDARD §4). 만료·본문 불일치는 409 IDEMPOTENCY_KEY_CONFLICT 다.

## Query parameters

- `env_scope` 'production' | 'real_test' | 'sandbox', nullable: 환경을 고릅니다. 값은 production · real_test · sandbox 중 하나입니다. 경로 접두(/v1/sandbox/... · /v1/test/... · /v1/production/...) · X-Env-Scope 헤더 · env_scope 쿼리 가운데 한 통로로 반드시 보내야 합니다. 빼면 자동으로 채우지 않고 거절하며, 두 통로에 서로 다른 값을 담아도 거절합니다. 거절은 HTTP 422 이고 오류 코드는 VALIDATION_ENV_SCOPE_MISMATCH 입니다.

## Headers

- `User-Agent` string, nullable
- `Idempotency-Key` string, nullable: 선택. 전사 표준 형식(1~128자 ASCII, UUID v4 권장). 주면 같은 키의 재전송이 등록을 두 번 하지 않고 첫 응답을 그대로 돌려준다(X-Idempotency-Replay: true). 결과는 GET /v1/credentials/link-operations 로 같은 키를 주고 조회한다.
- `X-Env-Scope` 'production' | 'real_test' | 'sandbox', nullable: 환경을 고릅니다. 값은 production · real_test · sandbox 중 하나입니다. 경로 접두(/v1/sandbox/... · /v1/test/... · /v1/production/...) · X-Env-Scope 헤더 · env_scope 쿼리 가운데 한 통로로 반드시 보내야 합니다. 빼면 자동으로 채우지 않고 거절하며, 두 통로에 서로 다른 값을 담아도 거절합니다. 거절은 HTTP 422 이고 오류 코드는 VALIDATION_ENV_SCOPE_MISMATCH 입니다.

## Request body

- CredentialLinkRequest: `POST /v1/credentials/link` 요청 본문 (레거시 register/v1 필드 정합 + v1 확장). RSA-OAEP 암호문 필드는 라우트가 서버 개인키로 복호화 후 kind별 credential payload로 변환한다. 평문 credential은 본 API body 에 직접 전송 금지. Headers: Authorization: Bearer console JWT, or Basic client_credential when app_credentials.integration_profile is desktop_agent. standard_external client keys remain 403 on this surface. X-Env-Scope: sandbox/real_test/production (필수). X-Trace-Id / X-Request-Id: 운영 상관관계 (선택). X-Organization-Id: admin_user 위임 hint (선택 — v1 Service 는 console_user 만 허용하므로 admin_user 는 403).
  - `cert_data` string, nullable: 인증서 DER (RSA-OAEP base64)
  - `key_data` string, nullable: 개인키 (RSA-OAEP base64)
  - `encrypted_pw` string, nullable: 인증서 비밀번호 (RSA-OAEP base64)
  - `login_id` string, nullable: 이용자 ID (RSA-OAEP base64)
  - `login_password` string, nullable: 이용자 비밀번호 (RSA-OAEP base64)
  - `display_label` string, nullable: 연결 표시 이름
  - `authorization_code` string, nullable: 동의 화면에서 돌아온 인가 코드(동의 왕복 provider 전용). RSA 로 감싸지 않는다 - 플랫폼이 고객 브라우저에 URL 로 돌려준 값이고 한 번만 쓸 수 있다.
  - `state` string, nullable: consent-url 이 발급한 state. 우리가 시작한 연결인지 확인한다.
  - `random_enc` string, nullable: random_enc (RSA-OAEP base64)
  - `cert_name` string, nullable: 인증서 별명
  - `is_primary` boolean: 대표 인증서 지정 여부
  - `confirm` boolean: 이미 등록된 같은 아이디의 **비밀번호를 바꾸겠다**는 명시 확인(ID/비밀번호 연동 전용). 값이 같으면 이 칸과 무관하게 아무것도 바꾸지 않는다. 값이 다른데 이 칸이 false 면 거절한다 - 옛 값으로 덮이면 되돌릴 수 없고 기관 세션도 함께 끊어지기 때문이다. 인증서·동의 왕복 연동에는 필요 없다.
  - `biz_number` string, nullable: 사업자등록번호
  - `business_name` string, nullable: 사업자명
  - `cert_oid` string, nullable: 인증서 정책 OID
  - `provider` string: 연동 provider (default hometax — 기존 클라이언트 무중단)
  - `exclude_providers` string[], nullable: console certificate fan-out 제외 provider 목록. omit/empty = registry certificate_fanout 전체 sibling 연동.
  - `serverTransmissionData` string, nullable: 레거시 이중 포장 JSON (호환 전용)

## Response `200`

Idempotent match or reactivated credential

- unknown

## Other responses

- `201`: New credential linked
- `422`: Validation Error

---

[API](https://skmtc.dev/xdata/apis/xboss-api.md) · [All operations](https://skmtc.dev/xdata/apis/xboss-api/llms.txt) · [OpenAPI document](https://skmtc.dev/xdata/apis/xboss-api/revisions/816de78950db?raw)
