# Cryptoments Partner Console — API 명세서

> **버전**: v1.0
> **작성일**: 2026-03-05
> **범위**: Partner Console Backend API 전체
> **대상**: 백엔드 개발팀
> **기반**: Partner Console 화면설계서 v1.2, IA v1.2, V2 DDL (39 tables)

---

## 목차

1. [공통 규칙](#1-공통-규칙)
2. [인증 API](#2-인증-api)
3. [대시보드 API](#3-대시보드-api)
4. [입금 관리 API](#4-입금-관리-api)
5. [출금 관리 API](#5-출금-관리-api)
6. [잔액/원장 API](#6-잔액원장-api)
7. [정산 API](#7-정산-api)
8. [가스비 API](#8-가스비-api)
9. [하위 파트너 관리 API (DISTRIBUTOR)](#9-하위-파트너-관리-api)
10. [총판 리포트 API (DISTRIBUTOR)](#10-총판-리포트-api)
11. [연동 설정 API](#11-연동-설정-api)
12. [내 계정 API](#12-내-계정-api)
13. [크로스컷팅 — 사용자 컨텍스트 API](#13-사용자-컨텍스트-api)
14. [Webhook 이벤트 정의](#14-webhook-이벤트-정의)
- [부록 A: 에러 코드 전체 목록](#부록-a-에러-코드-전체-목록)
- [부록 B: API 엔드포인트 인덱스](#부록-b-api-엔드포인트-인덱스)

---

## 1. 공통 규칙

### 1.1 Base URL

```
Production : https://api.cryptoments.cc/partner/v2
Staging    : https://api-staging.cryptoments.cc/partner/v2
```

### 1.2 인증 방식

- **Bearer Token** (JWT)
- Access Token: 15분, Refresh Token: 7일
- 모든 인증 필요 API는 `Authorization: Bearer {access_token}` 헤더 필수

```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

### 1.3 공통 요청 헤더

| 헤더 | 필수 | 설명 |
|------|:----:|------|
| Authorization | ✓ (인증 API 제외) | Bearer {access_token} |
| Content-Type | ✓ (POST/PUT/PATCH) | application/json |
| Accept-Language | — | ko (기본) / en |
| X-Request-Id | — | 클라이언트 요청 추적 ID (UUID) |

### 1.4 공통 응답 포맷

**성공 (단건)**:
```json
{
  "success": true,
  "data": { ... }
}
```

**성공 (목록 — 페이지네이션)**:
```json
{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "size": 20,
    "total_count": 158,
    "total_pages": 8
  }
}
```

**실패**:
```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "금액은 0보다 커야 합니다",
    "details": [
      { "field": "amount", "reason": "must be greater than 0" }
    ]
  }
}
```

### 1.5 페이지네이션

| 파라미터 | 기본값 | 범위 | 설명 |
|---------|--------|------|------|
| page | 1 | 1~ | 페이지 번호 |
| size | 20 | 1~100 | 페이지당 건수 |
| sort | created_at | 컬럼명 | 정렬 기준 |
| order | desc | asc/desc | 정렬 방향 |

### 1.6 날짜/시간

- 모든 날짜/시간은 **ISO 8601** 형식: `2026-03-05T14:30:00+09:00`
- 필터 기간: `from`, `to` 파라미터 (inclusive)

### 1.7 금액

- 모든 금액은 **문자열(string)** 전송 (부동소수점 정밀도 손실 방지)
- 예: `"amount": "1250.500000"`

### 1.8 공통 에러 코드

| HTTP | 코드 | 설명 |
|------|------|------|
| 400 | INVALID_PARAMETER | 파라미터 유효성 검증 실패 |
| 401 | UNAUTHORIZED | 인증 토큰 없음/만료 |
| 401 | TOKEN_EXPIRED | Access Token 만료 (Refresh 필요) |
| 403 | FORBIDDEN | 권한 없음 (partner_type 불일치 등) |
| 403 | ACCOUNT_SUSPENDED | 계정 정지 상태 |
| 403 | 2FA_REQUIRED | 2FA 인증 미완료 |
| 404 | NOT_FOUND | 리소스 없음 |
| 409 | CONFLICT | 상태 충돌 (이미 처리된 건 등) |
| 429 | RATE_LIMITED | 요청 한도 초과 |
| 500 | INTERNAL_ERROR | 서버 오류 |

### 1.9 CSV 내보내기 (공통)

목록 조회 API에 `format=csv` 쿼리 파라미터를 추가하면 CSV 파일로 응답.

```
GET /deposits?from=2026-03-01&status=CONFIRMED&format=csv
```

| 규칙 | 상세 |
|------|------|
| Content-Type | `text/csv; charset=utf-8` (BOM 포함) |
| Content-Disposition | `attachment; filename="deposits_20260305.csv"` |
| 최대 건수 | 10만 건 (초과 시 400 EXPORT_TOO_LARGE) |
| 페이지네이션 | 무시 (전체 결과 반환) |
| 필터 | 동일 쿼리 파라미터 적용 |

**CSV 지원 엔드포인트**:

| 엔드포인트 | 파일명 패턴 |
|-----------|-----------|
| GET /deposits | deposits_{date}.csv |
| GET /deposit-sessions | sessions_{date}.csv |
| GET /payment-links | payment_links_{date}.csv |
| GET /withdrawals | withdrawals_{date}.csv |
| GET /ledger | ledger_{date}.csv |
| GET /settlement/daily-fees | settlement_fees_{date}.csv |
| GET /gas-costs | gas_costs_{date}.csv |
| GET /gas-invoices | gas_invoices_{date}.csv |
| GET /collections | collections_{date}.csv |
| GET /axim-payments | axim_payments_{date}.csv |

---

### 1.10 데이터 범위 규칙

- 모든 API는 로그인 파트너(`partner_id`)의 데이터만 반환
- DISTRIBUTOR: 기본적으로 자신 데이터만, 하위 파트너 관리/리포트 API만 하위 데이터 접근
- 하위 파트너 데이터 조회 시 `parent_partner_id` 기반 재귀 검증

---

## 2. 인증 API

### 2.1 POST /auth/login

**화면**: PCR-0100

로그인 처리. 2FA 활성화 파트너는 `requires_2fa: true` 반환 후 2FA 인증 API 호출 필요.

**Request Body**:
```json
{
  "email": "partner@example.com",
  "password": "SecurePass123!"
}
```

**Response 200** (2FA 미사용):
```json
{
  "success": true,
  "data": {
    "access_token": "eyJ...",
    "refresh_token": "eyJ...",
    "expires_in": 900,
    "partner": {
      "id": 1,
      "partner_code": "PTN_2601_A1B2C3D4",
      "partner_name": "테스트 파트너",
      "partner_type": "MERCHANT",
      "two_factor_enabled": false,
      "status": "ACTIVE"
    }
  }
}
```

**Response 200** (2FA 사용):
```json
{
  "success": true,
  "data": {
    "requires_2fa": true,
    "temp_token": "tmp_eyJ...",
    "partner_type": "MERCHANT"
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 401 | INVALID_CREDENTIALS | 이메일/비밀번호 불일치 |
| 403 | ACCOUNT_SUSPENDED | partners.status = SUSPENDED |
| 403 | ACCOUNT_TERMINATED | partners.status = TERMINATED |
| 429 | ACCOUNT_LOCKED | 5회 연속 실패 — 잠금 (lock_until 포함) |

---

### 2.2 POST /auth/2fa/verify

**화면**: PCR-0110

2FA OTP 검증. 로그인 시 `requires_2fa: true`인 경우 호출.

**Request Body**:
```json
{
  "temp_token": "tmp_eyJ...",
  "otp_code": "123456"
}
```

**Response 200**:
```json
{
  "success": true,
  "data": {
    "access_token": "eyJ...",
    "refresh_token": "eyJ...",
    "expires_in": 900,
    "partner": { ... }
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 401 | INVALID_OTP | OTP 코드 불일치 |
| 401 | OTP_EXPIRED | temp_token 만료 (5분) |

---

### 2.3 POST /auth/token/refresh

Access Token 갱신.

**Request Body**:
```json
{
  "refresh_token": "eyJ..."
}
```

**Response 200**:
```json
{
  "success": true,
  "data": {
    "access_token": "eyJ...",
    "expires_in": 900
  }
}
```

---

### 2.4 POST /auth/password/forgot

**화면**: PCR-0120

비밀번호 재설정 이메일 발송 요청.

**Request Body**:
```json
{
  "email": "partner@example.com"
}
```

**Response 200** (이메일 존재 여부 관계없이 동일 응답 — 보안):
```json
{
  "success": true,
  "data": {
    "message": "입력한 이메일로 비밀번호 재설정 링크를 전송했습니다"
  }
}
```

---

### 2.5 POST /auth/password/reset

**화면**: PCR-0130

비밀번호 재설정 실행.

**Request Body**:
```json
{
  "token": "reset_token_from_email",
  "new_password": "NewSecurePass456!",
  "new_password_confirm": "NewSecurePass456!"
}
```

**Response 200**:
```json
{
  "success": true,
  "data": {
    "message": "비밀번호가 변경되었습니다. 다시 로그인하세요."
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | INVALID_TOKEN | 토큰 만료 또는 무효 |
| 400 | PASSWORD_TOO_WEAK | 비밀번호 정책 미충족 |

---

### 2.6 POST /auth/logout

현재 세션 로그아웃 (Refresh Token 무효화).

**Response 200**:
```json
{
  "success": true,
  "data": { "message": "로그아웃 완료" }
}
```

---

## 3. 대시보드 API

### 3.1 GET /dashboard/summary

**화면**: PCR-1000

대시보드 요약 데이터 (오늘 기준).

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| include_sub_partners | — | DISTRIBUTOR: true 시 하위 합산 포함 |

**Response 200**:
```json
{
  "success": true,
  "data": {
    "today_deposits": {
      "total_count": 15,
      "by_currency": [
        { "currency": "USDT", "amount": "12500.000000", "count": 10 },
        { "currency": "USDC", "amount": "3200.000000", "count": 5 }
      ]
    },
    "today_withdrawals": {
      "total_count": 3,
      "by_currency": [
        { "currency": "USDT", "amount": "5000.000000", "count": 3 }
      ]
    },
    "balances": [
      {
        "currency": "USDT",
        "available": "45230.500000",
        "frozen": "1000.000000",
        "unsettled": "2500.000000"
      }
    ],
    "pending_alerts": {
      "pending_withdrawals": 2,
      "two_factor_not_set": true,
      "unpaid_invoices": 1
    },
    "sub_partner_summary": {
      "total": 12,
      "active": 10,
      "suspended": 1,
      "new_this_week": 2
    }
  }
}
```

> `sub_partner_summary`는 DISTRIBUTOR이고 `include_sub_partners=true`일 때만 포함

---

### 3.2 GET /dashboard/chart

**화면**: PCR-1000

7일 추이 차트 데이터.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| days | — | 기본 7, 최대 30 |

**Response 200**:
```json
{
  "success": true,
  "data": {
    "series": [
      {
        "date": "2026-03-05",
        "deposit_amount": "5200.000000",
        "deposit_count": 8,
        "withdrawal_amount": "1500.000000",
        "withdrawal_count": 2
      }
    ]
  }
}
```

---

## 4. 입금 관리 API

### 4.1 GET /deposits

**화면**: PCR-2000

입금 내역 목록 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from | — | 시작 일시 (ISO 8601) |
| to | — | 종료 일시 |
| status | — | DETECTED / CONFIRMING / CONFIRMED / COLLECTING / SETTLED / FAILED |
| currency_id | — | 통화 필터 |
| network_id | — | 네트워크 필터 |
| deposit_type | — | CRYPTO_PAYMENT / AXIM_PAYMENT / MANUAL 등 |
| partner_user_id | — | 파트너 사용자 ID 필터 |
| search | — | 키워드 (deposit_code, tx_hash) |
| page, size, sort, order | — | 페이지네이션 |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "deposit_code": "dep_2603_X1Y2Z3W4",
      "partner_user_id": "user_12345",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "amount": "100.000000",
      "fee_amount": "1.000000",
      "net_amount": "99.000000",
      "status": "CONFIRMED",
      "tx_hash": "0xabc123...",
      "block_confirmations": 12,
      "deposit_session_id": 500,
      "created_at": "2026-03-05T10:30:00+09:00",
      "confirmed_at": "2026-03-05T10:35:00+09:00"
    }
  ],
  "pagination": { "page": 1, "size": 20, "total_count": 158, "total_pages": 8 }
}
```

---

### 4.2 GET /deposits/{deposit_id}

**화면**: PCR-2000 (상세 드로어)

입금 단건 상세 (상태 이력 포함).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 1001,
    "deposit_code": "dep_2603_X1Y2Z3W4",
    "partner_user_id": "user_12345",
    "currency": { "id": 1, "symbol": "USDT", "decimals": 6 },
    "network": { "id": 1, "name": "Tron (TRC-20)", "explorer_url": "https://tronscan.org" },
    "amount": "100.000000",
    "fee_amount": "1.000000",
    "net_amount": "99.000000",
    "status": "CONFIRMED",
    "tx_hash": "0xabc123...",
    "block_confirmations": 12,
    "deposit_address": "T9yD14Nj9j7xAB4dbGei...",
    "deposit_session": {
      "id": 500,
      "session_code": "ses_2603_A1B2C3D4",
      "deposit_method": "HD_WALLET",
      "request_source": "API"
    },
    "status_history": [
      { "status": "DETECTED", "changed_at": "2026-03-05T10:30:00+09:00" },
      { "status": "CONFIRMING", "changed_at": "2026-03-05T10:30:05+09:00" },
      { "status": "CONFIRMED", "changed_at": "2026-03-05T10:35:00+09:00" }
    ],
    "created_at": "2026-03-05T10:30:00+09:00",
    "updated_at": "2026-03-05T10:35:00+09:00"
  }
}
```

---

### 4.3 GET /deposit-sessions

**화면**: PCR-2010

입금 세션 목록 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| status | — | CREATED / WAITING / RECEIVED / COMPLETED / REFUNDED / EXPIRED / CANCELLED |
| deposit_method | — | HD_WALLET / DECIMAL_MATCH / DIRECT / EXTERNAL_WALLET |
| has_payment_link | — | true/false — 결제링크 경유 여부 |
| partner_user_id | — | 파트너 사용자 ID |
| search | — | 세션코드, 주소 |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 500,
      "session_code": "ses_2603_A1B2C3D4",
      "partner_user_id": "user_12345",
      "deposit_address": "T9yD14Nj9j7xAB4dbGei...",
      "deposit_method": "HD_WALLET",
      "request_source": "API",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "crypto_amount": "100.000000",
      "received_amount": "100.000000",
      "amount_match_status": "EXACT",
      "status": "COMPLETED",
      "deposit_count": 1,
      "payment_link_id": null,
      "expires_at": "2026-03-05T11:30:00+09:00",
      "created_at": "2026-03-05T10:30:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 4.4 POST /deposit-sessions

**화면**: PCR-2015

콘솔에서 입금 세션 직접 생성. `request_source = "CONSOLE"` 자동 설정.

**Request Body**:
```json
{
  "partner_user_id": "user_12345",
  "currency_id": 1,
  "network_id": 1,
  "deposit_method": "HD_WALLET",
  "amount": "100.000000",
  "amount_currency": "CRYPTO",
  "expires_in_minutes": 60
}
```

> `amount_currency`: `"CRYPTO"` (crypto 금액 직접 입력) 또는 `"KRW"` (KRW 금액 → 환율 변환)

**Response 201**:
```json
{
  "success": true,
  "data": {
    "id": 501,
    "session_code": "ses_2603_B2C3D4E5",
    "deposit_address": "T9yD14Nj9j7xAB4dbGei...",
    "deposit_method": "HD_WALLET",
    "crypto_amount": "100.000000",
    "decimal_key": null,
    "full_amount": null,
    "exchange_rate": null,
    "currency": { "id": 1, "symbol": "USDT" },
    "network": { "id": 1, "name": "Tron (TRC-20)" },
    "qr_data": "T9yD14Nj9j7xAB4dbGei...",
    "status": "WAITING",
    "expires_at": "2026-03-05T11:30:00+09:00",
    "created_at": "2026-03-05T10:30:00+09:00"
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | NO_AVAILABLE_ADDRESS | 할당 가능한 입금 주소 없음 |
| 400 | UNSUPPORTED_NETWORK | 파트너에 활성화되지 않은 네트워크 |
| 400 | UNSUPPORTED_DEPOSIT_METHOD | 파트너에 허용되지 않은 입금 방식 |

---

### 4.5 GET /deposit-sessions/{session_id}

입금 세션 단건 상세 (연결된 입금 건 포함).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 500,
    "session_code": "ses_2603_A1B2C3D4",
    "...": "...(4.3 응답과 동일 + 아래 추가)",
    "deposits": [
      {
        "id": 1001,
        "deposit_code": "dep_2603_X1Y2Z3W4",
        "amount": "100.000000",
        "status": "CONFIRMED",
        "tx_hash": "0xabc123...",
        "created_at": "2026-03-05T10:30:00+09:00"
      }
    ]
  }
}
```

---

### 4.6 GET /payment-links

**화면**: PCR-2020

결제 링크 목록 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| status | — | ACTIVE / USED / EXPIRED |
| partner_user_id | — | 파트너 사용자 ID |
| search | — | link_code, title, partner_reference |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 200,
      "link_code": "lnk_2603_P1Q2R3S4",
      "title": "상품 A 결제",
      "partner_user_id": "user_12345",
      "partner_reference": "order_9876",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": null,
      "amount": "50.000000",
      "deposit_method": null,
      "status": "ACTIVE",
      "payment_url": "https://pay.cryptoments.cc/lnk_2603_P1Q2R3S4",
      "deposit_session_id": null,
      "expires_at": null,
      "created_at": "2026-03-05T10:00:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

> `currency`, `network`, `amount`, `deposit_method`가 null이면 결제 페이지에서 사용자 선택

---

### 4.7 POST /payment-links

**화면**: PCR-2020 (생성 폼)

결제 링크 생성.

**Request Body**:
```json
{
  "title": "상품 A 결제",
  "partner_user_id": "user_12345",
  "partner_reference": "order_9876",
  "currency_id": 1,
  "network_id": null,
  "amount": "50.000000",
  "deposit_method": null,
  "expires_in_minutes": null
}
```

> nullable 필드를 null로 보내면 결제 페이지에서 사용자 선택

**Response 201**:
```json
{
  "success": true,
  "data": {
    "id": 201,
    "link_code": "lnk_2603_Q2R3S4T5",
    "payment_url": "https://pay.cryptoments.cc/lnk_2603_Q2R3S4T5",
    "status": "ACTIVE",
    "created_at": "2026-03-05T10:30:00+09:00"
  }
}
```

---

### 4.8 GET /deposit-addresses

**화면**: PCR-2030

입금 주소 풀 조회 (읽기 전용).

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| network_id | — | 네트워크 필터 |
| is_active | — | 활성 상태 필터 |
| page, size | — | |

**Response 200**:
```json
{
  "success": true,
  "data": {
    "summary": {
      "total_addresses": 50,
      "active_addresses": 48,
      "active_sessions": 12,
      "remaining_capacity": 180
    },
    "addresses": [
      {
        "id": 1,
        "address": "T9yD14Nj9j7xAB4dbGei...",
        "network": { "id": 1, "name": "Tron (TRC-20)" },
        "decimal_digits": 4,
        "max_concurrent_sessions": 10000,
        "active_session_count": 3,
        "is_active": true,
        "on_chain_balance": "150.340000",
        "created_at": "2026-01-15T00:00:00+09:00"
      }
    ]
  },
  "pagination": { ... }
}
```

---

### 4.9 GET /deposit-addresses/{address_id}

**화면**: PCR-2030 (상세 드로어)

입금 주소 단건 상세.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 1,
    "address": "T9yD14Nj9j7xAB4dbGei...",
    "network": { "id": 1, "name": "Tron (TRC-20)" },
    "decimal_digits": 4,
    "max_concurrent_sessions": 10000,
    "active_session_count": 3,
    "is_active": true,
    "on_chain_balance": "150.340000",
    "recent_sessions": [
      {
        "session_code": "ses_2603_A1B2C3D4",
        "status": "COMPLETED",
        "created_at": "2026-03-05T10:30:00+09:00"
      }
    ],
    "created_at": "2026-01-15T00:00:00+09:00"
  }
}
```

---

### 4.10 GET /collections

**화면**: PCR-2040

집금 현황 조회 (읽기 전용).

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| status | — | QUEUED / COLLECTING / COMPLETED / FAILED / DEFERRED |
| network_id | — | 네트워크 |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 300,
      "collection_code": "col_2603_M1N2O3P4",
      "source_address": "T9yD14Nj9j7xAB4dbGei...",
      "destination_address": "THot1234wallet...",
      "amount": "150.340000",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "status": "COMPLETED",
      "tx_hash": "0xdef456...",
      "gas_cost_native": "14.500000",
      "gas_cost_usd": "2.150000",
      "linked_deposit_count": 3,
      "created_at": "2026-03-05T11:00:00+09:00",
      "completed_at": "2026-03-05T11:02:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 4.11 GET /collections/{collection_id}

**화면**: PCR-2040 (상세 드로어)

집금 단건 상세 (연결된 입금 건 포함).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "...": "...(4.10 응답과 동일 + 아래 추가)",
    "linked_deposits": [
      {
        "deposit_code": "dep_2603_X1Y2Z3W4",
        "amount": "100.000000",
        "status": "COLLECTING",
        "created_at": "2026-03-05T10:30:00+09:00"
      }
    ],
    "status_history": [
      { "status": "QUEUED", "changed_at": "2026-03-05T11:00:00+09:00" },
      { "status": "COLLECTING", "changed_at": "2026-03-05T11:00:30+09:00" },
      { "status": "COMPLETED", "changed_at": "2026-03-05T11:02:00+09:00" }
    ]
  }
}
```

---

### 4.12 GET /axim-payments

**화면**: PCR-2050

Axim Pay 결제 내역 (partner_axim_settings 활성 시만 접근 가능).

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| status | — | REQUESTED / PENDING / CONFIRMED / CANCELED / DENIED / FAILED / EXPIRED |
| partner_user_id | — | 파트너 사용자 ID |
| search | — | payment_code, axim_payment_id |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 400,
      "payment_code": "pay_2603_R3S4T5U6",
      "axim_payment_id": "axim_pay_12345",
      "partner_user_id": "user_12345",
      "amount": "50.000000",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "status": "CONFIRMED",
      "linked_deposit": {
        "deposit_code": "dep_2603_Y2Z3A4B5",
        "amount": "50.000000",
        "status": "CONFIRMED"
      },
      "created_at": "2026-03-05T12:00:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 403 | AXIM_NOT_CONFIGURED | partner_axim_settings 미설정 |

---

### 4.13 POST /axim-payments/request

**화면**: PCR-C100 (컨텍스트 액션)

Axim 결제 요청 생성.

**Request Body**:
```json
{
  "partner_user_id": "user_12345",
  "amount": "50.000000",
  "currency_id": 1,
  "network_id": 1,
  "partner_reference": "order_9876"
}
```

**Response 201**:
```json
{
  "success": true,
  "data": {
    "id": 401,
    "payment_code": "pay_2603_S4T5U6V7",
    "axim_payment_id": "axim_pay_12346",
    "status": "REQUESTED",
    "created_at": "2026-03-05T12:30:00+09:00"
  }
}
```

---

## 5. 출금 관리 API

### 5.1 GET /withdrawals

**화면**: PCR-3000

출금 내역 목록 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| status | — | REQUESTED / PENDING_APPROVAL / APPROVED / REJECTED / PROCESSING / BROADCASTING / CONFIRMED / FAILED / CANCELLED |
| currency_id | — | 통화 |
| withdrawal_type | — | PARTNER_WITHDRAW / SETTLEMENT_WITHDRAW |
| partner_user_id | — | 파트너 사용자 ID |
| search | — | withdrawal_code, tx_hash |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 600,
      "withdrawal_code": "wdr_2603_T5U6V7W8",
      "partner_user_id": null,
      "withdrawal_type": "PARTNER_WITHDRAW",
      "currency": { "id": 1, "symbol": "USDT" },
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "amount": "500.000000",
      "fee_amount": "1.000000",
      "net_amount": "499.000000",
      "to_address": "TRecipient123...",
      "to_address_label": "메인 출금 지갑",
      "status": "CONFIRMED",
      "tx_hash": "0xghi789...",
      "created_at": "2026-03-05T14:00:00+09:00",
      "confirmed_at": "2026-03-05T14:05:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 5.2 GET /withdrawals/{withdrawal_id}

**화면**: PCR-3000 (상세 드로어)

출금 단건 상세 (상태 이력 포함).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "...": "...(5.1 응답과 동일 + 아래 추가)",
    "status_history": [
      { "status": "REQUESTED", "changed_at": "2026-03-05T14:00:00+09:00" },
      { "status": "APPROVED", "changed_at": "2026-03-05T14:01:00+09:00" },
      { "status": "PROCESSING", "changed_at": "2026-03-05T14:02:00+09:00" },
      { "status": "BROADCASTING", "changed_at": "2026-03-05T14:03:00+09:00" },
      { "status": "CONFIRMED", "changed_at": "2026-03-05T14:05:00+09:00" }
    ],
    "rejection_reason": null
  }
}
```

---

### 5.3 POST /withdrawals

**화면**: PCR-3010

수동 출금 요청.

**Request Body**:
```json
{
  "currency_id": 1,
  "network_id": 1,
  "to_address": "TRecipient123...",
  "whitelist_id": 5,
  "amount": "500.000000",
  "partner_user_id": null
}
```

> `whitelist_id` 또는 `to_address` 중 하나 필수. 화이트리스트 필수 파트너는 `whitelist_id` 필수.

**Response 201**:
```json
{
  "success": true,
  "data": {
    "id": 601,
    "withdrawal_code": "wdr_2603_U6V7W8X9",
    "status": "REQUESTED",
    "requires_approval": false,
    "estimated_fee": "1.000000",
    "created_at": "2026-03-05T14:30:00+09:00"
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | INSUFFICIENT_BALANCE | 가용 잔액 부족 |
| 400 | BELOW_MIN_WITHDRAWAL | 최소 출금액 미달 |
| 400 | WHITELIST_REQUIRED | 화이트리스트 주소만 허용 |
| 400 | ADDRESS_NOT_IN_WHITELIST | 입력 주소가 화이트리스트에 없음 |
| 400 | INVALID_ADDRESS_FORMAT | 주소 형식 오류 (네트워크별 검증) |

---

### 5.4 GET /withdrawals/whitelist

**화면**: PCR-3020

출금 주소 화이트리스트 목록.

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 5,
      "label": "메인 출금 지갑",
      "address": "TRecipient123...",
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "is_active": true,
      "created_at": "2026-02-01T00:00:00+09:00"
    }
  ]
}
```

---

### 5.5 POST /withdrawals/whitelist

화이트리스트 주소 추가.

**Request Body**:
```json
{
  "label": "콜드월렛",
  "address": "TNewAddress456...",
  "network_id": 1
}
```

**Response 201**: 생성된 화이트리스트 항목

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | INVALID_ADDRESS_FORMAT | 네트워크별 주소 형식 오류 |
| 409 | ADDRESS_ALREADY_EXISTS | 동일 네트워크+주소 이미 등록 |

---

### 5.6 PUT /withdrawals/whitelist/{id}

화이트리스트 주소 수정 (라벨 변경, 비활성화).

**Request Body**:
```json
{
  "label": "콜드월렛 (변경)",
  "is_active": false
}
```

**Response 200**: 수정된 화이트리스트 항목

---

## 6. 잔액/원장 API

### 6.1 GET /balances

**화면**: PCR-4000

통화별 잔액 현황.

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "currency": { "id": 1, "symbol": "USDT" },
      "available": "45230.500000",
      "frozen": "1000.000000",
      "unsettled_settlement": "2500.000000",
      "total": "48730.500000"
    }
  ]
}
```

---

### 6.2 GET /ledger

**화면**: PCR-4010

원장 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| currency_id | — | 통화 |
| entry_type | — | DEPOSIT_IN / WITHDRAWAL_OUT / FEE / SETTLEMENT / ADJUSTMENT 등 |
| search | — | 참조 코드 |
| page, size, sort, order | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 5000,
      "entry_type": "DEPOSIT_IN",
      "direction": "CREDIT",
      "amount": "99.000000",
      "balance_after": "45230.500000",
      "currency": { "id": 1, "symbol": "USDT" },
      "reference_type": "DEPOSIT",
      "reference_id": 1001,
      "reference_code": "dep_2603_X1Y2Z3W4",
      "description": "입금 — user_12345",
      "created_at": "2026-03-05T10:35:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

## 7. 정산 API

### 7.1 GET /settlement/daily-fees

**화면**: PCR-5000

일별 수수료 집계 조회.

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| from, to | — | 기간 |
| currency_id | — | 통화 |
| page, size | — | |

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 700,
      "fee_date": "2026-03-05",
      "currency": { "id": 1, "symbol": "USDT" },
      "role": "PARTNER",
      "total_fee_amount": "125.000000",
      "transaction_count": 50,
      "is_realized": false,
      "created_at": "2026-03-06T00:05:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 7.2 GET /settlement/realizations

**화면**: PCR-5010

정산 실현 내역.

**Query Parameters**: from, to, currency_id, page, size

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 800,
      "period_from": "2026-02-24",
      "period_to": "2026-03-02",
      "currency": { "id": 1, "symbol": "USDT" },
      "unrealized_amount": "875.000000",
      "realized_amount": "875.000000",
      "tx_hash": "0xrealize123...",
      "status": "COMPLETED",
      "realized_at": "2026-03-03T00:10:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 7.3 GET /settlement/balance

**화면**: PCR-5020

정산 잔액 현황.

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "currency": { "id": 1, "symbol": "USDT" },
      "unrealized": "250.000000",
      "realized": "3500.000000",
      "withdrawn": "2000.000000",
      "withdrawable": "1500.000000"
    }
  ]
}
```

---

### 7.4 POST /settlement/withdraw

**화면**: PCR-5030

쉐어 출금 요청 (정산 잔액에서 출금).

**Request Body**:
```json
{
  "currency_id": 1,
  "network_id": 1,
  "to_address": "TRecipient123...",
  "whitelist_id": 5,
  "amount": "1000.000000"
}
```

> withdrawals.withdrawal_type = 'SETTLEMENT_WITHDRAW'로 생성됨

**Response 201**: 출금 요청 결과 (5.3과 동일 구조)

**에러**: 5.3과 동일 + `INSUFFICIENT_SETTLEMENT_BALANCE`

---

## 8. 가스비 API

### 8.1 GET /gas-costs

**화면**: PCR-6000

가스비 기록 조회.

**Query Parameters**: from, to, network_id, page, size

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 900,
      "tx_type": "COLLECTION",
      "network": { "id": 1, "name": "Tron (TRC-20)" },
      "gas_amount_native": "14.500000",
      "gas_amount_usd": "2.150000",
      "tx_hash": "0xgas123...",
      "invoice_id": 100,
      "created_at": "2026-03-05T11:02:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

---

### 8.2 GET /gas-invoices

**화면**: PCR-6010

가스비 인보이스 목록.

**Query Parameters**: status (DRAFT/ISSUED/PAID/OVERDUE/CANCELLED), page, size

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "id": 100,
      "invoice_number": "INV-2603-0042",
      "billing_month": "2026-02",
      "total_amount_usd": "45.200000",
      "status": "ISSUED",
      "issued_at": "2026-03-01T00:00:00+09:00",
      "due_date": "2026-03-15",
      "paid_at": null
    }
  ],
  "pagination": { ... }
}
```

---

### 8.3 GET /gas-invoices/{invoice_id}

**화면**: PCR-6011

인보이스 상세 (납부 안내 포함).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 100,
    "invoice_number": "INV-2603-0042",
    "billing_month": "2026-02",
    "total_amount_usd": "45.200000",
    "status": "ISSUED",
    "payment_guide": {
      "deposit_address": "THotWallet123...",
      "currency": "USDT",
      "network": "Tron (TRC-20)",
      "instructions": "아래 주소로 정확한 금액을 전송하세요. 입금 확인 후 자동으로 PAID 처리됩니다."
    },
    "cost_records": [
      {
        "id": 900,
        "tx_type": "COLLECTION",
        "gas_amount_usd": "2.150000",
        "created_at": "2026-03-05T11:02:00+09:00"
      }
    ],
    "issued_at": "2026-03-01T00:00:00+09:00",
    "due_date": "2026-03-15"
  }
}
```

---

## 9. 하위 파트너 관리 API

> **접근 제한**: partner_type = DISTRIBUTOR만 호출 가능. MERCHANT는 403 FORBIDDEN.

### 9.1 GET /sub-partners

**화면**: PCR-7000

하위 파트너 목록 (직속 + 재귀 2단계).

**Query Parameters**:

| 파라미터 | 필수 | 설명 |
|---------|:----:|------|
| status | — | PENDING / ACTIVE / SUSPENDED / TERMINATED |
| partner_type | — | MERCHANT / DISTRIBUTOR |
| view | — | `list` (기본) / `tree` (트리 구조) |
| search | — | partner_code, partner_name |
| page, size | — | |

**Response 200** (list):
```json
{
  "success": true,
  "data": [
    {
      "id": 10,
      "partner_code": "PTN_2601_C3D4E5F6",
      "partner_name": "하위 파트너 A",
      "partner_type": "MERCHANT",
      "deposit_fee_rate": "0.010000",
      "status": "ACTIVE",
      "depth": 1,
      "created_at": "2026-02-01T00:00:00+09:00"
    }
  ],
  "pagination": { ... }
}
```

**Response 200** (tree):
```json
{
  "success": true,
  "data": {
    "id": 1,
    "partner_code": "PTN_2601_A1B2C3D4",
    "partner_name": "나(총판)",
    "children": [
      {
        "id": 10,
        "partner_code": "PTN_2601_C3D4E5F6",
        "partner_name": "하위 파트너 A",
        "partner_type": "MERCHANT",
        "status": "ACTIVE",
        "children": []
      },
      {
        "id": 11,
        "partner_name": "2차 총판 B",
        "partner_type": "DISTRIBUTOR",
        "status": "ACTIVE",
        "children": [
          { "id": 20, "partner_name": "B의 하위", "partner_type": "MERCHANT", "status": "ACTIVE", "children": [] }
        ]
      }
    ]
  }
}
```

---

### 9.2 POST /sub-partners

**화면**: PCR-7010

하위 파트너 등록.

**Request Body**:
```json
{
  "partner_type": "MERCHANT",
  "partner_name": "새 파트너",
  "business_name": "주식회사 새파트너",
  "contact_name": "김담당",
  "contact_email": "contact@newpartner.com",
  "contact_phone": "010-1234-5678",
  "login_email": "login@newpartner.com",
  "fee_settings": {
    "deposit_fee_rate": "0.012000"
  },
  "chain_configs": [
    { "network_id": 1, "currency_id": 1, "is_active": true }
  ]
}
```

> DISTRIBUTOR 타입 생성 시 fee_settings에 `parent_fee_rate`, `max_fee_cap` 포함

**Response 201**:
```json
{
  "success": true,
  "data": {
    "id": 21,
    "partner_code": "PTN_2603_NEW12345",
    "status": "ACTIVE",
    "initial_password_sent": true,
    "login_email": "login@newpartner.com"
  }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | FEE_RATE_OUT_OF_RANGE | deposit_fee_rate가 min_fee_rate~max_fee_cap 범위 벗어남 |
| 400 | CHAIN_NOT_AVAILABLE | 자신에게 활성화되지 않은 네트워크/통화 |
| 409 | EMAIL_ALREADY_EXISTS | login_email 중복 |

---

### 9.3 GET /sub-partners/{partner_id}

**화면**: PCR-7020

하위 파트너 상세.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 10,
    "partner_code": "PTN_2601_C3D4E5F6",
    "partner_name": "하위 파트너 A",
    "partner_type": "MERCHANT",
    "business_name": "주식회사 A",
    "contact_name": "이담당",
    "contact_email": "contact@a.com",
    "contact_phone": "010-5678-9012",
    "login_email": "login@a.com",
    "status": "ACTIVE",
    "fee_settings": {
      "deposit_fee_rate": "0.010000",
      "parent_fee_rate": null,
      "min_fee_rate": "0.008000",
      "max_fee_cap": "0.020000"
    },
    "chain_configs": [
      { "network_id": 1, "currency_id": 1, "is_active": true }
    ],
    "withdrawal_policy": {
      "whitelist_required": true,
      "auto_approve_limit": "1000.000000"
    },
    "created_at": "2026-02-01T00:00:00+09:00"
  }
}
```

---

### 9.4 PATCH /sub-partners/{partner_id}

**화면**: PCR-7020

하위 파트너 수정 (부분 업데이트).

**Request Body** (예: 수수료 변경 + 정지):
```json
{
  "status": "SUSPENDED",
  "fee_settings": {
    "deposit_fee_rate": "0.015000"
  }
}
```

**제약**:
- status: ACTIVE ↔ SUSPENDED만 가능 (TERMINATED는 Admin만)
- deposit_fee_rate: min_fee_rate ≤ 값 ≤ max_fee_cap

**Response 200**: 수정된 파트너 상세

---

### 9.5 GET /sub-partners/overview

**화면**: PCR-7030

하위 파트너 거래 현황 요약.

**Query Parameters**: period (today/week/month)

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "partner_id": 10,
      "partner_code": "PTN_2601_C3D4E5F6",
      "partner_name": "하위 파트너 A",
      "deposit_count": 25,
      "deposit_amount": "12500.000000",
      "withdrawal_count": 3,
      "withdrawal_amount": "3000.000000"
    }
  ]
}
```

---

## 10. 총판 리포트 API

> **접근 제한**: partner_type = DISTRIBUTOR만

### 10.1 GET /reports/revenue

**화면**: PCR-8000

하위 트리 매출 리포트.

**Query Parameters**: from, to, interval (daily/weekly/monthly), currency_id

**Response 200**:
```json
{
  "success": true,
  "data": {
    "summary": {
      "total_deposit": "125000.000000",
      "total_withdrawal": "45000.000000",
      "net_revenue": "80000.000000"
    },
    "series": [
      {
        "period": "2026-03-05",
        "deposit_amount": "5200.000000",
        "withdrawal_amount": "1500.000000"
      }
    ],
    "by_currency": [
      { "currency": "USDT", "deposit_amount": "100000.000000", "ratio": 0.8 },
      { "currency": "USDC", "deposit_amount": "25000.000000", "ratio": 0.2 }
    ]
  }
}
```

---

### 10.2 GET /reports/commission

**화면**: PCR-8010

수수료 수익 리포트.

**Query Parameters**: from, to, sub_partner_id (선택)

**Response 200**:
```json
{
  "success": true,
  "data": {
    "total_commission": "1250.000000",
    "by_partner": [
      {
        "partner_id": 10,
        "partner_name": "하위 파트너 A",
        "commission_amount": "500.000000",
        "transaction_count": 50
      }
    ]
  }
}
```

---

### 10.3 GET /reports/performance

**화면**: PCR-8020

하위 파트너별 성과 비교.

**Query Parameters**: from, to, sort_by (deposit_amount/commission/count), limit (기본 10)

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "rank": 1,
      "partner_id": 10,
      "partner_name": "하위 파트너 A",
      "deposit_count": 150,
      "deposit_amount": "50000.000000",
      "commission_amount": "500.000000",
      "activity_score": 95
    }
  ]
}
```

---

## 11. 연동 설정 API

### 11.1 GET /settings/api-key

**화면**: PCR-9000

API 키 조회 (마스킹).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "api_key_masked": "cmt_****...****a1b2",
    "api_key_created_at": "2026-02-01T00:00:00+09:00",
    "webhook_secret_exists": true
  }
}
```

