Files
2026-05-21 16:26:58 +08:00

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 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:

{
  "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.