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

# Reverse X lookup

> GET /v1/reverse/twitter/{handle}

Finds every wallet linked to an X handle. Costs **1 match credit per wallet returned**, 100 per page. A handle with no wallets attached costs nothing. Separately, each request weighs 2 units against your rate limit.

This runs the graph backwards, and it answers a question most wallet tooling cannot: given a person, what do they hold?

## Path parameters

<ParamField path="handle" type="string" required>
  1 to 15 characters, letters, numbers and underscores. A leading `@` is
  accepted and stripped. Case-insensitive.
</ParamField>

## Query parameters

<ParamField query="cursor" type="string">
  Continues a paginated result: pass the `next_cursor` value from the previous
  response, unmodified. Omit it for the first page. A value this API did not
  produce returns `INVALID_CURSOR`.
</ParamField>

## Request

```bash theme={null}
curl https://walletlink.social/api/v1/reverse/twitter/example_user \
  -H "Authorization: Bearer wts_live_YOUR_KEY"
```

## Response

<ResponseField name="data" type="array">
  Matching wallets, at most 100 per page, ordered by Farcaster reach (highest
  follower count first, wallets without one last, wallet address as the
  tiebreak). Each entry carries `wallet`, an always-present `twitter` object,
  and whichever of `ens_name`, `farcaster`, `lens`, `github` and `sources`
  apply, plus a flat `quality_score`.
</ResponseField>

<ResponseField name="meta" type="object">
  <Expandable title="properties">
    <ResponseField name="handle" type="string">
      The normalized handle, lowercased and without `@`.
    </ResponseField>

    <ResponseField name="total_count" type="number">
      Total matches in the index, across all pages.
    </ResponseField>

    <ResponseField name="returned_count" type="number">
      How many are in this response.
    </ResponseField>

    <ResponseField name="truncated" type="boolean">
      Whether more results remain after this page.
    </ResponseField>

    <ResponseField name="next_cursor" type="string | null">
      Pass as `cursor` to fetch the next page. `null` on the last page.
    </ResponseField>
  </Expandable>
</ResponseField>

```json theme={null}
{
  "data": [
    {
      "wallet": "0x00000000000000000000000000000000000000a1",
      "ens_name": "example.eth",
      "twitter": {
        "handle": "example_user",
        "url": "https://x.com/example_user",
        "verified": true,
        "reachable": true,
        "reachability": "live",
        "reachability_checked_at": "2026-08-21T14:02:11.000Z"
      },
      "sources": ["onchain"],
      "quality_score": 70
    }
  ],
  "meta": {
    "handle": "example_user",
    "total_count": 1,
    "returned_count": 1,
    "truncated": false,
    "next_cursor": null
  }
}
```

<Note>
  `quality_score` here is a flat number, unlike the nested `quality` object on
  [single lookup](/api-reference/wallet). Same scale, different shape.
</Note>

## One handle, many wallets

Multiple results are normal, not a bug. People keep a hot wallet and a vault, and the same handle can be attested from several addresses.

## Second accounts are matched too

A wallet is returned when the handle is its primary X account **or** its second attested one. Some owners have attested two live X accounts from the same wallet through different evidence; a single lookup shows that as [`twitter.also`](/api-reference/wallet#a-second-x-account), and searching either handle finds the wallet.

A wallet matched this way has a different handle in its own `twitter.handle` field, and names the handle you searched for under `twitter.also`. The same conditions apply as for `twitter.also` itself: both accounts must be live, and where the attestation carried an account id it must still match. Nothing is returned on the strength of a handle we cannot corroborate.

These wallets bill exactly as any other: 1 match credit per wallet returned.

The endpoint returns at most 100 wallets per page. When `truncated` is true, pass `meta.next_cursor` back as the `cursor` query parameter to fetch the next page; the last page carries `next_cursor: null`. Each page is its own request: it weighs 2 rate-limit units and bills 1 match credit per wallet it returns.

Pages walk the index in Farcaster-reach order, so the first page holds the most reachable wallets. The cursor is a position in a live index, not a snapshot: a wallet whose follower count changes mid-walk can appear twice or be skipped, and `total_count` can move between pages. Treat the cursor as opaque, use it promptly, and do not store it.

## Errors

`INVALID_HANDLE`, `INVALID_CURSOR`, plus the [standard errors](/api-reference/errors).

No matches is not an error: `data` is `[]` with `total_count: 0` at HTTP 200.