---

### 11.2 POST /settings/api-key/regenerate

**화면**: PCR-9000

API 키 재발급. 기존 키 즉시 폐기.

**Request Body**:
```json
{
  "confirm": true
}
```

**Response 200**:
```json
{
  "success": true,
  "data": {
    "api_key": "cmt_live_a1b2c3d4e5f6g7h8...",
    "api_secret": "sec_x9y8z7w6v5u4t3s2...",
    "warning": "이 시크릿은 다시 표시되지 않습니다. 안전하게 보관하세요."
  }
}
```

---

### 11.3 GET /settings/webhook

**화면**: PCR-9010

Webhook 설정 조회.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "webhook_url": "https://partner.com/webhook/cryptoments",
    "is_active": true,
    "recent_deliveries": [
      {
        "id": 5000,
        "event_type": "deposit.confirmed",
        "status_code": 200,
        "delivered_at": "2026-03-05T10:35:01+09:00",
        "response_time_ms": 120
      }
    ]
  }
}
```

---

### 11.4 PUT /settings/webhook

Webhook URL 수정.

**Request Body**:
```json
{
  "webhook_url": "https://partner.com/webhook/v2"
}
```

**Response 200**: 수정된 설정

---

### 11.5 POST /settings/webhook/test

Webhook 테스트 전송.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "status_code": 200,
    "response_time_ms": 85,
    "response_body": "OK"
  }
}
```

