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

# SocialFi API quickstart

> Integrate the CabalSpy ID SocialFi API in five steps: create an instance, add the login button, verify the ID token and read your users' verified wallets and trading data.

This guide walks through a complete SocialFi integration with the CabalSpy ID SocialFi API: from an empty dashboard to your first signed-in user and their live trading data, the base for leaderboards, trader profiles, activity feeds and every other social feature on your platform. It uses the login button in a browser app and a small backend that verifies the ID token and calls the Data API.

<Steps>
  <Step title="Create an instance">
    Open the [API Dashboard](https://apidashboard.cabalspy.xyz), go to the **CabalSpy ID** tab and create an instance. Start in **sandbox** or **development** mode. Copy the Instance ID, it is your `client_id` and starts with `csid_`.

    Add the page that shows the button as a redirect URI, for example `https://app.example.com/`, and add its origin as a web origin. Redirect URIs must match exactly.
  </Step>

  <Step title="Add the login button">
    Paste the button where people should sign in. For a browser app the widget runs the whole PKCE flow itself.

    ```html theme={null}
    <div data-csid-login data-client-id="csid_..."></div>
    <script src="https://id.cabalspy.xyz/widget.js" async></script>
    ```

    After a successful sign-in the widget fires a `csid:login` event on `window`.

    ```javascript theme={null}
    window.addEventListener("csid:login", async (e) => {
      const { user, idToken } = e.detail;
      await fetch("/api/session", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ idToken }),
      });
    });
    ```

    Server-side apps set `data-href` to their own login route instead and run the flow with their client secret, see [OpenID Connect](/cabalspy-id/openid-connect).
  </Step>

  <Step title="Verify the ID token on your backend">
    Never trust the user object from the browser on its own. Verify the ID token's signature with the public keys at `https://id.cabalspy.xyz/oauth/jwks`, and check the issuer and the audience.

    <CodeGroup>
      ```javascript Node.js theme={null}
      import { createRemoteJWKSet, jwtVerify } from "jose";

      const JWKS = createRemoteJWKSet(new URL("https://id.cabalspy.xyz/oauth/jwks"));

      export async function verifyIdToken(idToken) {
        const { payload } = await jwtVerify(idToken, JWKS, {
          issuer: "https://id.cabalspy.xyz",
          audience: process.env.CSID_CLIENT_ID,   // your csid_... Instance ID
          algorithms: ["ES256"],
        });
        if (payload.limited) {
          // over your plan's user limit: anonymous sub, no profile data
        }
        return payload;   // payload.sub is the stable user id
      }
      ```

      ```python Python theme={null}
      import os
      import jwt

      jwks = jwt.PyJWKClient("https://id.cabalspy.xyz/oauth/jwks")

      def verify_id_token(id_token: str) -> dict:
          key = jwks.get_signing_key_from_jwt(id_token).key
          claims = jwt.decode(
              id_token, key,
              algorithms=["ES256"],
              issuer="https://id.cabalspy.xyz",
              audience=os.environ["CSID_CLIENT_ID"],   # your csid_... Instance ID
          )
          if claims.get("limited"):
              pass  # over your plan's user limit: anonymous sub, no profile data
          return claims  # claims["sub"] is the stable user id
      ```
    </CodeGroup>

    Store `sub` as the user's id in your database. It never changes for that person and your instance.

    <Tip>
      **Handling users without X.** People can sign in with X, Telegram or Google. Users without X have `x: null`, a handle like `tg.<name>` or `id.<hex>`, and possibly no `picture`. Show a fallback avatar, never key on the handle (it changes when they connect X later), and see [Users without X](/cabalspy-id/openid-connect#users-without-x).
    </Tip>
  </Step>

  <Step title="Enable your API key for CabalSpy ID">
    The Data API uses the same API key as the CabalSpy API. On the **API keys** page of the dashboard, switch on **CabalSpy ID** for the key. Then check it:

    ```bash theme={null}
    curl "https://api-id.cabalspy.xyz/v1/me" \
      -H "Authorization: Bearer YOUR_KEY"
    ```

    The response shows your instance, plan, request rate and how many users you receive data for. If your account has several instances, add `instance=csid_...`.
  </Step>

  <Step title="Call the Data API">
    Look up the signed-in user by the `sub` from the ID token, then load the trading profile of one of their verified wallets.

    <CodeGroup>
      ```bash cURL theme={null}
      curl "https://api-id.cabalspy.xyz/v1/profile?csid_id=USER_SUB" \
        -H "Authorization: Bearer YOUR_KEY"

      curl "https://api-id.cabalspy.xyz/v1/wallets/tracker?blockchain=solana&address=WALLET&period=7d" \
        -H "Authorization: Bearer YOUR_KEY"
      ```

      ```python Python theme={null}
      import requests

      API = "https://api-id.cabalspy.xyz/v1"
      H = {"Authorization": "Bearer YOUR_KEY"}

      profile = requests.get(f"{API}/profile", params={"csid_id": sub}, headers=H).json()["data"]
      for w in profile["wallets"]:
          for chain in w["chains"]:
              t = requests.get(f"{API}/wallets/tracker", headers=H,
                               params={"blockchain": chain, "address": w["address"], "period": "7d"}).json()
              print(chain, w["address"], t["data"]["period_stats"]["realized_pnl_usd"])
      ```

      ```javascript JavaScript theme={null}
      const API = "https://api-id.cabalspy.xyz/v1";
      const H = { Authorization: "Bearer YOUR_KEY" };

      const { data: profile } = await (await fetch(`${API}/profile?csid_id=${sub}`, { headers: H })).json();
      for (const w of profile.wallets) {
        for (const chain of w.chains) {
          const p = new URLSearchParams({ blockchain: chain, address: w.address, period: "7d" });
          const { data } = await (await fetch(`${API}/wallets/tracker?${p}`, { headers: H })).json();
          console.log(chain, w.address, data.period_stats.realized_pnl_usd);
        }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

<Tip>
  The official [SDKs](/cabalspy-id/sdks) do steps 3 to 5 in a few lines: `npm install cabalspy-id` (with a React button in `cabalspy-id/react`) or `pip install cabalspy-id`.
</Tip>

## Going live

When everything works, switch the instance to **live** in the dashboard. Live instances need a verified domain, a logo, privacy policy and terms URLs and a support e-mail, and every redirect URI and web origin must be on a verified domain. If you request the `wallets` or `onchain` scope, the instance is reviewed first.

## Next steps

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bolt" href="/cabalspy-id/webhooks">
    React when a user verifies a wallet or gets the KOL badge.
  </Card>

  <Card title="Plans and limits" icon="gauge" href="/cabalspy-id/plans">
    How many users you receive data for and how fast you can call the API.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.