3.8 KiB
3.8 KiB
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):
{
"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 userbinding.canBind: boolean, whether the current user can still bind another user's invite codebinding.boundInviteCode: string ornull, bound inviter code snapshotbinding.boundAt: ISO 8601 datetime ornullsummary.rewardPoints: integer>= 0, reward granted to inviter and invitee per qualified post-bind CREEM paymentsummary.invitedCount: integer>= 0summary.rewardedCount: integer>= 0summary.pendingCount: integer>= 0, computed asinvitedCount - rewardedCountsummary.rewardedPoints: integer>= 0, computed asrewardedCount * rewardPointssummary.totalPotentialRewardPoints: integer>= 0, computed asinvitedCount * rewardPointsitems: inviter-side referral list, newest firstitems[].firstCreemPaidAt: present only when the invitee has completed the qualifying CREEM payment after bindingitems[].rewardGranted: whether inviter-side invite reward has been crediteditems[].rewardGrantedAt: present only whenrewardGranted=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:
{
"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, optionalparams. - 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 userINVITE_BIND_CODE_NOT_FOUND(404): Invite code to bind does not existINVITE_ALREADY_BOUND(409): Current user already bound an inviterINVITE_SELF_BIND_FORBIDDEN(409): Current user attempted to bind their own codeINVITE_CODE_NOT_BINDABLE(409): Invite code is disabled, expired, or has no ownerINVITE_CODE_INVALID(422): Invite code format is invalid
Data model linkage
- Invite codes are stored in
invite_codestable. - Referral bindings are stored in
invite_referrals. - See
docs/protocols/common/user-points-chat-data-protocol.mdforprofiles.referred_by,invite_referrals,redeem_code_batches, andredeem_codes.