---

### 11.6 POST /settings/webhook/retry/{delivery_id}

실패한 Webhook 수동 재시도.

**Response 200**: 재전송 결과

---

### 11.7 GET /settings/telegram

**화면**: PCR-9020

Telegram 알림 설정 조회. **공용 봇 방식** — 시스템이 운영하는 단일 봇(`@CryptomentsBot`)을 사용하며, 파트너별 bot_token은 없음. 파트너는 봇 링크를 클릭하여 텔레그램에서 `/start` → 서버가 chat_id를 자동 획득하여 연결.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "bot_username": "@CryptomentsBot",
    "bot_link": "https://t.me/CryptomentsBot",
    "is_connected": true,
    "chat_id_masked": "****6789",
    "connected_at": "2026-02-15T10:00:00+09:00",
    "subscriptions": [
      { "event_type": "deposit.confirmed", "enabled": true },
      { "event_type": "withdrawal.completed", "enabled": true },
      { "event_type": "invoice.issued", "enabled": false }
    ]
  }
}
```

> `is_connected = false`일 때: bot_link를 UI에 노출하여 연결 유도

---

### 11.8 DELETE /settings/telegram

Telegram 연결 해제.

**Response 200**:
```json
{
  "success": true,
  "data": { "is_connected": false }
}
```

---

### 11.9 PUT /settings/telegram/subscriptions

Telegram 이벤트 구독 설정 변경.

**Request Body**:
```json
{
  "subscriptions": [
    { "event_type": "deposit.confirmed", "enabled": true },
    { "event_type": "withdrawal.completed", "enabled": true },
    { "event_type": "invoice.issued", "enabled": true }
  ]
}
```

**Response 200**: 변경된 구독 목록

---

### 11.10 POST /settings/telegram/test

Telegram 테스트 메시지 발송.

**Response 200**:
```json
{
  "success": true,
  "data": { "sent": true, "message_id": 12345 }
}
```

---

### 11.11 GET /settings/axim

**화면**: PCR-9030

Axim Pay 연동 설정 조회.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "is_configured": true,
    "merchant_id": "axm_merchant_001",
    "callback_url": "https://api.cryptoments.cc/callback/axim",
    "status": "ACTIVE"
  }
}
```

