Estimate
curl --request POST \
--url https://walletlink.social/api/v1/estimate \
--header 'Content-Type: application/json' \
--data '
{
"wallets": [
"<string>"
]
}
'import requests
url = "https://walletlink.social/api/v1/estimate"
payload = { "wallets": ["<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({wallets: ['<string>']})
};
fetch('https://walletlink.social/api/v1/estimate', 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://walletlink.social/api/v1/estimate",
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([
'wallets' => [
'<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://walletlink.social/api/v1/estimate"
payload := strings.NewReader("{\n \"wallets\": [\n \"<string>\"\n ]\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://walletlink.social/api/v1/estimate")
.header("Content-Type", "application/json")
.body("{\n \"wallets\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://walletlink.social/api/v1/estimate")
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 \"wallets\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data.requested": 123,
"data.in_index": 123,
"data.previously_checked_empty": 123,
"data.never_checked": 123,
"data.would_bill_estimate": {}
}Lookups
Estimate
POST /v1/estimate
POST
/
api
/
v1
/
estimate
Estimate
curl --request POST \
--url https://walletlink.social/api/v1/estimate \
--header 'Content-Type: application/json' \
--data '
{
"wallets": [
"<string>"
]
}
'import requests
url = "https://walletlink.social/api/v1/estimate"
payload = { "wallets": ["<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({wallets: ['<string>']})
};
fetch('https://walletlink.social/api/v1/estimate', 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://walletlink.social/api/v1/estimate",
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([
'wallets' => [
'<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://walletlink.social/api/v1/estimate"
payload := strings.NewReader("{\n \"wallets\": [\n \"<string>\"\n ]\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://walletlink.social/api/v1/estimate")
.header("Content-Type", "application/json")
.body("{\n \"wallets\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://walletlink.social/api/v1/estimate")
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 \"wallets\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data.requested": 123,
"data.in_index": 123,
"data.previously_checked_empty": 123,
"data.never_checked": 123,
"data.would_bill_estimate": {}
}A dry run over a list: how many of these addresses are in the index, how many
we checked and found bare, how many we have never seen, and the band a resolve
would bill inside. Counts only, never identities. Use it to decide whether a
list is worth spending on, and which endpoint to spend on, before anything
bills.
It costs no match credits, at any balance, so a drained key can plan the
purchase that refills it. It weighs the rate window exactly like the
batch it previews, one unit per address, and the list
is capped at your plan’s
max_batch_size. The minimum is 10 distinct
addresses, because the counts are aggregates by design; for one address,
resolve it.
Request
curl -X POST https://walletlink.social/api/v1/estimate \
-H "Authorization: Bearer wts_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"wallets": ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "..."]}'
string[]
required
10 to
max_batch_size addresses. Duplicates are removed before counting; at
least 10 distinct addresses must remain.Response
number
Distinct addresses after deduplication.
number
Addresses holding at least one identity: X, Farcaster, ENS, Lens or GitHub.
number
Addresses we checked and found bare. Resolving these is free and unlikely to
find anything until the negative record ages out.
number
Addresses the index has never seen. A plain batch returns
null for these; a
job resolves them against live sources.object
low, high and a note saying how to read them.{
"data": {
"requested": 500,
"in_index": 138,
"previously_checked_empty": 214,
"never_checked": 148,
"would_bill_estimate": {
"low": 121,
"high": 157,
"note": "low is exact for a /v1/batch of this list: the wallets already holding an X handle or a Farcaster account. high adds never-checked wallets resolving at the measured overall rate..."
}
},
"meta": {
"generated_at": "2026-09-01T12:00:00.000Z",
"billable_predicate": "x_or_farcaster"
}
}
Reading the band
low is exact, not an estimate: it is what a batch of
this list bills today, because a batch bills the addresses already holding an
X handle or a Farcaster account. It can sit below in_index, because an
address carrying only an ENS name, a Lens profile or a GitHub account is
returned and never billed.
high adds the never-checked addresses at the measured overall match rate. A
job resolves those against live sources, so high is
the job-shaped ceiling; a plain batch stays at low. It is a band, not a
quote: match rates differ several-fold by chain, and an address does not say
which chain its holder community lives on. Take the measured per-chain table
from GET /v1/stats match_rates and use the row for
your chain.
Errors
The standard errors, plusLIST_TOO_SMALL (400) when
fewer than 10 distinct addresses remain after deduplication, and
BATCH_SIZE_EXCEEDED (400) when the list is over your plan’s ceiling; the
message names it.