> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rabbithole.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Bind a referee to a referrer for one campaign

> Accepts the referee's signed per-campaign referral bind. The referral code IS the referrer's wallet address; the server canonicalizes smart-wallet proxies (Polymarket/Forkast Safe) to their owner EOA before storing. The referee signs EIP-712 typed data: domain {name: 'Boost TBI Referrals', version: '1', chainId}, type ReferralBind {campaignIndex: uint256, referrer: address, deadline: uint256}, where deadline is unix seconds at most one hour ahead. campaignIndex and deadline travel as decimal strings in the request body because both are signed as uint256. One referrer per referee per campaign — the first accepted bind wins and repeat submissions of the same bind are idempotent (created: false). Binds are rejected once the referee already has a position in the campaign (no retroactive attribution; re-checked authoritatively when the distribution is built) and are accepted only while the campaign is active — not before it starts, and not once it can no longer accrue rewards. Referral links only work between Rabbithole-linked wallets: the referee (403 WALLET_NOT_LINKED) and the canonicalized referrer (409 REFERRER_NOT_LINKED) must each hold an active Rabbithole wallet link. Referral loops are rejected (409 CYCLIC_REFERRAL): a bind whose referrer is already referred by the referee in the same campaign, directly (A refers B, B refers A) or through a chain of binds (A refers B, B refers C, C refers A), is refused; the same pair in a different campaign is unaffected. Alternatively, Rabbithole sessions bind without the deadline/signature fields by sending a Clerk session token as an Authorization bearer; the server verifies the token and requires the referee's active wallet link to belong to that Clerk user (401 INVALID_SESSION / 403 WALLET_NOT_LINKED otherwise).



## OpenAPI

````yaml https://api-tbi.boost.xyz/v1/openapi.json post /v1/referrals/bind
openapi: 3.1.0
info:
  title: Boost Time-Based Incentives SDK API
  version: 1.0.0
  description: >-
    Public /v1 API surface for the Boost TBI SDK. Internal Rabbithole, admin,
    Polymarket, and widget routes are intentionally excluded.
servers:
  - url: https://api-tbi.boost.xyz
    description: Production
  - url: http://localhost:3000
    description: Local development
security: []
tags:
  - name: Campaigns
    description: Campaign discovery, detail, and stats endpoints.
  - name: Claims
    description: Claim proof and claim status endpoints.
  - name: Forwarder
    description: Boost Forwarder target discovery and direct deposit transaction builders.
  - name: Referrals
    description: >-
      Referral attribution: signed per-campaign binds, referrer earnings, and
      claim proofs.
  - name: Users
    description: User rewards and balance endpoints.
paths:
  /v1/referrals/bind:
    post:
      tags:
        - Referrals
      summary: Bind a referee to a referrer for one campaign
      description: >-
        Accepts the referee's signed per-campaign referral bind. The referral
        code IS the referrer's wallet address; the server canonicalizes
        smart-wallet proxies (Polymarket/Forkast Safe) to their owner EOA before
        storing. The referee signs EIP-712 typed data: domain {name: 'Boost TBI
        Referrals', version: '1', chainId}, type ReferralBind {campaignIndex:
        uint256, referrer: address, deadline: uint256}, where deadline is unix
        seconds at most one hour ahead. campaignIndex and deadline travel as
        decimal strings in the request body because both are signed as uint256.
        One referrer per referee per campaign — the first accepted bind wins and
        repeat submissions of the same bind are idempotent (created: false).
        Binds are rejected once the referee already has a position in the
        campaign (no retroactive attribution; re-checked authoritatively when
        the distribution is built) and are accepted only while the campaign is
        active — not before it starts, and not once it can no longer accrue
        rewards. Referral links only work between Rabbithole-linked wallets: the
        referee (403 WALLET_NOT_LINKED) and the canonicalized referrer (409
        REFERRER_NOT_LINKED) must each hold an active Rabbithole wallet link.
        Referral loops are rejected (409 CYCLIC_REFERRAL): a bind whose referrer
        is already referred by the referee in the same campaign, directly (A
        refers B, B refers A) or through a chain of binds (A refers B, B refers
        C, C refers A), is refused; the same pair in a different campaign is
        unaffected. Alternatively, Rabbithole sessions bind without the
        deadline/signature fields by sending a Clerk session token as an
        Authorization bearer; the server verifies the token and requires the
        referee's active wallet link to belong to that Clerk user (401
        INVALID_SESSION / 403 WALLET_NOT_LINKED otherwise).
      operationId: bindReferral
      parameters:
        - name: X-Boost-Ref-Id
          in: header
          required: false
          description: >-
            Optional partner attribution reference ID. Values are manually
            assigned by Boost and must be at most 128 characters.
          schema:
            type: string
            minLength: 1
            maxLength: 128
            pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReferralBindRequest'
      responses:
        '200':
          description: Stored referral bind
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReferralBind'
        '400':
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '401':
          description: Invalid or expired signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '404':
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '409':
          description: Conflicting state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '429':
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1ErrorResponse'
components:
  schemas:
    ReferralBindRequest:
      type: object
      properties:
        chainId:
          type: integer
          exclusiveMinimum: 0
          maximum: 9007199254740991
        campaignIndex:
          type: string
          maxLength: 78
          pattern: ^(0|[1-9]\d*)$
        referee:
          type: string
        referrer:
          type: string
        deadline:
          type: string
          maxLength: 20
          pattern: ^(0|[1-9]\d*)$
        signature:
          type: string
          maxLength: 4096
          pattern: ^0x[0-9a-fA-F]+$
      required:
        - chainId
        - campaignIndex
        - referee
        - referrer
      additionalProperties: false
    ReferralBind:
      type: object
      properties:
        id:
          type: object
          properties:
            chainId:
              type: integer
              exclusiveMinimum: 0
              maximum: 9007199254740991
            campaignIndex:
              type: integer
              minimum: 0
              maximum: 9007199254740991
          required:
            - chainId
            - campaignIndex
          additionalProperties: false
        referee: {}
        referrer: {}
        boundAt:
          type: string
          pattern: ^(0|[1-9]\d*)$
        created:
          type: boolean
      required:
        - id
        - referee
        - referrer
        - boundAt
        - created
      additionalProperties: false
    V1ErrorResponse:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
          enum:
            - INVALID_PARAMS
            - NOT_FOUND
            - RATE_LIMITED
            - INTERNAL_ERROR
            - INVALID_SIGNATURE
            - SELF_REFERRAL
            - ALREADY_BOUND
            - ALREADY_PARTICIPATING
            - CAMPAIGN_NOT_BINDABLE
            - INVALID_SESSION
            - WALLET_NOT_LINKED
            - REFERRER_NOT_LINKED
            - CYCLIC_REFERRAL
        details:
          anyOf:
            - {}
            - type: 'null'
      required:
        - error
        - code
        - details
      additionalProperties: false

````