> secret_key는 마스킹 처리

---

### 11.12 PUT /settings/axim

Axim 설정 수정.

**Request Body**:
```json
{
  "merchant_id": "axm_merchant_001",
  "secret_key": "new_secret_key",
  "callback_url": "https://api.cryptoments.cc/callback/axim"
}
```

**Response 200**: 수정된 설정 (secret_key 마스킹)

---

## 12. 내 계정 API

### 12.1 GET /account/profile

**화면**: PCR-A000

내 프로필 조회.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "id": 1,
    "partner_code": "PTN_2601_A1B2C3D4",
    "partner_name": "테스트 파트너",
    "partner_type": "MERCHANT",
    "business_name": "주식회사 테스트",
    "contact_name": "홍길동",
    "contact_email": "contact@test.com",
    "contact_phone": "010-1234-5678",
    "login_email": "login@test.com",
    "timezone": "Asia/Seoul",
    "locale": "ko",
    "two_factor_enabled": true,
    "status": "ACTIVE",
    "created_at": "2026-01-01T00:00:00+09:00",
    "last_login_at": "2026-03-05T09:00:00+09:00"
  }
}
```

---

### 12.2 PATCH /account/profile

프로필 수정 (수정 가능 필드만).

**Request Body**:
```json
{
  "contact_name": "홍길동 (변경)",
  "contact_phone": "010-9876-5432",
  "timezone": "Asia/Seoul",
  "locale": "en"
}
```

> login_email, partner_code, partner_type 등은 수정 불가

**Response 200**: 수정된 프로필

---

### 12.3 POST /account/password

**화면**: PCR-A010

비밀번호 변경.

**Request Body**:
```json
{
  "current_password": "OldPass123!",
  "new_password": "NewPass456!",
  "new_password_confirm": "NewPass456!"
}
```

**Response 200**:
```json
{
  "success": true,
  "data": { "message": "비밀번호가 변경되었습니다" }
}
```

**에러**:

| HTTP | 코드 | 상황 |
|------|------|------|
| 400 | WRONG_PASSWORD | 현재 비밀번호 불일치 |
| 400 | PASSWORD_TOO_WEAK | 새 비밀번호 정책 미충족 |
| 400 | SAME_PASSWORD | 현재와 동일한 비밀번호 |

---

### 12.4 POST /account/2fa/setup

**화면**: PCR-A020

2FA 등록 시작 (QR 코드 생성).

**Response 200**:
```json
{
  "success": true,
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "qr_code_url": "otpauth://totp/Cryptoments:login@test.com?secret=JBSWY3DPEHPK3PXP&issuer=Cryptoments",
    "qr_code_image": "data:image/png;base64,iVBOR..."
  }
}
```

---

### 12.5 POST /account/2fa/verify

2FA 등록 확인 (OTP 검증 → 활성화).

**Request Body**:
```json
{
  "otp_code": "123456"
}
```

**Response 200**:
```json
{
  "success": true,
  "data": {
    "two_factor_enabled": true,
    "backup_codes": ["12345678", "87654321", "..."]
  }
}
```

---

### 12.6 POST /account/2fa/disable

2FA 해제.

**Request Body**:
```json
{
  "otp_code": "123456"
}
```

**Response 200**:
```json
{
  "success": true,
  "data": { "two_factor_enabled": false }
}
```

---

### 12.7 GET /account/login-history

**화면**: PCR-A030

로그인 이력 조회.

**Query Parameters**: from, to, page, size

**Response 200**:
```json
{
  "success": true,
  "data": [
    {
      "login_at": "2026-03-05T09:00:00+09:00",
      "ip_address": "203.0.113.50",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X)...",
      "success": true,
      "failure_reason": null
    },
    {
      "login_at": "2026-03-04T23:30:00+09:00",
      "ip_address": "198.51.100.20",
      "user_agent": "Mozilla/5.0...",
      "success": false,
      "failure_reason": "INVALID_CREDENTIALS"
    }
  ],
  "pagination": { ... }
}
```

---

## 13. 사용자 컨텍스트 API

### 13.1 GET /user-context/{partner_user_id}/summary

**화면**: PCR-C100 (요약 카드)

특정 partner_user_id의 거래 집계 요약.

**Response 200**:
```json
{
  "success": true,
  "data": {
    "partner_user_id": "user_12345",
    "total_deposits": [
      { "currency": "USDT", "amount": "1250.000000", "count": 15 }
    ],
    "total_withdrawals": [
      { "currency": "USDT", "amount": "300.000000", "count": 3 }
    ],
    "last_transaction_at": "2026-03-05T14:23:11+09:00",
    "tab_counts": {
      "deposits": 15,
      "sessions": 20,
      "payment_links": 5,
      "withdrawals": 3,
      "axim_payments": 2
    }
  }
}
```

---

### 13.2 GET /user-context/{partner_user_id}/deposits

PCR-C100 입금 탭. 파라미터/응답은 4.1 GET /deposits와 동일 (partner_user_id 고정).

### 13.3 GET /user-context/{partner_user_id}/sessions

PCR-C100 세션 탭. 4.3 GET /deposit-sessions와 동일.

### 13.4 GET /user-context/{partner_user_id}/payment-links

PCR-C100 결제링크 탭. 4.6 GET /payment-links와 동일.

### 13.5 GET /user-context/{partner_user_id}/withdrawals

PCR-C100 출금 탭. 5.1 GET /withdrawals와 동일.

### 13.6 GET /user-context/{partner_user_id}/axim-payments

PCR-C100 Axim 탭. 4.12 GET /axim-payments와 동일.

---

## 14. Webhook 이벤트 정의

### 14.1 공통 Webhook 포맷

```json
{
  "event_id": "evt_2603_abc123",
  "event_type": "deposit.confirmed",
  "timestamp": "2026-03-05T10:35:00+09:00",
  "data": { ... },
  "signature": "hmac_sha256_signature"
}
```

- **서명 검증**: HMAC-SHA256(request_body, api_secret)
- **재시도 정책**: 실패 시 1분, 5분, 30분, 2시간, 24시간 후 재시도 (최대 5회)
- **타임아웃**: 5초 이내 2xx 응답 필요

### 14.2 이벤트 유형

| 이벤트 | 설명 | data 내용 |
|--------|------|----------|
| `deposit.detected` | 입금 감지 (미확인) | deposit 객체 |
| `deposit.confirmed` | 입금 확인 완료 | deposit 객체 (amount, fee, net_amount 포함) |
| `deposit.failed` | 입금 실패 | deposit 객체 + failure_reason |
| `withdrawal.requested` | 출금 요청 생성 | withdrawal 객체 |
| `withdrawal.approved` | 출금 승인 | withdrawal 객체 |
| `withdrawal.rejected` | 출금 거부 | withdrawal 객체 + rejection_reason |
| `withdrawal.completed` | 출금 완료 (온체인 확인) | withdrawal 객체 (tx_hash 포함) |
| `withdrawal.failed` | 출금 실패 | withdrawal 객체 + failure_reason |
| `session.expired` | 입금 세션 만료 | session 객체 |
| `collection.completed` | 집금 완료 | collection 객체 |
| `invoice.issued` | 가스비 인보이스 발행 | invoice 객체 |
| `invoice.overdue` | 인보이스 연체 | invoice 객체 |
| `settlement.realized` | 정산 실현 완료 | realization 객체 |
| `axim.confirmed` | Axim 결제 확인 | axim_payment 객체 |
| `axim.failed` | Axim 결제 실패 | axim_payment 객체 |

---

## 부록 A: 에러 코드 전체 목록

| HTTP | 코드 | 설명 |
|------|------|------|
| 400 | INVALID_PARAMETER | 파라미터 유효성 검증 실패 |
| 400 | PASSWORD_TOO_WEAK | 비밀번호 정책 미충족 (8자+, 영문+숫자+특수문자) |
| 400 | SAME_PASSWORD | 현재와 동일한 비밀번호 |
| 400 | WRONG_PASSWORD | 현재 비밀번호 불일치 |
| 400 | INVALID_TOKEN | 토큰 만료 또는 무효 |
| 400 | NO_AVAILABLE_ADDRESS | 할당 가능한 입금 주소 없음 |
| 400 | UNSUPPORTED_NETWORK | 파트너에 활성화되지 않은 네트워크 |
| 400 | UNSUPPORTED_DEPOSIT_METHOD | 파트너에 허용되지 않은 입금 방식 |
| 400 | INSUFFICIENT_BALANCE | 가용 잔액 부족 |
| 400 | INSUFFICIENT_SETTLEMENT_BALANCE | 정산 출금 가능 잔액 부족 |
| 400 | BELOW_MIN_WITHDRAWAL | 최소 출금액 미달 |
| 400 | WHITELIST_REQUIRED | 화이트리스트 주소만 허용 |
| 400 | ADDRESS_NOT_IN_WHITELIST | 입력 주소가 화이트리스트에 없음 |
| 400 | INVALID_ADDRESS_FORMAT | 네트워크별 주소 형식 오류 |
| 400 | FEE_RATE_OUT_OF_RANGE | 수수료율 범위 초과 |
| 400 | CHAIN_NOT_AVAILABLE | 미활성화 네트워크/통화 |
| 401 | UNAUTHORIZED | 인증 토큰 없음 |
| 401 | TOKEN_EXPIRED | Access Token 만료 |
| 401 | INVALID_CREDENTIALS | 이메일/비밀번호 불일치 |
| 401 | INVALID_OTP | OTP 코드 불일치 |
| 401 | OTP_EXPIRED | temp_token 만료 |
| 403 | FORBIDDEN | 권한 없음 |
| 403 | ACCOUNT_SUSPENDED | 계정 정지 상태 |
| 403 | ACCOUNT_TERMINATED | 계정 해지 상태 |
| 403 | 2FA_REQUIRED | 2FA 인증 미완료 |
| 403 | AXIM_NOT_CONFIGURED | Axim 미설정 |
| 404 | NOT_FOUND | 리소스 없음 |
| 409 | CONFLICT | 상태 충돌 |
| 409 | EMAIL_ALREADY_EXISTS | 이메일 중복 |
| 409 | ADDRESS_ALREADY_EXISTS | 주소 이미 등록 |
| 429 | RATE_LIMITED | 요청 한도 초과 |
| 429 | ACCOUNT_LOCKED | 로그인 5회 실패 잠금 |
| 500 | INTERNAL_ERROR | 서버 내부 오류 |

---

## 부록 B: API 엔드포인트 인덱스

| # | Method | Endpoint | 화면 | 접근 |
|---|--------|----------|------|------|
| 2.1 | POST | /auth/login | PCR-0100 | 공개 |
| 2.2 | POST | /auth/2fa/verify | PCR-0110 | 공개 |
| 2.3 | POST | /auth/token/refresh | — | 공개 |
| 2.4 | POST | /auth/password/forgot | PCR-0120 | 공개 |
| 2.5 | POST | /auth/password/reset | PCR-0130 | 공개 |
| 2.6 | POST | /auth/logout | — | 인증 |
| 3.1 | GET | /dashboard/summary | PCR-1000 | M+D |
| 3.2 | GET | /dashboard/chart | PCR-1000 | M+D |
| 4.1 | GET | /deposits | PCR-2000 | M+D |
| 4.2 | GET | /deposits/{id} | PCR-2000 | M+D |
| 4.3 | GET | /deposit-sessions | PCR-2010 | M+D |
| 4.4 | POST | /deposit-sessions | PCR-2015 | M+D |
| 4.5 | GET | /deposit-sessions/{id} | PCR-2010 | M+D |
| 4.6 | GET | /payment-links | PCR-2020 | M+D |
| 4.7 | POST | /payment-links | PCR-2020 | M+D |
| 4.8 | GET | /deposit-addresses | PCR-2030 | M+D |
| 4.9 | GET | /deposit-addresses/{id} | PCR-2030 | M+D |
| 4.10 | GET | /collections | PCR-2040 | M+D |
| 4.11 | GET | /collections/{id} | PCR-2040 | M+D |
| 4.12 | GET | /axim-payments | PCR-2050 | M+D* |
| 4.13 | POST | /axim-payments/request | PCR-C100 | M+D* |
| 5.1 | GET | /withdrawals | PCR-3000 | M+D |
| 5.2 | GET | /withdrawals/{id} | PCR-3000 | M+D |
| 5.3 | POST | /withdrawals | PCR-3010 | M+D |
| 5.4 | GET | /withdrawals/whitelist | PCR-3020 | M+D |
| 5.5 | POST | /withdrawals/whitelist | PCR-3020 | M+D |
| 5.6 | PUT | /withdrawals/whitelist/{id} | PCR-3020 | M+D |
| 6.1 | GET | /balances | PCR-4000 | M+D |
| 6.2 | GET | /ledger | PCR-4010 | M+D |
| 7.1 | GET | /settlement/daily-fees | PCR-5000 | M+D |
| 7.2 | GET | /settlement/realizations | PCR-5010 | M+D |
| 7.3 | GET | /settlement/balance | PCR-5020 | M+D |
| 7.4 | POST | /settlement/withdraw | PCR-5030 | M+D |
| 8.1 | GET | /gas-costs | PCR-6000 | M+D |
| 8.2 | GET | /gas-invoices | PCR-6010 | M+D |
| 8.3 | GET | /gas-invoices/{id} | PCR-6011 | M+D |
| 9.1 | GET | /sub-partners | PCR-7000 | D |
| 9.2 | POST | /sub-partners | PCR-7010 | D |
| 9.3 | GET | /sub-partners/{id} | PCR-7020 | D |
| 9.4 | PATCH | /sub-partners/{id} | PCR-7020 | D |
| 9.5 | GET | /sub-partners/overview | PCR-7030 | D |
| 10.1 | GET | /reports/revenue | PCR-8000 | D |
| 10.2 | GET | /reports/commission | PCR-8010 | D |
| 10.3 | GET | /reports/performance | PCR-8020 | D |
| 11.1 | GET | /settings/api-key | PCR-9000 | M+D |
| 11.2 | POST | /settings/api-key/regenerate | PCR-9000 | M+D |
| 11.3 | GET | /settings/webhook | PCR-9010 | M+D |
| 11.4 | PUT | /settings/webhook | PCR-9010 | M+D |
| 11.5 | POST | /settings/webhook/test | PCR-9010 | M+D |
| 11.6 | POST | /settings/webhook/retry/{id} | PCR-9010 | M+D |
| 11.7 | GET | /settings/telegram | PCR-9020 | M+D |
| 11.8 | DELETE | /settings/telegram | PCR-9020 | M+D |
| 11.9 | PUT | /settings/telegram/subscriptions | PCR-9020 | M+D |
| 11.10 | POST | /settings/telegram/test | PCR-9020 | M+D |
| 11.11 | GET | /settings/axim | PCR-9030 | M+D* |
| 11.12 | PUT | /settings/axim | PCR-9030 | M+D* |
| 12.1 | GET | /account/profile | PCR-A000 | M+D |
| 12.2 | PATCH | /account/profile | PCR-A000 | M+D |
| 12.3 | POST | /account/password | PCR-A010 | M+D |
| 12.4 | POST | /account/2fa/setup | PCR-A020 | M+D |
| 12.5 | POST | /account/2fa/verify | PCR-A020 | M+D |
| 12.6 | POST | /account/2fa/disable | PCR-A020 | M+D |
| 12.7 | GET | /account/login-history | PCR-A030 | M+D |
| 13.1 | GET | /user-context/{uid}/summary | PCR-C100 | M+D |
| 13.2 | GET | /user-context/{uid}/deposits | PCR-C100 | M+D |
| 13.3 | GET | /user-context/{uid}/sessions | PCR-C100 | M+D |
| 13.4 | GET | /user-context/{uid}/payment-links | PCR-C100 | M+D |
| 13.5 | GET | /user-context/{uid}/withdrawals | PCR-C100 | M+D |
| 13.6 | GET | /user-context/{uid}/axim-payments | PCR-C100 | M+D* |

> M = MERCHANT, D = DISTRIBUTOR, * = Axim 설정 필요
> **총 63개 엔드포인트** (공개 5 + 인증 58)

---

## 변경 이력

| 버전 | 날짜 | 변경 내용 |
|------|------|----------|
| v1.0 | 2026-03-05 | 최초 작성 — 63개 엔드포인트, 14개 섹션, Webhook 15개 이벤트, CSV 내보내기 공통 규칙, Telegram 공용 봇 방식 확정 |
