Skip to content

API 레퍼런스

백엔드 API 엔드포인트를 도메인별로 묶었다. 핸들러 파일 151개가 라우트 등록 166건을 담당하고, 스케줄러 21개와 워커 4개가 있다(apps/infra/lambda/의 177개 파일, 2026-08-14 기준). lib/stacks/route-configs.tsROUTE_CONFIGS에서 셌다. 서로 다른 entry 값과 paths × methods의 합계다. api-stack.ts에서 세지 않는 이유는 라우트 표가 더 이상 거기 없기 때문이다(라우트 표가 그것을 마운트하는 스택 밖으로 옮겨 갔다). 이전 수치는 133/136이었고 2026-07-29 기준이었다. 스케줄러와 워커 목록은 문서 끝에 있다.

Auth (11)

메서드엔드포인트설명
POST/auth/admin-2fa/verify어드민 TOTP 2FA 코드를 검증한다
POST/auth/admin-login자격 증명으로 어드민 로그인
POST/auth/admin-oauthGoogle OAuth로 어드민 로그인
POST/auth/admin/logout-all호출자의 모든 세션을 폐기한다("모든 기기에서 로그아웃"). 본인 범위이고 FUND_MANAGER를 포함한 모든 어드민 role이 쓸 수 있다
GET/auth/admin/sessions호출자의 만료되지 않은 활성 어드민 세션(로그인된 기기)을 조회한다. FUND_MANAGER를 포함한 모든 어드민 role이 쓸 수 있고 구조상 본인 범위다
POST/auth/admin/sessions/{id}/revoke어드민 세션 하나를 폐기한다(기기별 로그아웃). 본인 범위이고 FUND_MANAGER를 포함한 모든 어드민 role이 쓸 수 있다
POST/auth/logout호출자의 HttpOnly refresh 토큰 쿠키와 분리 이전의 공용 쿠키를 지운다(web과 admin 로그아웃)
POST/auth/nonceSIWE 메시지 서명을 위한 랜덤 nonce를 요청한다
POST/auth/refreshHttpOnly refresh 쿠키를 새 access 토큰으로 교환한다. 요청 본문의 refresh 토큰은 거부된다. access와 refresh JWT는 서로 다른 필수 token_use 클레임을 담는다. 프론트엔드별로 다르다. 쿠키는 Origin으로 고른다(admin-web이면 refresh_token_admin, 아니면 refresh_token_investor, 없으면 분리 이전의 refresh_token). 호출한 앱과 role이 맞지 않는 토큰은 401이다. 한 앱이 다른 앱의 신원으로 갱신할 수 없다.
POST/auth/verify서명된 SIWE 메시지를 검증한다. access 토큰을 본문에, refresh 토큰을 HttpOnly 쿠키로 반환한다
POST/auth/verify-email이메일로 보낸 토큰으로 투자자 이메일 주소를 검증한다

Users (8)

메서드엔드포인트설명
GET/users유저 목록(필터 지원)
GET/users/me현재 인증된 유저의 프로필 조회
POST/users/me/email현재 유저의 이메일 설정·변경(검증이 시작된다)
GET/users/me/kyc-status현재 인증된 유저의 KYC 상태 확인
GET/users/me/notification-preferences투자자 본인의 알림 토글
PUT/users/me/notification-preferences투자자 알림 토글 수정
GET/users/{id}ID로 유저 상세 조회
GET/users/{id}/investor-detail어드민·운영자가 의도적으로 PII와 보유 내역을 여는 경로다. 서버가 PII_ACCESS를 먼저 기록하고, 감사 기록에 실패하면 상세를 반환하지 않는다.

Wallets (2)

메서드엔드포인트설명
GET/wallets지갑 목록(필터 지원)
GET/wallets/{address}주소로 지갑 상세 조회

KYC Logs (2)

메서드엔드포인트설명
GET/kyc-logsKYC 검증 로그 목록
GET/kyc-logs/{id}ID로 KYC 로그 상세 조회

KYC / SumSub (5)

메서드엔드포인트설명
POST/kyc/access-tokenKYC/KYB 플로우용 SumSub SDK 접근 토큰을 발급한다
POST/kyc/mint-sbtKYC 승인 후 Soulbound Token을 민팅한다. { userId }는 본인이거나 운영자다. force: true(운영자 전용)를 넣으면 이미 MINTED인 자격증명을 다시 발급한다. 워커가 기존 토큰을 소각하고 대체 토큰을 민팅하며(새 토큰 id와 유효기간) SBT_REMINT로 감사된다. 다른 민팅이 진행 중이면 409로 거부된다.
POST/kyc/reuseshare token으로 파트너가 공유한 KYC를 임포트한다(크로스-VASP 재사용, v3-17)
POST/kyc/sync지금 SumSub의 현재 심사 상태를 가져와 적용한다(재방문·재사용 신청자용이고 reconcile sweep 대기를 건너뛴다). 본문 { userId? }에서 생략하면 호출자가 자기 것을 동기화하고, 운영자는 다른 유저의 id를 넘길 수 있다. webhook 유실로 IN_REVIEW에 묶인 행을 푸는 어드민 수단이다
POST/kyc/webhookSumSub webhook이다. 검증 결과로 kyc_status를 갱신한다

Pools (18)

