# Invite Protocol (Frontend <-> Backend) This document defines the invite/referral contract for authenticated web users. Protocol verification status: - Backend route source: `backend/src/v1/invite/router.py` - Backend service source: `backend/src/v1/invite/service.py` - Backend schema source: `backend/src/v1/invite/schemas.py` - Web mapping source: `web/src/lib/api.ts` ## Compatibility strategy - Additive evolution only. - Existing response fields are stable and must remain backward-compatible. ## Route ### GET /api/v1/invite/me Get the current user's invite overview. **Authorization**: Requires authenticated session. User identity from JWT `sub`. **Response (200)**: ```json { "myCode": "ABC123", "binding": { "canBind": false, "boundInviteCode": "QWE789", "boundAt": "2026-05-21T10:30:00+00:00" }, "summary": { "rewardPoints": 40, "invitedCount": 3, "rewardedCount": 2, "pendingCount": 1, "rewardedPoints": 80, "totalPotentialRewardPoints": 120 }, "items": [ { "referralId": "5ed7c3f0-6d1c-4f3f-8b40-2cb537da53e6", "inviteCode": "ABC123", "boundAt": "2026-05-18T09:00:00+00:00", "firstCreemPaidAt": "2026-05-20T11:15:00+00:00", "rewardGranted": true, "rewardGrantedAt": "2026-05-20T11:15:02+00:00" } ] } ``` Field rules: - `myCode`: string, unique invite code assigned to the current user - `binding.canBind`: boolean, whether the current user can still bind another user's invite code - `binding.boundInviteCode`: string or `null`, bound inviter code snapshot - `binding.boundAt`: ISO 8601 datetime or `null` - `summary.rewardPoints`: integer `>= 0`, reward granted to inviter and invitee per qualified post-bind CREEM payment - `summary.invitedCount`: integer `>= 0` - `summary.rewardedCount`: integer `>= 0` - `summary.pendingCount`: integer `>= 0`, computed as `invitedCount - rewardedCount` - `summary.rewardedPoints`: integer `>= 0`, computed as `rewardedCount * rewardPoints` - `summary.totalPotentialRewardPoints`: integer `>= 0`, computed as `invitedCount * rewardPoints` - `items`: inviter-side referral list, newest first - `items[].firstCreemPaidAt`: present only when the invitee has completed the qualifying CREEM payment after binding - `items[].rewardGranted`: whether inviter-side invite reward has been credited - `items[].rewardGrantedAt`: present only when `rewardGranted=true` ### POST /api/v1/invite/bind Bind another user's invite code to the current account. This endpoint is write-once: - a user can bind at most once; - binding cannot be removed; - self-binding is forbidden; - binding is allowed even if the current user already has completed CREEM payments. **Authorization**: Requires authenticated session. **Request**: ```json { "code": "ABC123" } ``` Field rules: - `code`: string, exactly 6 uppercase alphanumeric invite characters after normalization **Response (200)**: Same shape as `GET /api/v1/invite/me` after the bind succeeds. ## Error contract linkage - RFC7807 + extension `code`, optional `params`. - Shared registry: `docs/protocols/common/http-error-codes.md`. - Error codes for this feature: - `INVITE_CODE_NOT_FOUND` (404): Invite code not found for current user - `INVITE_BIND_CODE_NOT_FOUND` (404): Invite code to bind does not exist - `INVITE_ALREADY_BOUND` (409): Current user already bound an inviter - `INVITE_SELF_BIND_FORBIDDEN` (409): Current user attempted to bind their own code - `INVITE_CODE_NOT_BINDABLE` (409): Invite code is disabled, expired, or has no owner - `INVITE_CODE_INVALID` (422): Invite code format is invalid ## Data model linkage - Invite codes are stored in `invite_codes` table. - Referral bindings are stored in `invite_referrals`. - See `docs/protocols/common/user-points-chat-data-protocol.md` for `profiles.referred_by`, `invite_referrals`, `redeem_code_batches`, and `redeem_codes`.