내 명함 편집 API 맥락 (Live Studio)
계약(엔드포인트·스키마·enum)의 원본은 frontmatter
spec:의 OpenAPI (2026-07-24 시드 완료). 이 문서는 스펙이 못 담는 행동 계약과 맥락만. 과거의live-studio.md포크와 FEbusiness-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_EXCEEDED는data.limit·data.scope동봉.IMAGES_PER_BLOCKscope는 구버전 호환용으로 FE만 인식 — BE는 더 이상 안 씀. - 정렬(
PATCH .../order):orderedIds는 해당 스코프의 전체 ID 집합과 정확히 일치해야 함(누락·중복·외부 ID → 422). 서버가 0..n-1 재부여. - 이미지 추가의
sortOrder는 요청에서 받지 않는다 — 서버가 마지막 다음 순서 부여. - 동시 이미지 추가는 명함 루트 쓰기 잠금 + version 검증으로 직렬화 — 최종 합계 6장 초과 불가.
엣지케이스 · gotcha
- 로고 삭제는 별도 엔드포인트가 아니라
PATCH /style에logoMediaAssetId: null. card.status(NFC 연결 상태UNCONNECTED|CONNECTED)와 공개 API의visibility(PUBLIC|PRIVATE)는 다른 축 — 두 live-studio 초안이 이 지점에서 서로 다르게 갈라져 있었음(중복 문서의 실제 피해 사례).- mutation 실패 시 FE는 로컬 입력값 유지 + 토스트 — 서버 상태로 롤백하지 않는다.
- 업로드 중 사용자가 삭제하면: 이미지 추가 POST 전이면 요청 중단, POST 성공 후면 삭제 API로 서버까지 정리.
- 명함 저장용 PNG는 FE가 생성·다운로드 — BE 엔드포인트 없음,
CARD_DOWNLOAD이벤트만 전송.
관련 문서
- 계약 원본: openapi.yaml
- 공개 조회 쪽: public-card.md · NFC 연결: nfc-card.md