Bind a referee to a referrer for one campaign
curl --request POST \
--url https://api-tbi.boost.xyz/v1/referrals/bind \
--header 'Content-Type: application/json' \
--data '
{
"chainId": 123,
"campaignIndex": "<string>",
"referee": "<string>",
"referrer": "<string>",
"deadline": "<string>",
"signature": "<string>"
}
'import requests
url = "https://api-tbi.boost.xyz/v1/referrals/bind"
payload = {
"chainId": 123,
"campaignIndex": "<string>",
"referee": "<string>",
"referrer": "<string>",
"deadline": "<string>",
"signature": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
chainId: 123,
campaignIndex: '<string>',
referee: '<string>',
referrer: '<string>',
deadline: '<string>',
signature: '<string>'
})
};
fetch('https://api-tbi.boost.xyz/v1/referrals/bind', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-tbi.boost.xyz/v1/referrals/bind",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'chainId' => 123,
'campaignIndex' => '<string>',
'referee' => '<string>',
'referrer' => '<string>',
'deadline' => '<string>',
'signature' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-tbi.boost.xyz/v1/referrals/bind"
payload := strings.NewReader("{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-tbi.boost.xyz/v1/referrals/bind")
.header("Content-Type", "application/json")
.body("{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-tbi.boost.xyz/v1/referrals/bind")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": {
"chainId": 123,
"campaignIndex": 4503599627370495
},
"referee": "<unknown>",
"referrer": "<unknown>",
"boundAt": "<string>",
"created": true
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}Referrals
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).
POST
/
v1
/
referrals
/
bind
Bind a referee to a referrer for one campaign
curl --request POST \
--url https://api-tbi.boost.xyz/v1/referrals/bind \
--header 'Content-Type: application/json' \
--data '
{
"chainId": 123,
"campaignIndex": "<string>",
"referee": "<string>",
"referrer": "<string>",
"deadline": "<string>",
"signature": "<string>"
}
'import requests
url = "https://api-tbi.boost.xyz/v1/referrals/bind"
payload = {
"chainId": 123,
"campaignIndex": "<string>",
"referee": "<string>",
"referrer": "<string>",
"deadline": "<string>",
"signature": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
chainId: 123,
campaignIndex: '<string>',
referee: '<string>',
referrer: '<string>',
deadline: '<string>',
signature: '<string>'
})
};
fetch('https://api-tbi.boost.xyz/v1/referrals/bind', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-tbi.boost.xyz/v1/referrals/bind",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'chainId' => 123,
'campaignIndex' => '<string>',
'referee' => '<string>',
'referrer' => '<string>',
'deadline' => '<string>',
'signature' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-tbi.boost.xyz/v1/referrals/bind"
payload := strings.NewReader("{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-tbi.boost.xyz/v1/referrals/bind")
.header("Content-Type", "application/json")
.body("{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-tbi.boost.xyz/v1/referrals/bind")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"chainId\": 123,\n \"campaignIndex\": \"<string>\",\n \"referee\": \"<string>\",\n \"referrer\": \"<string>\",\n \"deadline\": \"<string>\",\n \"signature\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": {
"chainId": 123,
"campaignIndex": 4503599627370495
},
"referee": "<unknown>",
"referrer": "<unknown>",
"boundAt": "<string>",
"created": true
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}{
"error": "<string>",
"code": "INVALID_PARAMS",
"details": null
}Headers
Optional partner attribution reference ID. Values are manually assigned by Boost and must be at most 128 characters.
Required string length:
1 - 128Pattern:
^[A-Za-z0-9][A-Za-z0-9._:-]*$Body
application/json
Required range:
x <= 9007199254740991Maximum string length:
78Pattern:
^(0|[1-9]\d*)$Maximum string length:
20Pattern:
^(0|[1-9]\d*)$Maximum string length:
4096Pattern:
^0x[0-9a-fA-F]+$