내 명함 편집 API 맥락 (Live Studio)

계약(엔드포인트·스키마·enum)의 원본은 frontmatter spec:의 OpenAPI (2026-07-24 시드 완료). 이 문서는 스펙이 못 담는 행동 계약과 맥락만. 과거의 live-studio.md 포크와 FE business-card-api-spec.md는 해체됐다(원문은 git 이력).

한눈에

엔드포인트역할 한 줄
GET /v1/me/business-card편집 진입 시 전체 스냅샷 + 현재 version
PATCH /v1/me/business-card/profile · /style · /slug섹션 단위 저장
/v1/me/business-card/links* · /text-blocks* · /image-blocks*블록 CRUD + 정렬
POST /v1/media-assets/presigned-url → S3 PUT → /complete이미지 3단계 업로드
POST /v1/me/business-card/publish · /unpublish공개·비공개 전환

왜 이렇게 생겼나

  • 단일 version 낙관적 동시성. 모든 쓰기는 expectedVersion을 싣고(PATCH/POST는 body, DELETE는 query ?expectedVersion={n}), 성공 응답은 항상 새 version을 반환 — FE가 다음 요청에 이어 쓴다. FE가 쓰기를 큐로 직렬화하므로 한 사용자의 쓰기는 사실상 순차적. 서버는 매 요청 최신 version만 정확히 돌려주면 된다.
  • 저장 전략이 필드별로 다른 이유: 프로필은 필수값(이름·직함·회사)이 있어 입력 중간 상태를 공개 데이터에 넣으면 안 됨 → 저장 버튼. 링크·텍스트는 타이핑 중 요청 폭주 방지 → debounce. 테마·추가·삭제·정렬은 의도가 명확 → 즉시 저장.
  • 이미지는 BE가 바이너리를 받지 않는다. presigned URL 3단계(발급 → S3 직접 PUT → complete 확정). complete 전에 mediaAssetId를 연결하면 422 MEDIA_ASSET_NOT_UPLOADED.

불변조건

  • 개수 제한: 섹션당 블록 5개, 명함 전체 이미지 6장(imageBlocks[*].images만 합산 — 아바타·로고 제외). FE 버튼 비활성은 1차 방어, 서버가 최종 방어선. 이미 초과된 기존 데이터는 조회·삭제 허용, 추가만 거부.
  • 오류는 status + code로만 분기(영문 message 비의존). LIMIT_EXCEEDEDdata.limit·data.scope 동봉. IMAGES_PER_BLOCK scope는 구버전 호환용으로 FE만 인식 — BE는 더 이상 안 씀.
  • 정렬(PATCH .../order): orderedIds는 해당 스코프의 전체 ID 집합과 정확히 일치해야 함(누락·중복·외부 ID → 422). 서버가 0..n-1 재부여.
  • 이미지 추가의 sortOrder는 요청에서 받지 않는다 — 서버가 마지막 다음 순서 부여.
  • 동시 이미지 추가는 명함 루트 쓰기 잠금 + version 검증으로 직렬화 — 최종 합계 6장 초과 불가.

엣지케이스 · gotcha

  • 로고 삭제는 별도 엔드포인트가 아니라 PATCH /stylelogoMediaAssetId: null.
  • cardTheme(구 cardColorTheme, 2026-07-25 개명): 커스텀 이미지 테마 추가로 “색상”이 이름과 어긋나 개명. CUSTOM_IMAGE는 연결된 NFC 카드에 이미지가 등록된 경우에만 저장 허용(아니면 422 VALIDATION_FAILED). 배경은 PRD.
  • 커스텀 이미지 존재 확인용 별도 API는 없다 — 조회 응답의 customCardImage(null 가능)로 판단. 저장된 cardTheme=CUSTOM_IMAGE인데 customCardImage가 null이면(이미지 삭제·카드 연결 해제) FE는 기본 테마로 렌더 폴백만 하고 저장값은 유지 — 이미지가 복구되면 자동 복귀. 리셋 로직을 만들지 말 것.
  • 커스텀 이미지 테마에서 “이미지 저장”은 화면 캡처(PNG 합성)가 아니라 등록된 원본 이미지 다운로드.
  • card.status(NFC 연결 상태 UNCONNECTED|CONNECTED)와 공개 API의 visibility(PUBLIC|PRIVATE)는 다른 축 — 두 live-studio 초안이 이 지점에서 서로 다르게 갈라져 있었음(중복 문서의 실제 피해 사례).
  • mutation 실패 시 FE는 로컬 입력값 유지 + 토스트 — 서버 상태로 롤백하지 않는다.
  • 업로드 중 사용자가 삭제하면: 이미지 추가 POST 전이면 요청 중단, POST 성공 후면 삭제 API로 서버까지 정리.
  • 명함 저장용 PNG는 FE가 생성·다운로드 — BE 엔드포인트 없음, CARD_DOWNLOAD 이벤트만 전송.

관련 문서