메서드엔드포인트설명
GET/pools전체 풀 목록(상태, NAV, APY, capacity 포함)
POST/pools설정(APY, 락업, reserve % 등)으로 새 풀을 만든다. 배포 워커가 시작된다
GET/pools/{id}ID로 풀 상세 조회
PATCH/pools/{id}풀 정보 수정(배포 후에는 Class B 필드가 거부된다)
DELETE/pools/{id}엔드포인트 하나에 모드가 둘이다(`?hard=true
POST/pools/{id}/close✅ 반영 완료(v3-110 B). actionclose / reopen이고 reason을 함께 받는다. 모집을 끝낸다. 입금만 막고 상환과 수익 청구는 완전히 열어 둔다. reopen으로 되돌릴 수 있되 의도적으로 좁다. 현재 CLOSED인 풀만 reopen할 수 있으므로 만기나 wind-down을 되돌리는 데 쓸 수 없다. 이 엔드포인트가 있는 이유는 예전에 CLOSED쓰기 경로가 전혀 없었기 때문이다. 문서화된 lifecycle 상태인데 코드베이스의 어떤 것도 거기 도달할 수 없었다. 스케줄러 절반(end_date에 자동 마감)은 pools.scheduler.lifecycle에 함께 들어갔다
PATCH/pools/{id} → restore✅ 반영 완료(v3-110 A). POST /restore는 없다. 아카이브 해제는 deleted_at을 지우는 PATCH이고, reason필수이며 전용 POOL_RESTORE 감사 이벤트에 기록된다(다른 수정에서는 거부된다). 아카이브 자체도 이제 **POOL_ARCHIVE**를 발생시켜, 아카이브·복원·영구 삭제가 하나의 POOL_DELETE가 아니라 구분되는 세 이벤트가 된다
GET/pools/{id}/eligibility풀별 KYC 게이팅 사전 확인이다. 투자 시점 게이트를 그대로 반영해 UI가 입금 시도 전에 경고할 수 있게 한다
POST/pools/{id}/freeze긴급 동결 제어다. actionfreeze / unfreeze뿐이다(PAUSER, 즉시). freeze에는 reason이 필요하고(v3-86) freeze_started_at을 미러링한다. 동결에는 기간이 있다(이탈은 72시간 뒤 자동으로 풀리고 동결 전체는 7일에 자동 만료된다, v3-28). ⚠️ propose_extend / execute_extend / cancel_extend410 Gone을 반환한다. 온체인 연장이 제거됐기 때문이다(7일 타임락이 7일 동결 수명과 같아서 제안이 실행 가능해질 때쯤이면 동결이 이미 끝나 있었고, 그때 실행하면 아무 일도 안 하거나 소급해서 풀을 다시 잠그고 72시간 이탈 차단을 재시작했다). 410 본문이 대체 수단 둘을 알려 준다. 동결이 만료되게 두고 POST /pools/{id}/pause로 입금을 계속 막거나, 파트너 부실이면 POST /pools/{id}/impairment를 쓴다.
GET/pools/{id}/fund-data/history그 풀의 Joob 펀드 성과 이력
GET/pools/{id}/fund-data/summary그 풀의 Joob 펀드 데이터 요약
GET/pools/{id}/fund-data/writeoffs그 풀의 대출 상각 이력
POST/pools/{id}/governance거버넌스 타임락 설정 변경이다. fund_wallet, reserve %, treasury, 관할, 상환 게이팅에 대한 propose / execute / cancel이다. ⚠️ redemption_gating_bps는 폐기됐고 MVP에 포함되지 않는다(2026-08-27, 아직 반영 안 됨, v3-150). 화면과 API에서 빠지지만 온체인 타임락 3종 세트는 남으므로 이 엔드포인트도 그 항목을 유지한다. → 04 → redemption_gating_bps
POST/pools/{id}/epoch-funding회차 하나를 통째로 펀딩한다(v3-119). epochFundTopUp[currentEpochId]를 크레딧하는데, 이는 회차 풀에서 Pool.fundRedemption이 이미 하던 일이다(넘겨받은 요청 id는 무시하면서). 그래서 요청별 라우트는 다른 메커니즘이 아니라 모양이 안 맞는 것이었다. amount(플랫폼 키가 서명한다. fundRedemptiononlyRole(YIELD_DEPOSITOR_ROLE)이므로 fund wallet이 그 키인 경우에만 동작한다)와 fund_tx_hash(FM이 fund wallet에서 서명했고, 엔드포인트가 영수증이 이 풀을 건드렸는지 검증해 기록한다) 중 정확히 하나를 받는다. ADMIN / SUPER_ADMIN / OPERATOR / FUND_MANAGER가 쓰고 펀드 범위다. redemption_requests에는 아무것도 쓰지 않는다. 크레딧은 회차에 속하므로, 펀딩 tx 하나로 대기 중인 N개 행에 도장을 찍으면 N번 잘못 귀속된다. 기록은 활동 로그의 POOL_EPOCH_FUNDED다. 응답에는 펀딩 후 체인에서 되읽은 epoch_id / topup_usd / shortfall_usd(18자리 정규화)가 담기고, 그 읽기가 실패하면 snapshot_unavailable: true가 담긴다.
POST/pools/{id}/epoch-schedule주기별 스케줄 노브다(v3-107). action: confirm_funding_date는 현재 요청을 접수 중인 주기를 대상으로 하고, action: delay_settlement는 정산을 기다리는 주기를 대상으로 한다. 체인 먼저, DB 나중이다. 체인이 갖고 있지 않은 확정을 주장하는 행은 v3-92 부류의 결함이다. 새로 추가된 선택 인자 epoch_id(v3-139)를 생략하면 서버가 예전과 똑같이 acceptingEpochId를 해석하므로 기존 호출자 하나는 그대로 동작한다. 넘기면 그 주기를 확정하는데, 배포의 쓰기 실행이 중간에 끊긴 상환 계획에 필요한 동작이다. 접수 중인 주기보다 뒤에 있는 주기는 디코딩된 WindowAlreadyOpen이 아니라 409를 반환한다. 🔴 pools.next_funding_date와 그 출처 컬럼 둘은 대상이 접수 중인 주기일 때만 미러링된다. 그 컬럼들은 한 주기를 서술하는데, 4번 주기 쓰기로 채우면 풀이 4번 주기를 다음 펀딩일로 알리고 창이 열리기도 전에 확정으로 표시하게 된다. 그래서 응답에 mirrored와 함께 mirror_scope(accepting_cycle / future_cycle_no_pool_row)가 담긴다. 그러지 않으면 mirrored: false가 "미러링이 실패했다"와 "미러링할 게 없었다"를 동시에 뜻하게 되고, 앞의 경우로 재시도하는 호출자가 이미 동의한 체인에 두 번째 트랜잭션을 보내게 된다
GET/pools/{id}/repayment-cycles만기 후 상환 계획의 주기당 한 행이다(v3-133). { pool_id, term, funding_anchor_date, epoch_duration_days, cycles: [{ epoch_index, funding_date, settled_at, fill_ratio, settled_nav }], unwritten_cycles }를 반환한다. epoch-summary가 커서가 있는 주기에 대한 풀당 한 행인데 계획은 전체를 보여 줘야 하므로 존재한다. 🔴 의도적으로 체인을 읽지 않는다. 체인 읽기는 모든 주기에 답을 주면서, 호출자가 알고 싶어 한 유일한 구분(funding_date: null아무도 이걸 설정하지 않았다를 뜻하는 것)을 지운다. 원장에 있든 없든 1..term 행을 반환하고(아무것도 기록되지 않은 주기가 오히려 보여 줄 가치가 있는 경우다), term을 넘는 인덱스는 버리며, term: null계획이 아님을 뜻하고 기록이 아직 없는 계획과 구분된다. 원장 읽기 실패는 []가 아니라 500이다. 빈 목록은 "어느 주기에도 날짜가 없다"로 읽히고 확인 카드가 전부를 트랜잭션 하나씩 써 주겠다고 제안하게 되기 때문이다. unwritten_cycles는 펀딩일 리마인더가 쓰는 같은 함수에서 온다. pools.next_funding_date는 읽지 않는다. 그 컬럼은 접수 중인 주기를 서술하므로 4번 주기 질문에 2번 주기 날짜로 답하게 된다. ADMIN / SUPER_ADMIN / OPERATOR / FUND_MANAGER가 쓰고 펀드 범위다
POST/pools/{id}/impairmentimpairment 제안·취소(7일 타임락, IMPAIRED lifecycle)
POST/pools/{id}/lifecycle수동 발행 전이(DRAFT → UPCOMING / ACTIVE)다. 다른 전이는 전용 엔드포인트를 쓴다. Retry Deploy 경로이기도 하다. 이미 발행됐고 deploy_status = DEPLOY_FAILED인 풀이 기존 상태를 유지한 채 DEPLOYING으로 다시 들어간다(pools.post.lifecycle.ts:62-78). ⚠️ lifecycle_status = ACTIVEdeploy_status = DEPLOY_FAILED의 조합은 고쳐야 할 어긋남이 아니라 의도된 짝이다. lifecycle은 운영자의 발행 의도를 기록하고 deploy_status는 체인이 따라잡았는지를 기록하며, 재시도는 원래 목표에 도달하려면 앞의 것이 보존돼야 한다. 실패 시 lifecycle을 DRAFT로 되돌리면 신규 발행 분기를 타게 되고 선택했던 목표를 잃는다. 이 조합은 격리돼 있다. pool_address는 배포에 성공해야만 기록되므로 모든 온체인 쓰기 경로가 거부하고, 투자자에게는 API(pools.get.list.ts:143,272)와 클라이언트 양쪽에서 걸러지며, 어드민 칩은 lifecycle 칩보다 우선하는 Deploy failed를 보여 준다(pool-badges.tsx:103).
POST/pools/{id}/preview-token15분짜리 풀 범위 토큰을 발급해 내부 role(운영자 / 자기 펀드의 FM / 어드민)이 실제 투자자 PDP로 DRAFT 풀을 볼 수 있게 한다(GET /pools/{id}?preview=<token>)
POST/pools/{id}/pause입금 soft pause와 해제다. 온체인 pause()/unpause()에 배선돼 있다. ⚠️ pause하려면 lifecycle_status = ACTIVE이고 동결되지 않아야 하므로 DRAFT 풀은 절대 pause할 수 없다(400). 더 높은 상태는 이미 입금을 막고 있고, 그 아래에 pause를 겹치면 A1이 없앤 다중 상태 혼동이 되살아난다. unpause는 언제나 허용해 풀이 복구될 수 있게 한다. 따라서 DB만 쓰는 경로는 "DRAFT"가 아니라 ACTIVE이면서 아직 배포되지 않은 경우다(deploy_statusDEPLOYING이거나 DEPLOY_FAILED). 호출할 컨트랙트가 없으므로 플래그가 DB에만 산다. 그게 문제가 되는 구간이 DEPLOY_FAILED → 재시도 창이다. 거기서 건 pause는 온체인에 반영되지 않고, 재시도에 성공한 뒤 그것을 다시 적용하는 것이 아무것도 없다(공백 G2)
POST/pools/{id}/wind-downwind-down 제안·실행(종착, pro-rata 상환만)
GET/pools/{poolId}/nav-history풀의 NAV 이력 조회

상환 총액의 UNKNOWN 원인

GET /pools/{id}/repayment-cyclestotal_due_at_maturity가 객체일 때 unknown_reason을 포함한다. 원금 읽기 실패는 CHAIN_READ_FAILED, 음수이거나 유한하지 않은 원금은 INVALID_PRINCIPAL, 음수이거나 유한하지 않은 고정 이율은 INVALID_ACCRUAL_RATE, 누락되거나 유한하지 않거나 0 이하인 고정 이자 계산 기간은 INVALID_MATURITY_DAYS다. 이때 overdue_statusUNKNOWN이고 금액 필드는 모두 null이다. 처음 실패한 검증의 원인 하나를 반환하며 추가 체인 조회는 없다.

그 외에는 unknown_reason이 null이다. 고정 이자가 없는 TARGET 풀, 환율 부재로 환산하지 못한 연체금, 실제 금액 0도 여기에 해당한다. 만기 전이나 투자자 조회처럼 총액을 의도적으로 제공하지 않는 경우에는 total_due_at_maturity 자체가 null로 유지된다. 구버전 API에는 unknown_reason이 없을 수 있으므로 관리 화면은 기존 UNKNOWN 응답에 일반적인 데이터 확인 안내를 유지한다.

🃏 GET /pools의 카드 콘텐츠 필드

목록 응답에는 숫자 요약뿐 아니라 투자자 브라우즈 카드가 렌더하는 필드가 담긴다. 상환 상태 필드와 함께 tagline, term, eligibility_mode, is_showcase, funds.verified가 들어간다. 이들이 목록 계약에 포함된 이유는, 컬럼이 없을 때 카드가 "알 수 없음"으로 렌더되지 않고 잘못된 기본값으로 렌더되기 때문이다(verified 체크가 사라지고, 전문투자자 칩이 사라지며, 카드 소개 문구가 조용히 description으로 폴백하는데 상세 페이지는 tagline을 보여 준다). GET /pools/{id}는 추가로 apy_disclosure를 반환하고 PDP가 목표 APY 아래에 렌더한다.

❄️ 풀 응답의 동결 필드 (v3-28)

GET /poolsGET /pools/{id}is_emergency_frozen, freeze_started_at, 그리고 freeze_started_at과 온체인 상수에서 파생한 편의 필드 둘을 노출한다(추가 읽기가 없다). freeze_exit_window_ends_at(시작 + 72시간. 가치 유출이 자동으로 풀리는 시점)과 freeze_auto_expires_at(시작 + 7일. 동결 전체가 만료되는 시점)이다. 풀이 현재 동결 상태가 아니면(freeze_started_atnull) 두 파생 필드도 null이다. 마이그레이션 0023(pools.freeze_started_at)이 뒷받침한다.

Pool Updates (4)

메서드엔드포인트설명
PATCH/pool-updates/{id}풀 업데이트·공지 수정
DELETE/pool-updates/{id}풀 업데이트·공지 삭제
GET/pools/{id}/updates풀의 업데이트·공지 목록
POST/pools/{id}/updates풀 업데이트·공지 작성

Pool Categories (3)

메서드엔드포인트설명
GET/pool-categories풀 카테고리 목록
POST/pool-categories풀 카테고리 생성
DELETE/pool-categories/{id}풀 카테고리 삭제

Pool Data (5)

메서드엔드포인트설명
GET/pool-documents풀 문서 목록
GET/pool-documents/{id}ID로 풀 문서 조회
GET/pools/{poolId}/tvl-history풀의 TVL 이력 조회
GET/underlying-assets기초자산 목록
GET/underlying-assets/{id}ID로 기초자산 상세 조회

Deposits (3)

메서드엔드포인트설명
GET/deposits입금 목록이다. 필터는 status, pool_id, user_id, q(investor_address / tx_hash / 풀 이름 / 투자자 이름)다. status는 쉼표로 구분한 목록을 받는다(status=PENDING,PROCESSING. 둘 다 "Pending"으로 표시하는 어드민 Pending 탭이 쓴다). deposit_status enum 밖의 값은 400이다. limit/offsetX-Total-Count로 페이지네이션한다.
POST/deposits새 입금을 생성한다(온체인 atomic이다. USDC가 Pool로 들어가고 LP가 민팅된다. 에스크로도 receipt도 없다, v3-03/v3-11)
GET/deposits/{id}ID로 입금 상세 조회

Redemption Requests (10)

상환은 비수탁이다. FM이 fundRedemption에 클라이언트 서명하고 지급은 온체인에서 자동 실행된다. 07-redemptionv3-53 참조.

메서드엔드포인트설명
GET/redemption-requests상환 요청 목록(필터 지원)이다. 회차 풀epoch_id, lp_filled, settled_nav, 체결 %, 청구 가능액, 다음 정산 시각도 함께 노출한다. 필터는 status, pool_id, user_id, funding, epoch, 그리고 EPOCH 탭의 큐별 무한 스크롤을 위한(R1) epoch_open=true(상태 QUEUED/PARTIALLY_FILLED)와 funding_status(예: HELD)다. limit/offsetX-Total-Count로 페이지네이션한다.
GET/redemption-requests/epoch-summary회차 풀의 풀별 수요 집계다(R1). 열린 수요 전체(QUEUED/PARTIALLY_FILLED)에 대해 서버에서 SUM/COUNT한다(SQL 함수 redemption_epoch_summary, 마이그레이션 0084). 이월 수요(LP와 USD, HELD 제외), 열림·보류·이월 건수, 다음 정산과 직전 정산(체결 %, NAV)을 담는다. 어드민 Redemptions의 EPOCH 탭 카드를 구동하고, 개별 큐 행은 목록 엔드포인트로 따로 페이지네이션된다. 회차 풀당 항목 하나이고(수요가 0인 풀도 포함) FM 범위가 적용된다. 상환 계획을 위해 필드 셋이 추가됐다(v3-133). maturityDate(배포가 설정하고 체인에서 미러링한 날짜다. ⚠️ created_at + maturity_days와 바꿔 쓸 수 없다. 그건 위저드의 배포 전 추정치다), unwrittenCycles(아직 날짜를 유도 중인 주기 id를 오름차순으로 담는다. 계획이 없는 풀은 []이다), hasUnwrittenCycles다. 이름은 문자 그대로 쓰이지 않았다는 뜻이지 "미확정"이나 "대기 중"이 아니다. 리마인더가 게이트로 삼는 것과 같은 함수에서 오므로 소비자가 해석할 여지가 없다.
GET/redemption-requests/counts어드민 Redemptions 화면의 탭별 건수다. 목록 엔드포인트와 같은 읽기 범위와 필터를 따른다. v3-99의 종료 사유 분해도 반환한다. closures.{UNFUNDED, COMPLIANCE, OTHER, INVESTOR_CANCELLED, UNCLASSIFIED}와 그 합으로 파생되는 CLOSED다. REJECTED는 결과 넷을 담는 한 상태라 "거부" 수치 하나는 오해를 부른다. 투자자가 자기 요청을 철회한 것이 플랫폼이 거부한 것으로 읽힌다. UNCLASSIFIED는 분리 이전의 레거시 행과, 인덱서가 미러링한 온체인 직접 거부의 NULL이다.
POST/redemption-requests상환 요청을 제출한다. 즉시는 NAV 스냅샷을 고정하고, 회차는 현재 회차에 등록한다(스냅샷 없음). epoch_id가 기록되고 상태는 QUEUED다.
GET/redemption-requests/{id}ID로 상환 요청 상세 조회
POST/redemption-requests/{id}/approve즉시는 어드민 승인이다(reserve 확인 후 지급). 회차는 요청별 승인이 아니라 holdRequest/releaseRequest(이상치 보류)를 반영한다.
POST/redemption-requests/{id}/cancel투자자가 진행 중인 자기 회차 요청을 취소한다(QUEUED/PARTIALLY_FILLED). 🔴 즉시 경로의 취소는 MVP 범위 밖이다(2026-08-27 결정, 아직 반영 안 됨, v3-149. 온체인 분기는 여전히 있고 REQUESTEDPENDING_RESERVE를 모두 다룬다, v3-76). 온체인 cancelRedemption은 투자자만 호출할 수 있고(지갑 호출), 이 엔드포인트는 확정된 취소를 미러링하며 DB 상태를 REJECTED로 뭉친다(failure_type = INVESTOR_CANCELLED. 결정 A에 따라 CANCELLED는 저장되는 enum 값이 아니다).
POST/redemption-requests/{id}/claim회차 풀에서 온체인 claimRedemption(RedemptionClaimed 이벤트)을 미러링한다. lp_filled / settled_nav / 지급액 / 상태를 정산하고 잔량을 이월한다. 온체인에서는 허가가 필요 없다(대리 청구, 목적지 고정).
POST/redemption-requests/{id}/claim-fallback🔴 2026-08-31 제거됐다. 온체인 claimRedemptionFallback을 미러링했는데, 그 함수가 끌어 쓰던 reserve와 함께 사라졌다. 오류를 내게 두지 않고 라우트를 제거했다. revert하는 핸들러에 닿는 경로는 홀더에게 출구가 고장 났다고 가르치고, 404가 진실이기 때문이다.
POST/redemption-requests/{id}/record-funding유일한 펀딩 경로다. FM의 온체인 fundRedemption 트랜잭션을 기록한다(v3-53). 즉시 풀에서는 영수증에 이미 RedemptionCompleted가 있으면 그 자리에서 정산하고, 아니면 인덱서가 정산한다. ⚠️ 서버 키를 쓰던 POST /redemption-requests/{id}/fund2026-08-13에 제거됐다. fundRedemption은 파트너의 role인 onlyRole(YIELD_DEPOSITOR_ROLE)이고, 플랫폼은 v3-53이 수탁형 개발 임시 조치라고 부른 배포 시점 자가 부여로만 그것을 만족시킬 수 있었다.
POST/redemption-requests/{id}/reject**Return position**이다(v3-99). ADMIN / SUPER_ADMIN 전용이고 FM은 제외된다. REQUESTEDPENDING_RESERVE 요청을 닫는다. 에스크로된 LP가 투자자에게 돌아가고 파트너 자금은 fund_wallet으로 돌아간다. 본문에 failure_type(UNFUNDED | COMPLIANCE | OTHER. 검증된다. INVESTOR_CANCELLED는 거부되고 /cancel이 담당한다)과 비어 있지 않은 reason둘 다 필요하다. 상태는 REJECTED가 되고 분류가 투자자 안내 문구를 고른다. 경로와 enum은 옛 reject/REJECTED 이름을 유지한다. 라벨 하나 바꾸자고 둘 다 이름을 바꾸는 것은 마이그레이션 비용에 값하지 않는다.
GET/redemption-requests/{id}/exit-gateADMIN / SUPER_ADMIN 전용이다. 참고용 읽기 전용이다. 이 요청의 홀더에게 아직 지급할 수 있는가를 답한다. 온체인 요청 튜플과 canRedeem호출 시점에 읽는다(한 시간 낡았을 수 있는 매시간 sweep이 아니다). 그래야 Return position 다이얼로그가 COMPLIANCE를 미리 고를 수 있다. { known, blocked, kycState, suggestedReason }를 반환한다. 회차 풀이거나 온체인 id가 없는 요청이거나 체인을 읽을 수 없으면 **known: false**이고, 호출자는 이를 "막히지 않았다"로 읽지 말고 자기 기본값을 유지해야 한다. 아무것도 바꾸지 않는다.

⚠️ 제거됨

POST /redemption-requests/{id}/fm-accept(v3-34. FM 사전 확인 단계가 없다)와 POST /redemption-requests/{id}/complete(v3-53. 인덱서가 RedemptionCompleted로 정산하므로 /complete 엔드포인트는 그것과 경쟁하게 된다)는 v3 플로우에 없다. 레거시 멀티시그 공동서명과 execute-transfer 엔드포인트도 마찬가지다.

Portfolio Positions (2)

메서드엔드포인트설명
GET/portfolio-positions현재 유저의 포트폴리오 포지션 목록
GET/portfolio-positions/{id}ID로 포트폴리오 포지션 상세 조회

Yield Distributions (5)

메서드엔드포인트설명
GET/yield-distributions수익 분배 목록
POST/yield-distributions풀의 수익 분배를 생성한다(기간, total_amount)
GET/yield-distributions/me현재 투자자의 수익 분배 조회
GET/yield-distributions/{id}ID로 수익 분배 상세 조회
POST/yield-distributions/{id}/distributeFM의 온체인 depositYield를 검증한 뒤 서버 키로 settleYield를 실행한다(v3-53, v3-102)
GET/yield-distributions/preview입력한 총액을 기록하지 않고 가격만 매긴다. ?pool_id=&gross_amount=(v3-104)
GET/yield-distributions/fund-summary펀드별로 빚진 금액을 합산하고 각 펀드의 FM 통지 상태를 함께 준다(v3-121)
GET/yield-distributions/{id}/investors분배 하나의 투자자별 배분과 그 풀의 미청구 잔액(v3-121)

에스크로 발생액 읽기 실패

DUE 행은 escrow_accrual_totalescrow_accrual_unknown_reason을 반환한다. 전체 읽기에 성공하면 원인은 null이다. 실제 금액이 0이거나 열린 요청이 없는 경우도 포함한다. 요청의 온체인 ID가 없거나 일부만 합산할 수 있으면 PARTIAL_READ, 체인 조회의 시간 초과·요청 제한·기타 실패는 각각 CHAIN_READ_TIMEOUT·CHAIN_READ_RATE_LIMITED·CHAIN_READ_FAILED다. 열린 요청 목록 조회 실패는 REQUESTS_READ_FAILED, 조회 대상이 아니거나 조회 조건이 없는 풀은 NOT_AVAILABLE이다. 고정 채무가 없는 TARGET 풀도 조회 대상이 아니다. 값을 알 수 없으면 금액은 null이며 부분합을 반환하지 않는다.

FIXED 도래 행의 전체 채무를 알 수 없으면 관리 화면은 합계에 , 실패 사유, Retry를 표시한다. 기표된 금액은 아래 Booked 내역에 유지하고 합계를 대신하지 않는다. 읽기가 성공할 때까지 개별·일괄 입금을 막으며, 홀더 유무를 확인할 수 없는 상태를 ‘홀더 없음’으로 표시하지 않는다. 재시도는 화면의 큐와 입금 모달·일괄 검토가 사용하는 전체 큐를 함께 갱신하며 체인·DB에 쓰지 않는다. TARGET 입금과 도래 큐에 없는 풀의 기존 흐름은 유지한다.

GET /yield-distributions?include_due=true: 읽기 모델의 도래 기간 (v3-104, 2026-08-03 반영)

도래했지만 아직 행이 없는 기간은 pools.next_yield_dueyield_overdue로만 존재한다. 예정 기간 테이블이 없다. include_due=true를 주면 목록이 기록된 행과 합성된 도래 행을 함께 반환하고 source: 'RECORD' | 'DUE'로 구분한다. 그래서 어드민 Yield의 "Pending" 탭이 브라우저에서 GET /pools를 조인하는 대신 다른 네 탭과 같은 엔드포인트를 읽는다.

기본값은 false다. 생략하면 응답이 이전과 바이트 단위로 동일하다(투자자 /me, CSV 내보내기, 다른 탭은 영향이 없다. 추가 필드는 플래그가 켜졌을 때만 붙는다).

status와의 상호작용: 생략하면 기록 + 도래 행이고, status=PENDING이면 도래 행만이며(마이그레이션 0113 이후 분배에는 불가능한 상태다), 다른 status면 기록만이고 플래그는 무시된다.

도래 행은 pool_id(키다. idnull이고 원장상 신원이 없다), pool_name, 백엔드가 정한 period 라벨, due_at, is_overdue, overdue_days, missed_periods(빚진 주기 수다. sweep이 놓친 날짜를 앞으로 굴리지 않으므로 4개월 밀린 풀은 4라고 말하는 행 하나다), 그리고 estimated_gross / estimated_net / estimate_basis: 'APY_ON_TVL'을 담는다. 추정치는 실제 정산이 쓰는 것과 같은 computeYieldFees로 서버에서 계산하고 놓친 기간 전부를 포함한다. 풀에 TVL이 없거나 추정할 APY가 없으면 null이고, 그때는 $0이 아니라 로 렌더해야 한다. 원장 컬럼(total_amount, status, tx_hash 등)은 null이다.

⚠️ v3-120 이후 창이 도래일 7일 전까지 열리므로 도래 행이 반드시 연체인 것은 아니다. "이미 빚진 것"을 뜻하는 값은 전부 **is_overdue**로 필터링해야 한다. overdue_days는 0에서 클램프되고 다가오는 기간과 오늘 도래한 기간 둘 다 0을 보고하므로 대신 쓸 수 없다.

정렬에 **due_at**이 추가됐다. 기록은 distributed_at ?? created_at으로, 도래 행은 next_yield_due로 하나의 순서에 정렬한다. q는 도래 행에서 풀 이름과 매칭한다(그 행에는 tx_hash가 없다). X-Total-Count는 두 종류를 모두 센다. FM 펀드 범위 제한은 기록과 똑같이 도래 행에도 적용된다.

GET /yield-distributions/fund-summary: 어느 펀드를 쫓아야 하나 (v3-121, 2026-08-07)

미결 기간을 가진 펀드당 한 행이고 나쁜 순서대로 정렬한다. fund_id · fund_name · fm_count · pool_count · owed_estimated · behind_pools · next_due_at · next_due_pool_name · notify_state · last_escalated_at다. Pending 큐가 읽는 것과 같은 fetchYieldDueRows 위에 만들어서, By-Fund 합계와 화면의 노출 카드가 어긋날 수 없다. 브라우저에서 그룹핑하면 정확히 그렇게 어긋난다. owed_estimatedbehind_poolsis_overdue 행만 센다.

fund_id가 없는 풀(레거시다. v3-26 이후 생성에는 필수다)은 버려지지 않고 fund_id: null 버킷에 들어간다. 그래야 펀드별 합계가 여전히 노출 수치와 맞는다.

notify_state는 저장값이 아니라 파생값이고, 세 값은 서로 바꿔 쓸 수 없다.

조치
UNREACHABLE펀드에 활성 FM이 없거나 에스컬레이션이 아무에게도 닿지 않았다FM을 배정한다. 다시 알려도 아무 일도 일어나지 않는다
NOTIFIED에스컬레이션이 전달됐다. 최소 한 사람에게 도달했다펀드 지갑을 기다린다
NONE도달 가능한데 아직 아무것도 보내지 않았다에스컬레이션한다

⚠️ yield_distributions.fm_notified_at은 이 용도의 필드가 아니다. run-distribution.ts가 분배가 실제로 실행될 때 찍으므로, 이 표가 다루는 미지급 기간에는 정확히 null이다. 에스컬레이션(POST /pools/{id}/escalate-yield)은 어떤 컬럼도 쓰지 않는다. 남는 기록은 그 notification_events 행이다(subject_type='pool'). 그리고 notification_events만으로는 부족하다. dispatch.ts가 대상자를 해석하기 전에 이벤트를 insert하므로, FM이 없는 펀드를 에스컬레이션해도 이벤트 행은 남는다. UNREACHABLENOTIFIED를 갈라 주는 것은 notifications 조인이다. 그것이 없으면 운영자가 전달된 적 없는 서명 요청을 기다리게 된다.

GET /yield-distributions/{id}/investors: 누가 받았나 (v3-121, 2026-08-07)

{ distribution_id, pool_id, net_total, investor_count, truncated, investors[], pool_unclaimed_total, pool_distributed_total }를 반환한다. 투자자 행은 { user_id, name, address, amount, share_percentage }이고 금액순으로 정렬되며 50건에서 잘리고 그 이상이면 truncated: true다. 합계는 모든 행에 대해 계산하므로 잘린 목록에서도 지분 막대가 100%로 닫힌다. share_percentage는 저장된 컬럼을 읽는 대신 이번 회차의 합계로 다시 계산한다. 레거시 행에서는 그 컬럼이 null이기 때문이다.

PII: viewInvestor(09-rbac 문서)가 적용되므로 FM은 이름과 지갑을 보고 이메일은 절대 못 본다. 펀드 범위 제한은 기록 목록과 같다. FM이 자기 펀드 밖의 분배를 읽으면 403이다.

⚠️ pool_unclaimed_total은 풀 단위 누계이지 회차별이 아니다. 특정 회차의 몫이 청구됐는지를 기록하는 것이 없다. yield_distribution_investors.claimed_at이 존재하지만 라이터가 없다. 실제로 추적되는 것은 portfolio_positions.accrued_yield이고, 정산 시 increment_accrued_yield가 크레딧하고 청구나 재투자에서 차감한다. 그래서 풀 전체에 대한 그 합이 모든 회차를 아우르는 실제 미청구 잔액이다. 영수증도 그렇게 라벨을 단다. 이를 "이번 회차 미청구"로 제시하면 아무것도 뒷받침하지 않는 숫자가 된다.

yield_distribution_investors.investor_name.investor_address도 읽지 말 것. 컬럼은 있지만 run-distribution.ts가 둘 다 쓰지 않는다. 이름은 users 조인에서 온다.

GET /yield-distributions/preview: 운영자가 입력한 총액에 가격 매기기 (v3-104, 2026-08-03)

?pool_id=<uuid>&gross_amount=<number>{ gross_amount, period_days, fee_configured, treasury_fee, pool_mgmt_fee, fee_total, net_amount, legs: { platform_take, spc_mgmt, pool_mgmt, perf }, clamped }다. 읽기 전용이다. DB 쓰기도 온체인 호출도 없다. 엔드포인트 뒤의 computeYieldFees이므로, 기록 다이얼로그가 미리 보여 주는 값이 곧 생성 엔드포인트가 적용할 값이다.

왜 서버 쪽이어야 하나. 도래 행의 추정치는 백엔드가 도래를 아는 기간만 다루고, net은 gross에 비례하지 않는다. 관리 수수료 항목들은 AUM 기준(aum × bps/10000 × 일수/365)이라 gross와 함께 움직이지 않는다. TVL 710만인 풀에서 SPC 0.5%와 pool 0.75%/연으로 측정하면 net/gross가 총액 $100k에서 0.706, $20k에서 0.410이다. 그러니 클라이언트에서 추정치를 비례 조정하는 것은 틀렸고, $5k에서는 수수료가 총액을 넘어 clamped가 true가 되고 net이 0으로 바닥친다. 브라우저에서 유도하면 프론트엔드가 어떤 항목이 비례하는지를 다시 인코딩하게 되는데, 그게 v3-104가 없앤 중복이다.

권한은 기록과 같다. requirePagePermission('yield')requireFundAccess이므로 FM은 자기 펀드 밖의 풀에 가격을 매길 수 없다(응답이 그 풀의 수수료 설정과 TVL에서 유도되기 때문이다). lifecycle_status / deleted_at / deploy_status로는 의도적으로 게이팅하지 않는다. 기록이 허용된다고 주장하는 게 아니라 숫자를 계산할 뿐이고, POST /yield-distributions가 여전히 그런 풀을 거부한다.

⚠️ 생성 핸들러의 기간 없는 경로를 그대로 반영한다(yieldPeriodDays로 풀 주기에서 periodDays를 얻는다). 다이얼로그가 보내는 것이 그것이다. 다이얼로그에 period_start/period_end 입력이 생기면 두 핸들러가 모두 그것을 받아야 한다. 아니면 미리보기가 거짓말을 시작한다.

POST /yield-distributions에는 deposit_tx_hash가 필요하다 (v3-104, 2026-08-03)

레거시 통합 경로(deposit_tx_hash 없이 서버 키가 depositYield에 서명하고 같은 호출에서 정산하던 것)는 제거됐다. PENDING을 insert한 유일한 라이터였고, 거기 남은 행은 고칠 수 없는 크래시 고아였다. 이제 FM이 클라이언트 서명한 입금 tx가 없으면 400이고, 행을 PROCESSING으로 만들어 POST /{id}/distribute가 마무리하게 한다. body.deposit은 없어졌다.

Yield Claims (3)

메서드엔드포인트설명
GET/yield-claims수익 청구 목록
POST/yield-claims수익 청구 생성(투자자가 발생 수익을 청구한다)
GET/yield-claims/{id}ID로 수익 청구 상세 조회

Yield Reinvest (1)

메서드엔드포인트설명
POST/yield/reinvest청구한 수익을 풀에 다시 넣어 새 LP를 민팅한다. ⚠️ MVP에서는 재투자를 어느 화면에도 노출하지 않는다(2026-08-27, 아직 반영 안 됨, v3-151). 투자자 앱에서 CTA가 빠지고 어드민에서 토글이 빠진다. 엔드포인트와 온체인 reinvest()는 둘 다 남는다. 경로를 닫는 것은 allow_rollover의 기본값 false이고, 그 때문에 호출이 RolloverDisabled로 revert한다
메서드엔드포인트설명
GET/nav-changesNAV 변경 제안 목록
POST/nav-changesNAV 변경을 제안한다(하락이면 타임락이 걸린다). 본문은 cumulative_loss(권장. 풀의 버퍼를 거쳐 가격이 유도된다, 0122)와 new_nav(직접 지정) 중 하나를 받고 둘 다는 안 된다. dry_run: true면 쓰지 않고 계산과 시뮬레이션만 한다
GET/nav-changes/{id}ID로 NAV 변경 상세 조회
POST/nav-changes/{id}/activate타임락 이후 제안된 NAV 변경을 적용한다
POST/nav-changes/{id}/cancel대기 중인 NAV 변경 제안을 취소한다

Tranche Writedown (1)

메서드엔드포인트설명
POST/tranche-writedown트랜치 단위 손실 워터폴과 NAV 상각을 적용한다

Funds (5)

메서드엔드포인트설명
GET/funds펀드 목록이다. 포함되는 fund_members는 ACTIVE만이고 포함되는 pools는 소프트 삭제된 행을 뺀다(예전에는 둘 다 부풀려져 있었다). limit/offset으로 페이지네이션한다(기본 50, 최대 200)
POST/funds새 펀드를 만든다. name은 비어 있으면 안 되고 primary_contact_email은 형식을 검사한다. verifiednotification_health받지 않는다(신뢰 신호이거나 유지하는 것이 없는 컬럼이다)
GET/funds/{id}ID로 펀드 상세를 조회한다. linked_pool_total이 추가된다. 소프트 삭제된 것을 포함해 그 펀드를 가리키는 풀 수이고, 곧 DELETE를 막는 값이다
PATCH/funds/{id}펀드 정보를 수정한다. name / 이메일 / verified / notification_health 규칙은 POST와 같다
DELETE/funds/{id}펀드 삭제

Fund Members (5)

메서드엔드포인트설명
GET/fund-members펀드 구성원 목록
POST/fund-members펀드 구성원 추가
GET/fund-members/{id}ID로 펀드 구성원 상세 조회
PATCH/fund-members/{id}펀드 구성원 수정
DELETE/fund-members/{id}펀드 구성원 삭제

Fund Invites (5)

메서드엔드포인트설명
GET/fund-invites펀드 구성원 초대 목록(펀드별·상태별)
POST/fund-invites펀드 구성원 초대 생성
POST/fund-invites/accept코드로 펀드 초대 수락
GET/fund-invites/code/{code}코드로 펀드 초대 조회
DELETE/fund-invites/{id}펀드 초대 철회

Admin Users (7)

메서드엔드포인트설명
GET/admin-users어드민 유저 목록
POST/admin-users어드민 유저 생성
POST/admin-users/invite이메일로 어드민 유저 초대
GET/admin-users/{id}ID로 어드민 유저 상세 조회
PATCH/admin-users/{id}어드민 유저 수정
DELETE/admin-users/{id}어드민 유저 삭제
PATCH/admin-users/{id}/permissions운영자의 페이지 단위 권한 수정(admin_user_permissions)

Admin Wallets (3)

FM·어드민 서명 지갑 연결이다(v3-53/v3-54, B3). SIWE 연결은 세션을 만들지 않는다(JWT 없음). 권한의 기준은 온체인에 남고(msg.sender == pool.fund_wallet), 이것들은 표시·감사·서명 전 캐시다.

메서드엔드포인트설명
GET/admin/me/wallets호출자 본인의 SIWE 연결 지갑 목록(인증 범위로 제한되고 타인 조회는 없다)
POST/admin/wallet/nonce서명 지갑 소유 증명을 위한 SIWE nonce 요청(연결 전용, JWT 없음)
POST/admin/wallet/verifySIWE 서명을 검증하고 지갑을 호출자 계정에 연결한다(admin_user_wallets)

Admin Console (10)

어드민·운영자 콘솔이다. 인앱 알림, 플랫폼 설정, 어드민 본인의 프로필과 알림 환경설정을 다룬다.

메서드엔드포인트설명
PATCH/admin/me호출자 본인의 표시 프로필을 수정한다(nameavatar_url만). 모든 어드민 role에 열려 있다. 대상이 언제나 auth.sub이고 role·이메일·지갑은 받지 않으므로 권한이 실리지 않는다. 계정 관리(role, 이메일, 지갑)는 SUPER_ADMIN/ADMIN 전용인 PATCH /admin-users/{id}에 남는다
GET/admin/me/notification-preferences호출자 본인의 어드민 알림 토글과, 그것을 렌더하는 어드민 이벤트 카탈로그({ events, preferences })
PUT/admin/me/notification-preferences호출자의 어드민 알림 토글을 수정한다. category는 알림의 event_key이고, 카탈로그에서 optional인 이벤트만 받는다(critical은 임의로 바꾸지 않고 거부한다)
GET/admin/signer-balances🔴 SUPER_ADMIN 전용이다. KMS 서명 키 넷(admin/minter/oracle/pauser)이 지원 체인별로 보유한 가스와, 각 키가 가진 온체인 role을 반환한다. 읽기 전용이고 의도적으로 ROUTE_SIGNER_ROLES에 없다. 잔액을 읽을 뿐이므로 서명하지 않는 공용 실행 role을 받는다. 가스가 없는 키도 role은 그대로 갖고 트랜잭션을 만들기 때문에, 실패가 작업 도중 revert로 도착한다. 그것을 먼저 볼 수 있게 하는 화면이다. balance: null은 읽기 실패이지 0이 아니다
GET/admin/notifications어드민의 인앱 알림 목록
PATCH/admin/notifications/read-all어드민 알림 전체를 읽음 처리
GET/admin/notifications/unread-count어드민 알림 미읽음 수
PATCH/admin/notifications/{id}/read어드민 알림 하나를 읽음 처리
GET/admin/settings플랫폼 설정 조회
PUT/admin/settings플랫폼 설정 수정

Dashboard (1)

메서드엔드포인트설명
GET/dashboard/stats대시보드 통계 조회(TVL, 입금, 상환, 유저)

Activity (3)

메서드엔드포인트설명
GET/activity-events활동 이벤트 목록
GET/activity-events/counts필터 배지를 위해 심각도별 활동 이벤트 수를 센다
GET/activity-events/export활동 이벤트를 CSV로 내보낸다

Config (2)

메서드엔드포인트설명
GET/config/chains지원 체인 목록(chain_id, RPC·익스플로러 메타데이터)
GET/config/stablecoins지원 스테이블코인과 그 컨트랙트 주소 목록

Notification Logs (3)

메서드엔드포인트설명
GET/notification-logs알림 로그 목록
GET/email-suppressionsSES가 하드 바운스나 불만으로 보고한 주소들이다(0110). OPERATOR는 notifications 페이지 권한이 필요하다
POST/email-suppressions주소를 손으로 차단한다(reason='MANUAL'). ADMIN/SUPER_ADMIN 전용이고 note가 필수이며 감사된다(EMAIL_SUPPRESSION_ADD). 이미 차단돼 있으면 409다
DELETE/email-suppressions/{email}차단을 해제해 다시 메일을 보낼 수 있게 한다. ADMIN/SUPER_ADMIN 전용이고 감사된다(EMAIL_SUPPRESSION_REMOVE)
POST/notification-logs/{id}/escalate알림을 에스컬레이션한다
POST/notification-logs/{id}/resend실패한 알림을 재발송한다

Investor Notifications (4)

투자자 인앱 알림 센터와 투자자 본인의 알림 환경설정이다.

메서드엔드포인트설명
GET/notifications투자자의 인앱 알림 목록
PATCH/notifications/read-all투자자 알림 전체를 읽음 처리
GET/notifications/unread-count투자자 알림 미읽음 수
PATCH/notifications/{id}/read투자자 알림 하나를 읽음 처리

스케줄러와 워커

HTTP가 아닌 Lambda다(EventBridge 크론, SQS, 비동기 호출). 위의 엔드포인트 수에는 포함되지 않는다.

유형이름설명
SCHEDpools.scheduler.lifecycle풀 lifecycle 전이(UPCOMING→ACTIVE, ACTIVE→MATURED). 매시간
SCHEDpools.scheduler.tvl-snapshot매일 TVL 스냅샷 sweep이다. pool_tvl_history의 유일한 라이터다
SCHEDpools.scheduler.yield-dueFM·운영자에게 보내는 수익 도래 알림(수동 트리거 모델)
SCHEDredemption-requests.scheduler.epoch-execute회차 정산 sweep(매시간)이다. 회차 풀마다 executeEpoch를 실행한다(v3-26)
SCHEDredemption-requests.scheduler.partner-funding파트너 펀딩 리마인더(매시간, v3-26 §A5.1)
SCHEDyield.scheduler.reconcile수익 정합기(매시간)다. 배포된 풀마다 분배를 대조한다(v3-51)
SCHEDkyc.scheduler.reconcile-sweepSumSub webhook 유실 정합(5분 주기)
SCHEDkyc.scheduler.reverify-sweep재KYC 리마인더 창(v3-19)
SCHEDreport.scheduler.fetch권한이 있는 모든 엔티티에 대해 파트너 리포트 API를 호출하고(whoamifundIdsAuthorized / masterIdsAuthorized) 관측값을 report_events에 append하며, 호출마다 성공과 실패를 report_fetches에 기록한다. 호출 중 하나라도 실패하면 예외를 던져서, 피드가 조용해지는 대신 함수별 알람이 발동한다
SCHEDreport.scheduler.derive로그에서 report_pool_latest를 다시 만든 뒤 NAV 가격 sweep을 돌린다. fetch보다 한 시간 뒤에 돌아서 한 실행이 그 실행이 append한 것을 유도한다. fund-data.scheduler.sync(최신값 upsert)와 nav-proposals.scheduler.suggest를 대체했다
SCHEDactivity-events.scheduler.archive감사 로그의 보존·아카이브 sweep(v3-41)
SQSnotifications.worker.deliverSES 발송 워커다. 발송 큐를 비우면서 notification_deliveries 행마다 SENTFAILED를 쓰고, SQS 재전달로 재시도한다(1/5/15/60/240분 → DLQ). v3-103에서 notification-logs.scheduler.send 폴러를 대체했다
SCHEDnotifications.scheduler.sweepPENDING으로 남은 발송을 다시 큐에 넣는다(SQS 메시지 유실이나 워커가 도중에 죽은 경우)
HOOKnotifications.webhook.sesSES 반송·불만 이벤트를 BOUNCED / COMPLAINED로 반영하고 주소를 email_suppressions에 추가한다(HARD_BOUNCE / COMPLAINT)
WORKERpools.worker.deploy비동기 풀 배포다. PlatformPoolFactory.createPool()을 호출한다(POST /pools가 실행한다)
WORKERkyc.worker.apply-reviewSumSub webhook 뒤의 비동기 워커다. 심사 결과를 적용한다
WORKERkyc.worker.mint-sbtSBT 온체인 FIFO 큐의 SQS 컨슈머다(민팅 / 소각 / 취소)